N or P
Contents Glossary
19

Optimization with continuous numbers

Real-valued optimization with linear constraints is in the cheap category — even at millions of variables. Buy or open-source the solver.

Linear programming covers an enormous fraction of business optimization. Variables are continuous (money, hours, units of inventory). Constraints and objective are linear (budget caps, capacity limits, sum-to-one). Modern solvers — HiGHS (open source), Gurobi, CPLEX — handle millions of variables in seconds. Diet planning, blending problems, transportation problems, scheduling relaxations, network flow at scale, portfolio allocation, blending ad targeting — all linear programs. Rust's lp" data-term="good_lp">good_lp library lets your team model the problem in code (define variables, add constraints, set objective) and dispatch to a backend solver. HiGHS is the recommended open-source backend. When some variables must be integer (page 32), the problem moves to the expensive category. When the variables are continuous, the work is industrial and your team should not be writing their own solver.

  1. 01
    good_lp lets your team model the LP in code — variables with bounds, linear constraints with operator overloading, an objective — and dispatch to a backend solver. HiGHS is the recommended open-source backend; the model below is a diet problem in three lines of math.
    use good_lp::{constraint, default_solver, variables, SolverModel, Solution};
    
    fn cheapest_diet() -> (f64, f64, f64) {
        variables! { vars: 0 <= bread; 0 <= rice; 0 <= eggs; }
        let model = vars
            .minimise(2.0 * bread + 1.5 * rice + 3.0 * eggs) // dollars
            .using(default_solver)
            .with(constraint!(80.0 * bread + 130.0 * rice + 70.0 * eggs >= 2000.0))
            .with(constraint!( 3.0 * bread +   2.0 * rice + 13.0 * eggs >= 50.0));
        let sol = model.solve().expect("feasible");
        (sol.value(bread), sol.value(rice), sol.value(eggs))
    }
  2. 02
    The trailing exclamation point in variables! (and constraint! two lines below) marks them as macros, not regular function calls. Rust macros are metaprogramming — they take code at compile time and transform it into other code before normal compilation runs. The ! is how Rust distinguishes a macro invocation from a function call; the rest of the call site looks similar. Macros exist because some patterns cannot be expressed as ordinary functions: taking a variable number of arguments, accepting non-expression syntax like 0 <= bread; 0 <= rice;, or declaring new variables in the caller's scope. variables! does that last thing — it expands at compile time into a sequence of let bindings, one per variable name listed, plus a ProblemVariables builder. A regular function call cannot introduce new local bindings into its caller, so this work has to happen via macro. constraint! is a macro for a related reason: it parses an inequality expression with <= or >= in the middle, which is not how function arguments normally look.
    // Macros are marked with a trailing ! — the only syntactic difference
    // from a function call.
    println!("hello");        // macro
    format!("hi {}", name);   // macro
    vec![1, 2, 3];            // macro (and uses [] instead of ())
    assert_eq!(x, y);         // macro
    
    some_function(x);         // not a macro — no !
    
    
    // What variables! roughly expands to at compile time:
    
    variables! { vars: 0 <= bread; 0 <= rice; 0 <= eggs; }
    
    //  ≈
    
    let mut vars = ProblemVariables::new();
    let bread = vars.add(variable().min(0.0));
    let rice  = vars.add(variable().min(0.0));
    let eggs  = vars.add(variable().min(0.0));
    
    // A regular function CAN'T do this — function calls cannot introduce
    // new bindings (`bread`, `rice`, `eggs`) into their caller's scope.
    // Macros expand inline AT the call site, so the bindings appear
    // where they're written.
    
    
    // Two flavors of macro you'll meet in real Rust code:
    //
    //   macro_rules! NAME { ... }    declarative macros — pattern-match on
    //                                token trees.  vec!, println!, todo!.
    //
    //   #[derive(Debug)]              procedural macros — actual Rust code
    //   #[serde(rename = "x")]        that takes a TokenStream and returns
    //   sql!("SELECT ...")            one.  Includes #[derive], attribute
    //                                 macros, and function-like procedural
    //                                 macros (the !-call variety).
    
    
    // Common macros you'll see everywhere in Rust:
    //   println!  print!  eprintln!     formatted output
    //   vec!                              build a Vec inline
    //   format!                           build a String inline
    //   assert!  assert_eq!  assert_ne!   tests and runtime checks
    //   panic!                            crash with a message
    //   dbg!                              print + return — for debugging
    //   todo!  unimplemented!             placeholders that compile but panic
    //   write!  writeln!                  format into a Write target
Dantzig, G. (1947) simplex. Khachiyan, L. (1979) ellipsoid (proved LP is polynomial). Karmarkar, N. (1984) interior-point. Source →
In plain terms

Linear programming is the workhorse of operations research. An enormous fraction of business optimization problems can be modeled as: pick values for a set of real-valued variables, subject to linear inequality constraints, to maximize or minimize a linear objective. Diet planning. Production blending. Transportation costs. Portfolio allocation. Network flow. Advertising auctions. Workforce scheduling relaxations.

The modeling discipline is to write the problem in three pieces: variables (what can vary), constraints (what limits the variables), and objective (what to maximize or minimize). Once it is in that form, the solver does the rest. Modern solvers like HiGHS handle problems with millions of variables and constraints in seconds.

The history is instructive. George Dantzig invented the simplex method in 1947 for the US Air Force, who needed to plan logistics. Simplex is exponential in the worst case but almost always fast in practice. In 1979, Khachiyan proved that linear programming is genuinely in the cheap category — there is a polynomial-time algorithm. In 1984, Karmarkar found a practical polynomial-time alternative (interior-point), which kicked off a revolution. Modern solvers combine simplex and interior-point methods.

In Rust, the lp" data-term="good_lp">good_lp library is the standard. Your team declares variables, adds constraints with operator overloading, sets the objective, and calls solve. The library dispatches to a backend. HiGHS is the recommended open-source backend; commercial backends (Gurobi, CPLEX) handle larger problems faster but cost real money.

The boundary to remember: linear programming with continuous variables is in the cheap category. Linear programming with integer variables (page 32) is NP-complete and expensive. The continuous-versus-integer distinction is one of the most consequential complexity boundaries in applied mathematics.

When your problem has linear constraints and continuous variables, you have a solved problem and should reach for a solver. When it has integer variables, the work changes — see page 32.

← 18 How different are these two strings
19/39
20 Optimization with curves instead of lines →