### Full Resource Allocation LP Example in Rust Source: https://context7.com/rust-or/good_lp/llms.txt This snippet demonstrates a complete LP problem setup. It defines structs for products and the problem, dynamically adds products with their resource requirements and values, and solves the problem with resource constraints. Ensure good_lp is added to Cargo.toml with a solver feature. ```rust use good_lp::{ variable, variables, Expression, Constraint, constraint, default_solver, Solution, SolverModel, Variable, }; use good_lp::variable::ProblemVariables; struct Product { fuel: f64, time: f64, value: f64 } struct Problem { vars: ProblemVariables, total_value: Expression, fuel_used: Expression, time_used: Expression, } impl Problem { fn new() -> Self { Self { vars: variables!(), total_value: Expression::from(0.), fuel_used: Expression::from(0.), time_used: Expression::from(0.), } } fn add_product(&mut self, p: Product) -> Variable { let qty = self.vars.add(variable().min(0.)); self.total_value += qty * p.value; self.fuel_used += qty * p.fuel; self.time_used += qty * p.time; qty } fn solve(self, max_fuel: f64, max_time: f64) -> impl Solution { let obj = self.total_value; self.vars .maximise(obj) .using(default_solver) .with(self.fuel_used.leq(max_fuel)) .with(self.time_used.leq(max_time)) .solve() .unwrap() } } fn main() { let mut pb = Problem::new(); let steel = pb.add_product(Product { fuel: 1., time: 1., value: 10. }); let ss_steel = pb.add_product(Product { fuel: 2., time: 1., value: 11. }); let sol = pb.solve(5., 3.); println!("steel={}, stainless={}", sol.value(steel), sol.value(ss_steel)); // steel=1, stainless=2 → profit = 10 + 22 = 32 } ``` -------------------------------- ### Warm-Start Solver with `WithInitialSolution` Source: https://context7.com/rust-or/good_lp/llms.txt Use `with_initial_solution` to provide a feasible starting point for MIP problems, potentially speeding up the solving process. Supported by HiGHS and CBC. ```rust use good_lp::{ variables, variable, default_solver, SolverModel, Solution, constraint, solvers::WithInitialSolution, }; #[cfg(any(feature = "highs", feature = "coin_cbc"))] fn main() -> Result<(), Box> { variables! { vars: 0 <= x (integer) <= 10; 0 <= y (integer) <= 10; } // Provide a warm-start: x=4, y=6 is feasible and near-optimal let model = vars .maximise(x + y) .using(default_solver) .with_initial_solution([(x, 4.), (y, 6.)]) .with(constraint!(x + y <= 15)); let solution = model.solve()?; println!("x={}, y={}", solution.value(x), solution.value(y)); // x=10, y=5 Ok(()) } ``` -------------------------------- ### Install CBC Solver Dependencies on Ubuntu Source: https://github.com/rust-or/good_lp/blob/main/README.md On Ubuntu systems, install the necessary CBC solver and its development headers using apt-get. ```bash sudo apt-get install coinor-cbc coinor-libcbc-dev ``` -------------------------------- ### Install SCIP with Conda Source: https://github.com/rust-or/good_lp/blob/main/README.md Use this command to install SCIP through conda-forge. This is a convenient way to set up SCIP for use with good_lp. ```sh conda install --channel conda-forge scip ``` -------------------------------- ### Install CBC Solver Dependencies on MacOS Source: https://github.com/rust-or/good_lp/blob/main/README.md For MacOS users, install the CBC solver using the Homebrew package manager. ```bash brew install cbc ``` -------------------------------- ### Add Constraints to Solver Model with good_lp Source: https://context7.com/rust-or/good_lp/llms.txt Add constraints to a `SolverModel` using `.with()` for single constraints, `.with_all()` for multiple constraints from an iterator, or `.add_constraint()` to get a `ConstraintReference`. Requires a solver to be specified using `.using()`. ```rust use good_lp::{variables, variable, default_solver, SolverModel, Solution, constraint}; fn main() -> Result<(), Box> { variables! { vars: 0 <= x; 0 <= y; 0 <= z; } let mut model = vars.maximise(x + y + z).using(default_solver); // Add a single constraint with the builder let model = model .with(constraint!(x + y <= 10)) // Add multiple constraints at once from an iterator .with_all([ constraint!(y + z <= 8), constraint!(x + z <= 9), ]); let solution = model.solve()?; println!("x={}, y={}, z={}", solution.value(x), solution.value(y), solution.value(z)); // x=9, y=1, z=7 (total=17) Ok(()) } ``` -------------------------------- ### Define constraints with the `constraint!` macro Source: https://context7.com/rust-or/good_lp/llms.txt The `constraint!` macro provides a readable, infix syntax for defining linear constraints using `<=`, `>=`, and `==` operators. This example demonstrates inequality, equality, and compound expression constraints. ```rust use good_lp::{variables, variable, default_solver, SolverModel, Solution, constraint}; use float_eq::assert_float_eq; fn main() -> Result<(), Box> { variables! { vars: 0 <= a <= 10; 0 <= b <= 10; 0 <= c <= 10; } let solution = vars .maximise(a + b + c) .using(default_solver) // Inequality constraint .with(constraint!(a + b <= 15)) // Equality constraint .with(constraint!(b == c)) // Compound expression on both sides .with(constraint!(2 * a + b >= c + 3)) .solve()?; assert_float_eq!(solution.value(b), solution.value(c), abs <= 1e-8); println!("a={}, b={}, c={}", solution.value(a), solution.value(b), solution.value(c)); // a=10, b=5, c=5 (total=20) Ok(()) } ``` -------------------------------- ### Set objective direction with `optimise` Source: https://context7.com/rust-or/good_lp/llms.txt Use the `optimise` method with an `ObjectiveDirection` enum to programmatically select whether to maximise or minimise. This example shows how to set up a simple problem and solve it for both directions. ```rust use good_lp::{variables, default_solver, SolverModel, Solution, ObjectiveDirection}; use float_eq::assert_float_eq; fn solve_with_direction(direction: ObjectiveDirection) -> f64 { variables! { prob: 2. <= x <= 8.; } prob.optimise(direction, x) .using(default_solver) .solve() .unwrap() .value(x) } fn main() { let min_val = solve_with_direction(ObjectiveDirection::Minimisation); let max_val = solve_with_direction(ObjectiveDirection::Maximisation); assert_float_eq!(min_val, 2., abs <= 1e-8); assert_float_eq!(max_val, 8., abs <= 1e-8); println!("min={min_val}, max={max_val}"); // min=2, max=8 } ``` -------------------------------- ### Add variables dynamically with `ProblemVariables` Source: https://context7.com/rust-or/good_lp/llms.txt Use `ProblemVariables` methods like `add_all` to build variable collections when the number of variables is only known at runtime. This example demonstrates adding binary variables for items in a knapsack-like problem. ```rust use good_lp::{variable, variables, Expression, default_solver, SolverModel, Solution, constraint}; fn main() -> Result<(), Box> { let items: Vec<(f64, f64)> = vec![ (10., 5.), // (value, weight) (6., 3.), (12., 7.), (4., 2.), ]; let capacity = 10.; let mut prob = variables!(); // Dynamically add one binary variable per item let selected: Vec<_> = prob.add_all(items.iter().map(|_| variable().binary())); // Build objective and constraint expressions programmatically let mut objective = Expression::with_capacity(items.len()); let mut weight_sum = Expression::with_capacity(items.len()); for ((&(value, weight), &var)) in items.iter().zip(selected.iter()) { objective.add_mul(value, var); weight_sum.add_mul(weight, var); } let solution = prob .maximise(objective.clone()) .using(default_solver) .with(weight_sum.leq(capacity)) .solve()?; for (i, &var) in selected.iter().enumerate() { if solution.value(var) > 0.5 { println!("Item {} selected (value={}, weight={})", i, items[i].0, items[i].1); } } // Total value: 22 (items 0 and 2) println!("Total value = {}", objective.eval_with(&solution)); Ok(()) } ``` -------------------------------- ### `SolverModel::with` / `with_all` / `add_constraint` Source: https://context7.com/rust-or/good_lp/llms.txt Explains how to add constraints to a `SolverModel` using builder methods like `.with()`, batch method `.with_all()`, or the mutable `.add_constraint()`. ```APIDOC ## `SolverModel::with` / `with_all` / `add_constraint` — Add constraints to a model After calling `.using(solver)`, the resulting `SolverModel` accepts constraints via the builder method `.with()`, a batch method `.with_all()`, or the mutable `.add_constraint()` which returns a `ConstraintReference` for later use (e.g., dual values). ```rust use good_lp::{variables, variable, default_solver, SolverModel, Solution, constraint}; fn main() -> Result<(), Box> { variables! { vars: 0 <= x; 0 <= y; 0 <= z; } let mut model = vars.maximise(x + y + z).using(default_solver); // Add a single constraint with the builder let model = model .with(constraint!(x + y <= 10)) // Add multiple constraints at once from an iterator .with_all([ constraint!(y + z <= 8), constraint!(x + z <= 9), ]); let solution = model.solve()?; println!("x={}, y={}, z={}", solution.value(x), solution.value(y), solution.value(z)); // x=9, y=1, z=7 (total=17) Ok(()) } ``` ``` -------------------------------- ### WithInitialSolution Source: https://context7.com/rust-or/good_lp/llms.txt Enables warm-starting the solver by providing an initial feasible solution, which can significantly speed up the solving process for MIP problems. Supported by HiGHS and CBC. ```APIDOC ## `WithInitialSolution` — Warm-start the solver Providing an initial feasible solution can dramatically speed up solving for MIP problems. Supported by HiGHS and CBC. ```rust use good_lp::{ variables, variable, default_solver, SolverModel, Solution, constraint, solvers::WithInitialSolution, }; #[cfg(any(feature = "highs", feature = "coin_cbc"))] fn main() -> Result<(), Box> { variables! { vars: 0 <= x (integer) <= 10; 0 <= y (integer) <= 10; } // Provide a warm-start: x=4, y=6 is feasible and near-optimal let model = vars .maximise(x + y) .using(default_solver) .with_initial_solution([(x, 4.), (y, 6.)]) .with(constraint!(x + y <= 15)); let solution = model.solve()?; println!("x={}, y={}", solution.value(x), solution.value(y)); // x=10, y=5 Ok(()) } ``` ``` -------------------------------- ### Define and Solve a Simple MILP Problem in Rust Source: https://github.com/rust-or/good_lp/blob/main/README.md Demonstrates how to define variables with constraints, set an objective function, and solve a simple MILP problem using the default solver. Ensure the `good_lp` crate is added as a dependency. ```rust use std::error::Error; use good_lp::{constraint, default_solver, Solution, SolverModel, variables}; fn main() -> Result<(), Box> { variables! { vars: a <= 1; 2 <= b <= 4; } // variables can also be added dynamically with ProblemVariables::add let solution = vars.maximise(10 * (a - b / 5) - b) .using(default_solver) // IBM's coin_cbc by default .with(constraint!(a + 2 <= b)) .with(constraint!(1 + a >= 4 - b)) // .with_all(iter) is also available .solve()?; println!("a={} b={}", solution.value(a), solution.value(b)); println!("a + b = {}", solution.eval(a + b)); Ok(()) } ``` -------------------------------- ### `SolutionWithDual` / `DualValues` Source: https://context7.com/rust-or/good_lp/llms.txt Details on how to retrieve dual values (shadow prices) from a solved model for solvers that support it, indicating the impact of constraint relaxation on the objective. ```APIDOC ## `SolutionWithDual` / `DualValues` — Retrieve shadow prices (dual values) For solvers that support it (HiGHS, Clarabel), dual values (shadow prices) can be retrieved after solving. A dual value measures how much the objective would improve per unit relaxation of a constraint. ```rust use good_lp::{ variables, variable, default_solver, SolverModel, Solution, constraint, solvers::{SolutionWithDual, DualValues}, }; use float_eq::assert_float_eq; // Requires the "highs" or "clarabel" feature #[cfg(any(feature = "highs", feature = "clarabel"))] fn main() -> Result<(), Box> { let mut vars = variables!(); let n_chairs = vars.add(variable().min(0.).name("chairs")); let n_tables = vars.add(variable().min(0.).name("tables")); let profit = 70 * n_chairs + 50 * n_tables; let mut model = vars.maximise(profit.clone()).using(default_solver); // add_constraint returns a ConstraintReference we can query later let c_wood = model.add_constraint(constraint!(4 * n_chairs + 3 * n_tables <= 240.)); let c_labor = model.add_constraint(constraint!(2 * n_chairs + n_tables <= 100.)); let mut solution = model.solve()?; println!("Profit = {}", solution.eval(&profit)); // 4100 println!("Chairs = {}", solution.value(n_chairs)); // 30 println!("Tables = {}", solution.value(n_tables)); // 40 let dual = solution.compute_dual(); // Each extra unit of wood capacity is worth $15 of profit assert_float_eq!(dual.dual(c_wood), 15., abs <= 1e-1); // Each extra unit of labor capacity is worth $5 of profit assert_float_eq!(dual.dual(c_labor), 5., abs <= 1e-1); println!("Shadow price (wood) = {}", dual.dual(c_wood)); // 15 println!("Shadow price (labor) = {}", dual.dual(c_labor)); // 5 Ok(()) } ``` ``` -------------------------------- ### `Expression` arithmetic and `eval_with` Source: https://context7.com/rust-or/good_lp/llms.txt Demonstrates how to compose and evaluate linear expressions using arithmetic operators and the `eval_with` method against a `Solution` or `HashMap`. ```APIDOC ## `Expression` arithmetic and `eval_with` — Build and evaluate linear expressions `Expression` supports all standard arithmetic operators (`+`, `-`, `*`, `/`) with `Variable`, `f64`, and `i32`. The `.eval_with()` method evaluates an expression against any `Solution` (including `HashMap`). ```rust use std::collections::HashMap; use good_lp::{variables, variable, Expression, Solution, default_solver, SolverModel}; fn main() -> Result<(), Box> { let mut vars = variables!(); let a = vars.add(variable().max(3.).name("a")); let b = vars.add(variable().max(5.).name("b")); // Compose expressions let cost: Expression = 2. * a + 3. * b - 1.; let bonus: Expression = a / 2. + b; let total = cost.clone() + bonus.clone(); // Display expression using variable names println!("cost = {}", vars.display(&cost)); // "2 a + 3 b + -1" println!("total = {}", vars.display(&total)); // "2.5 a + 4 b + -1" // Evaluate against a HashMap (no solver needed) let manual: HashMap<_, _> = vec![(a, 3_i32), (b, 5)].into_iter().collect(); assert_eq!(cost.eval_with(&manual), 20.); // 2*3 + 3*5 - 1 // Evaluate against a real solution let solution = vars.maximise(total.clone()).using(default_solver).solve()?; println!("total at optimum = {}", solution.eval(&total)); // 2.5*3 + 4*5 - 1 = 26.5 Ok(()) } ``` ``` -------------------------------- ### Configure GoodLP with a Specific Solver Feature Source: https://github.com/rust-or/good_lp/blob/main/README.md To use a solver other than the default CBC, specify the desired solver's feature in your Cargo.toml. Ensure to disable default features if you are not using the default CBC solver. ```toml good_lp = { version = "*", features = ["your solver feature name"], default-features = false } ``` -------------------------------- ### Retrieve Dual Values (Shadow Prices) with good_lp Source: https://context7.com/rust-or/good_lp/llms.txt Retrieve dual values (shadow prices) for constraints after solving, provided the solver supports it (e.g., HiGHS, Clarabel). Dual values indicate the change in the objective function per unit increase in constraint capacity. ```rust use good_lp::{ variables, variable, default_solver, SolverModel, Solution, constraint, solvers::{SolutionWithDual, DualValues}, }; use float_eq::assert_float_eq; // Requires the "highs" or "clarabel" feature #[cfg(any(feature = "highs", feature = "clarabel"))] fn main() -> Result<(), Box> { let mut vars = variables!(); let n_chairs = vars.add(variable().min(0.).name("chairs")); let n_tables = vars.add(variable().min(0.).name("tables")); let profit = 70 * n_chairs + 50 * n_tables; let mut model = vars.maximise(profit.clone()).using(default_solver); // add_constraint returns a ConstraintReference we can query later let c_wood = model.add_constraint(constraint!(4 * n_chairs + 3 * n_tables <= 240.)); let c_labor = model.add_constraint(constraint!(2 * n_chairs + n_tables <= 100.)); let mut solution = model.solve()?; println!("Profit = {}", solution.eval(&profit)); // 4100 println!("Chairs = {}", solution.value(n_chairs)); // 30 println!("Tables = {}", solution.value(n_tables)); // 40 let dual = solution.compute_dual(); // Each extra unit of wood capacity is worth $15 of profit assert_float_eq!(dual.dual(c_wood), 15., abs <= 1e-1); // Each extra unit of labor capacity is worth $5 of profit assert_float_eq!(dual.dual(c_labor), 5., abs <= 1e-1); println!("Shadow price (wood) = {}", dual.dual(c_wood)); // 15 println!("Shadow price (labor) = {}", dual.dual(c_labor)); // 5 Ok(()) } ``` -------------------------------- ### `variable()` / `VariableDefinition` Source: https://context7.com/rust-or/good_lp/llms.txt The `variable()` function returns a default unbounded continuous `VariableDefinition`. Builder methods like `.min()`, `.max()`, `.bounds()`, `.clamp()`, `.integer()`, `.binary()`, `.name()`, and `.initial()` can be used to configure the variable before it is added to a `ProblemVariables` instance. ```APIDOC ## `variable()` / `VariableDefinition` — Build variable definitions `variable()` returns a default unbounded continuous `VariableDefinition`. The builder methods `.min()`, `.max()`, `.bounds()`, `.clamp()`, `.integer()`, `.binary()`, `.name()`, and `.initial()` configure the variable before it is added to a `ProblemVariables`. ```rust use good_lp::{variable, variables, default_solver, SolverModel, Solution}; fn main() -> Result<(), Box> { let mut prob = variables!(); // Continuous variable with name (useful for debugging) let temperature = prob.add(variable().min(-273.15).max(1000.).name("temperature")); // Integer variable in [0, 100] let count = prob.add(variable().integer().min(0).max(100).name("count")); // Binary selection variable with an initial hint for the solver let active = prob.add(variable().binary().initial(1).name("active")); // Using range syntax for bounds let pressure = prob.add(variable().bounds(0.0_f64..=200.0).name("pressure")); let solution = prob .maximise(temperature + count + 50. * active + pressure) .using(default_solver) .solve()?; println!("temperature = {}", solution.value(temperature)); // 1000 println!("count = {}", solution.value(count)); // 100 println!("active = {}", solution.value(active)); // 1 println!("pressure = {}", solution.value(pressure)); // 200 Ok(()) } ``` ``` -------------------------------- ### Set MIP Optimality Tolerance with `WithMipGap` Source: https://context7.com/rust-or/good_lp/llms.txt Use `with_mip_gap` to allow solvers to return near-optimal solutions faster by setting a relative MIP gap. Supported by HiGHS, CBC, and SCIP. ```rust use good_lp::{ variable, variables, ProblemVariables, Expression, default_solver, SolverModel, Solution, solvers::{SolutionStatus, WithMipGap}, }; #[cfg(any(feature = "highs", feature = "coin_cbc", feature = "scip"))] fn main() -> Result<(), Box> { let mut prob = ProblemVariables::new(); // 10-item binary knapsack with budget = 15 let weights = [3., 5., 2., 7., 4., 1., 6., 3., 5., 2.]; let values = [9., 7., 5., 8., 6., 3., 10., 4., 8., 5.]; let budget = 15.; let vars: Vec<_> = prob.add_all(weights.iter().map(|_| variable().binary())); let mut obj = Expression::with_capacity(vars.len()); let mut cost = Expression::with_capacity(vars.len()); for (&v, (&val, &wt)) in vars.iter().zip(values.iter().zip(weights.iter())) { obj.add_mul(val, v); cost.add_mul(wt, v); } // Allow up to 20% suboptimality for a faster solve let solution = prob .maximise(obj.clone()) .using(default_solver) .with_mip_gap(0.20)? .with(cost.leq(budget)) .solve()?; // Status will be GapLimit rather than Optimal println!("Status = {:?}", solution.status()); // GapLimit println!("Value = {:.1}", obj.eval_with(&solution)); Ok(()) } ``` -------------------------------- ### Implement SOS1 Constraints with `ModelWithSOS1` Source: https://context7.com/rust-or/good_lp/llms.txt Use `with_sos1` to enforce that at most one variable from a set can be non-zero. Supported by CBC and lpsolve solvers. ```rust use good_lp::{variables, default_solver, SolverModel, Solution, ModelWithSOS1}; // Requires "coin_cbc" or "lpsolve" feature #[cfg(any(feature = "coin_cbc", feature = "lpsolve"))] fn main() -> Result<(), Box> { variables! { problem: 0 <= x <= 2; 0 <= y <= 3; } // Maximise x + y but only one of them can be non-zero (SOS1) let solution = problem .maximise(x + y) .using(default_solver) .with_sos1(x + y) // at most one of x, y may be non-zero .solve()?; // The solver picks y=3 (larger upper bound) and x=0 assert_eq!(solution.value(x), 0.); assert_eq!(solution.value(y), 3.); println!("x={}, y={}", solution.value(x), solution.value(y)); // x=0, y=3 Ok(()) } ``` -------------------------------- ### Access Solution Values as f64 Source: https://github.com/rust-or/good_lp/blob/main/README.md Solution values are always returned as f64, even for variables defined as integers. Attempting to use i32 directly with solution values will result in a compilation error. ```rust // Correct use of f64 for solution values println!("a={{}} b={{}}", solution.value(a), solution.value(b)); println!("a + b = {{}}", solution.eval(a + b)); // Incorrect use of i32 in combination with solution value (Will fail!) println!("a + 1 = {{}}", solution.value(a) + 1); // This will cause a compilation error! ``` -------------------------------- ### WithTimeLimit::with_time_limit Source: https://context7.com/rust-or/good_lp/llms.txt Limits the solver's wall-clock time, causing it to return the best feasible solution found so far when the time expires. The solution status will be `SolutionStatus::TimeLimit`. ```APIDOC ## `WithTimeLimit::with_time_limit` — Limit solver wall-clock time Time limits cause the solver to return the best feasible solution found so far when time expires. The solution status will be `SolutionStatus::TimeLimit`. ```rust use good_lp::{ variable, variables, ProblemVariables, Expression, default_solver, SolverModel, Solution, solvers::{SolutionStatus, WithTimeLimit}, }; #[cfg(any(feature = "highs", feature = "coin_cbc", feature = "scip"))] fn main() -> Result<(), Box> { let mut prob = ProblemVariables::new(); // Large binary problem: 50 items let vars: Vec<_> = prob.add_all((0..50).map(|_| variable().binary())); let obj: Expression = vars.iter().enumerate() .map(|(i, &v)| (i as f64 + 1.) * v) .sum(); let cost: Expression = vars.iter().map(|&v| v * 3.).sum(); let solution = prob .maximise(obj.clone()) .using(default_solver) .with_time_limit(1.0) // 1 second limit .with(cost.leq(50.)) .solve()?; println!("Status = {:?}", solution.status()); // Optimal or TimeLimit println!("Obj = {:.1}", obj.eval_with(&solution)); Ok(()) } ``` ``` -------------------------------- ### WithMipGap::with_mip_gap Source: https://context7.com/rust-or/good_lp/llms.txt Sets a relative MIP optimality tolerance, allowing the solver to return a near-optimal solution more quickly by trading solution quality for speed. Supported by HiGHS, CBC, and SCIP. ```APIDOC ## `WithMipGap::with_mip_gap` — Set MIP optimality tolerance Setting a relative MIP gap allows the solver to return a near-optimal solution faster, trading solution quality for speed. Supported by HiGHS, CBC, and SCIP. ```rust use good_lp::{ variable, variables, ProblemVariables, Expression, default_solver, SolverModel, Solution, solvers::{SolutionStatus, WithMipGap}, }; #[cfg(any(feature = "highs", feature = "coin_cbc", feature = "scip"))] fn main() -> Result<(), Box> { let mut prob = ProblemVariables::new(); // 10-item binary knapsack with budget = 15 let weights = [3., 5., 2., 7., 4., 1., 6., 3., 5., 2.]; let values = [9., 7., 5., 8., 6., 3., 10., 4., 8., 5.]; let budget = 15.; let vars: Vec<_> = prob.add_all(weights.iter().map(|_| variable().binary())); let mut obj = Expression::with_capacity(vars.len()); let mut cost = Expression::with_capacity(vars.len()); for (&v, (&val, &wt)) in vars.iter().zip(values.iter().zip(weights.iter())) { obj.add_mul(val, v); cost.add_mul(wt, v); } // Allow up to 20% suboptimality for a faster solve let solution = prob .maximise(obj.clone()) .using(default_solver) .with_mip_gap(0.20)? .with(cost.leq(budget)) .solve()?; // Status will be GapLimit rather than Optimal println!("Status = {:?}", solution.status()); // GapLimit println!("Value = {:.1}", obj.eval_with(&solution)); Ok(()) } ``` ``` -------------------------------- ### Limit Solver Time with `WithTimeLimit` Source: https://context7.com/rust-or/good_lp/llms.txt Use `with_time_limit` to stop the solver after a specified duration, returning the best feasible solution found. The status will be `SolutionStatus::TimeLimit`. Supported by HiGHS, CBC, and SCIP. ```rust use good_lp::{ variable, variables, ProblemVariables, Expression, default_solver, SolverModel, Solution, solvers::{SolutionStatus, WithTimeLimit}, }; #[cfg(any(feature = "highs", feature = "coin_cbc", feature = "scip"))] fn main() -> Result<(), Box> { let mut prob = ProblemVariables::new(); // Large binary problem: 50 items let vars: Vec<_> = prob.add_all((0..50).map(|_| variable().binary())); let obj: Expression = vars.iter().enumerate() .map(|(i, &v)| (i as f64 + 1.) * v) .sum(); let cost: Expression = vars.iter().map(|&v| v * 3.).sum(); let solution = prob .maximise(obj.clone()) .using(default_solver) .with_time_limit(1.0) // 1 second limit .with(cost.leq(50.)) .solve()?; println!("Status = {:?}", solution.status()); // Optimal or TimeLimit println!("Obj = {:.1}", obj.eval_with(&solution)); Ok(()) } ``` -------------------------------- ### Define Variables with f64 and i32 Ranges Source: https://github.com/rust-or/good_lp/blob/main/README.md Use the `variables!` macro to define variables with continuous (f64) or integer (i32) feasible ranges. Constraints can be expressed using f64 or i32. ```rust // Correct use of f64 and i32 to specify feasible ranges for Variables variables! { problem: a <= 10.0; 2 <= b (integer) <= 4; // Variables can be restricted using qualifiers like (integer) }; let model = problem .maximise(b) .using(default_solver) .with(constraint!(a + 2 <= b)) .with(constraint!(1 + a >= 4.0 - b)); ``` -------------------------------- ### Build variable definitions with `variable()` builder Source: https://context7.com/rust-or/good_lp/llms.txt Use the `variable()` function and its builder methods to create `VariableDefinition`s for continuous, integer, or binary variables. Configure bounds, names, and initial solver hints before adding them to a `ProblemVariables` instance. ```rust use good_lp::{variable, variables, default_solver, SolverModel, Solution}; fn main() -> Result<(), Box> { let mut prob = variables!(); // Continuous variable with name (useful for debugging) let temperature = prob.add(variable().min(-273.15).max(1000.).name("temperature")); // Integer variable in [0, 100] let count = prob.add(variable().integer().min(0).max(100).name("count")); // Binary selection variable with an initial hint for the solver let active = prob.add(variable().binary().initial(1).name("active")); // Using range syntax for bounds let pressure = prob.add(variable().bounds(0.0_f64..=200.0).name("pressure")); let solution = prob .maximise(temperature + count + 50. * active + pressure) .using(default_solver) .solve()?; println!("temperature = {}", solution.value(temperature)); // 1000 println!("count = {}", solution.value(count)); // 100 println!("active = {}", solution.value(active)); // 1 println!("pressure = {}", solution.value(pressure)); // 200 Ok(()) } ``` -------------------------------- ### constraint! macro Source: https://context7.com/rust-or/good_lp/llms.txt Express linear constraints using a readable, infix style with standard comparison operators. This macro expands to calls to `leq`, `geq`, or `eq`. ```APIDOC ## `constraint!` macro — Express linear constraints The `constraint!` macro allows writing LP constraints using familiar `<=`, `>=`, and `==` operators in a readable, infix style. It expands to calls to `leq`, `geq`, or `eq` in the `good_lp::constraint` module. ```rust use good_lp::{variables, variable, default_solver, SolverModel, Solution, constraint}; use float_eq::assert_float_eq; fn main() -> Result<(), Box> { variables! { vars: 0 <= a <= 10; 0 <= b <= 10; 0 <= c <= 10; } let solution = vars .maximise(a + b + c) .using(default_solver) // Inequality constraint .with(constraint!(a + b <= 15)) // Equality constraint .with(constraint!(b == c)) // Compound expression on both sides .with(constraint!(2 * a + b >= c + 3)) .solve()?; assert_float_eq!(solution.value(b), solution.value(c), abs <= 1e-8); println!("a={}, b={}, c={}", solution.value(a), solution.value(b), solution.value(c)); // a=10, b=5, c=5 (total=20) Ok(()) } ``` ``` -------------------------------- ### Evaluate Linear Expressions with good_lp Source: https://context7.com/rust-or/good_lp/llms.txt Compose and evaluate linear expressions using `Expression` and `eval_with`. Supports arithmetic operators with variables and numeric types. Evaluation can be done against a `HashMap` or a `Solution` object. ```rust use std::collections::HashMap; use good_lp::{variables, variable, Expression, Solution, default_solver, SolverModel}; fn main() -> Result<(), Box> { let mut vars = variables!(); let a = vars.add(variable().max(3.).name("a")); let b = vars.add(variable().max(5.).name("b")); // Compose expressions let cost: Expression = 2. * a + 3. * b - 1.; let bonus: Expression = a / 2. + b; let total = cost.clone() + bonus.clone(); // Display expression using variable names println!("cost = {}", vars.display(&cost)); // "2 a + 3 b + -1" println!("total = {}", vars.display(&total)); // "2.5 a + 4 b + -1" // Evaluate against a HashMap (no solver needed) let manual: HashMap<_, _> = vec![(a, 3_i32), (b, 5)].into_iter().collect(); assert_eq!(cost.eval_with(&manual), 20.); // 2*3 + 3*5 - 1 // Evaluate against a real solution let solution = vars.maximise(total.clone()).using(default_solver).solve()?; println!("total at optimum = {}", solution.eval(&total)); // 2.5*3 + 4*5 - 1 = 26.5 Ok(()) } ``` -------------------------------- ### `variables!` macro Source: https://context7.com/rust-or/good_lp/llms.txt The `variables!` macro is used to declare a `ProblemVariables` instance and simultaneously bind named Rust variables for each LP variable. It supports continuous, integer, and binary variables, as well as scalar and vector definitions with various bound syntaxes. ```APIDOC ## `variables!` macro — Declare problem variables The `variables!` macro creates a `ProblemVariables` instance and simultaneously binds named Rust variables for each LP variable. It supports continuous, integer, and binary variables, scalar and vector definitions, and both prefix and postfix bound syntax. ```rust use good_lp::{variables, variable, default_solver, SolverModel, Solution, constraint}; fn main() -> Result<(), Box> { // Declare a problem called `vars` with three variables: // x: continuous, 0 \u2264 x \u2264 10 // y: integer, 2 \u2264 y \u2264 8 // z: binary (0 or 1) variables! { vars: 0 <= x <= 10; 2 <= y (integer) <= 8; z (binary); } // Vector of 5 non-negative continuous variables let w: Vec<_> = vars.add_vector(variable().min(0.), 5); let solution = vars .maximise(x + 2 * y + 10 * z + w[0]) .using(default_solver) .with(constraint!(x + y <= 12)) .solve()?; println!("x = {}", solution.value(x)); // 10 println!("y = {}", solution.value(y)); // 2 println!("z = {}", solution.value(z)); // 1 Ok(()) } ``` ``` -------------------------------- ### ResolutionError Source: https://context7.com/rust-or/good_lp/llms.txt Handles infeasible and unbounded problems. The `solve()` method returns a `Result`, so you should always match on the error to distinguish between infeasible and unbounded problems. ```APIDOC ## `ResolutionError` — Handle infeasible and unbounded problems `solve()` returns `Result`. Always match on the error to distinguish infeasible problems from unbounded ones. ```rust use good_lp::ResolutionError; // ... inside a model context ... match result { Ok(sol) => println!("Solution: {}", sol.value(x)), Err(ResolutionError::Infeasible) => println!("No solution exists"), Err(ResolutionError::Unbounded) => println!("Objective is unbounded"), Err(e) => println!("Solver error: {e}"), } ``` ``` -------------------------------- ### Declare problem variables with `variables!` macro Source: https://context7.com/rust-or/good_lp/llms.txt Use the `variables!` macro to declare continuous, integer, and binary variables with optional bounds. It supports scalar and vector definitions. This macro also binds named Rust variables for each LP variable. ```rust use good_lp::{variables, variable, default_solver, SolverModel, Solution, constraint}; fn main() -> Result<(), Box> { // Declare a problem called `vars` with three variables: // x: continuous, 0 ≤ x ≤ 10 // y: integer, 2 ≤ y ≤ 8 // z: binary (0 or 1) variables! { vars: 0 <= x <= 10; 2 <= y (integer) <= 8; z (binary); } // Vector of 5 non-negative continuous variables let w: Vec<_> = vars.add_vector(variable().min(0.), 5); let solution = vars .maximise(x + 2 * y + 10 * z + w[0]) .using(default_solver) .with(constraint!(x + y <= 12)) .solve()?; println!("x = {}", solution.value(x)); // 10 println!("y = {}", solution.value(y)); // 2 println!("z = {}", solution.value(z)); // 1 Ok(()) } ``` -------------------------------- ### ProblemVariables::add / add_all / add_vector Source: https://context7.com/rust-or/good_lp/llms.txt Dynamically add variables to a problem when the number of variables is not known at compile time. These methods allow for the programmatic construction of variable collections. ```APIDOC ## `ProblemVariables::add` / `add_all` / `add_vector` — Add variables programmatically When the number of variables is only known at runtime, use the `ProblemVariables` methods directly to build variable collections dynamically. ```rust use good_lp::{variable, variables, Expression, default_solver, SolverModel, Solution, constraint}; fn main() -> Result<(), Box> { let items: Vec<(f64, f64)> = vec![ (10., 5.), // (value, weight) (6., 3.), (12., 7.), (4., 2.), ]; let capacity = 10.; let mut prob = variables!(); // Dynamically add one binary variable per item let selected: Vec<_> = prob.add_all(items.iter().map(|_| variable().binary())); // Build objective and constraint expressions programmatically let mut objective = Expression::with_capacity(items.len()); let mut weight_sum = Expression::with_capacity(items.len()); for ((&(value, weight), &var)) in items.iter().zip(selected.iter()) { objective.add_mul(value, var); weight_sum.add_mul(weight, var); } let solution = prob .maximise(objective.clone()) .using(default_solver) .with(weight_sum.leq(capacity)) .solve()?; for (i, &var) in selected.iter().enumerate() { if solution.value(var) > 0.5 { println!("Item {} selected (value={}, weight={})", i, items[i].0, items[i].1); } } // Total value: 22 (items 0 and 2) println!("Total value = {}", objective.eval_with(&solution)); Ok(()) } ``` ``` -------------------------------- ### ModelWithSOS1::with_sos1 Source: https://context7.com/rust-or/good_lp/llms.txt Enforces Special Ordered Set type 1 constraints, ensuring that at most one variable from a specified set can be non-zero. This feature is supported by CBC and lpsolve solvers. ```APIDOC ## `ModelWithSOS1::with_sos1` — Special Ordered Set type 1 constraints SOS1 constraints enforce that at most one variable from a set is non-zero. Supported by CBC and lpsolve. ```rust use good_lp::{variables, default_solver, SolverModel, Solution, ModelWithSOS1}; // Requires "coin_cbc" or "lpsolve" feature #[cfg(any(feature = "coin_cbc", feature = "lpsolve"))] fn main() -> Result<(), Box> { variables! { problem: 0 <= x <= 2; 0 <= y <= 3; } // Maximise x + y but only one of them can be non-zero (SOS1) let solution = problem .maximise(x + y) .using(default_solver) .with_sos1(x + y) // at most one of x, y may be non-zero .solve()?; // The solver picks y=3 (larger upper bound) and x=0 assert_eq!(solution.value(x), 0.); assert_eq!(solution.value(y), 3.); println!("x={}, y={}", solution.value(x), solution.value(y)); // x=0, y=3 Ok(()) } ``` ```