Optimization
Plans with OR-Tools: decisions for the rows of a table, an objective, constraints with parameters, the solution in words with shadow prices and ranging, scenarios, and vehicle routing.
Optimize
public static class Optimize
Operations research in a step: decisions made for the rows of a table, an objective and the limits that hold them back, written the way a decision maker reads them, then solved with OR-Tools.
var plan = Optimize.Maximize("Margin"); var liters = plan.Decide("Liters", rows, min: 0); // one decision per row (product × plant) plan.Objective(liters.Sum("Margin per liter")); foreach (var (plant, x) in liters.By("Plant")) plan.Constrain("Bottling hours", x.Sum("Hours per liter") <= hours[plant], per: plant); var s = plan.Solve(); // the plan, and what limits it
Methods
static OptimizationModel Maximize(string what)- A model that makes something as large as it can (a margin, a service level).
static OptimizationModel Minimize(string what)- A model that makes something as small as it can (a cost, a distance, a shortage).
OptimizationModel
public sealed class OptimizationModel
An operations research model: decisions, what to make as large or as small as possible, and constraints. It describes itself in words, saves as formulation.json (the engine solves it again for a what-if), and Solve() finds the best plan with OR-Tools.
Properties
string What { get; }- What it makes as large (or small) as it can.
Also: bool Maximizes { get; }, IReadOnlyList<Decision> Variables { get; }, IReadOnlyList<Constraint> Constraints { get; }, IReadOnlyList<Parameter> Parameters { get; }.
Methods
Decisions Decide(string name, Table rows, double min = 0, double max = double.PositiveInfinity, bool integer = false, string? maxColumn = null, string? describe = null)One decision per row of a table: Liters for every product and plant.
name: What is decided ("Liters", "Open", "Price").rows: The rows to decide for (an OAX or SQL step's result).min: Its least value (0: nothing below).max: Its largest value (∞ by default).integer: Whole numbers only (units, crates; with min 0 and max 1, a yes/no).maxColumn: A column holding each row's own maximum (a capacity, a stock).describe: What it means, in a sentence (the Model tab shows it).
Decision Decide(string name, double min = 0, double max = double.PositiveInfinity, bool integer = false)- A single decision (a budget split, a price).
Decisions Choose(string name, Table rows, string? describe = null)- A yes/no per row (open a site, run a line): 0 or 1.
Parameter Parameter(string name, double value, string? describe = null)- A value a scenario or a report changes: a capacity, a demand, a budget (in constraints' right-hand sides).
void Objective(LinExpr e, string? describe = null)- What to make as large (or small) as possible: Σ margin × liters.
Constraint Constrain(string name, LinConstraint c, string? per = null, string? describe = null)- A constraint: Σ hours × liters ≤ the plant's hours. A limit that holds once per something (per plant) is the same name with per: the plant.
string Describe()- The model in words: what it decides, what it makes as large (or small) as it can, and subject to what.
JsonObject ToJson()- The model written as data (formulation.json): each decision with its bounds, the objective's coefficients, each constraint's coefficients and bounds, and the parameters on the constraints' right-hand side, so the engine (HiGHS) can solve it again for other parameter values.
Solution Solve(string solver = "auto", double seconds = 60, double gap = 0.0001)Solves the model with OR-Tools: the best plan, what limits it (the shadow price of every constraint), or why there is none (the constraints that cannot all hold; what grows without end).
solver: auto (GLOP for quantities, SCIP with whole numbers), GLOP, SCIP, HIGHS, CBC or CP-SAT.seconds: At most this long; with whole numbers, the best plan found by then.gap: With whole numbers: stop when proven this close to the best (0.0001 = 0.01 %).
Solution SolveRelaxation(double seconds = 60)- The LP relaxation: the same model with every whole-number decision free to take any value within its bounds (a yes/no between 0 and 1), solved with GLOP. Its objective bounds what the whole-number plan can reach, and its shadow prices say what one more unit of each limit is worth to the relaxed plan - which the whole-number plan's own shadow prices (the LP with its whole numbers fixed as chosen) cannot say for a model of yes/no decisions only. Its decisions keep their fractions (Value is not rounded).
Also: string ToString().
Decision
public sealed class Decision : LinExpr
One decision: a number the solver chooses (a quantity, a yes/no), within its bounds.
Properties
Decisions? Set { get; }- The decisions it is one of (one per row of a table), or null.
int Row { get; }- Its row in the set's table.
Also: string Name { get; }, double Min { get; }, double Max { get; }, bool Integer { get; }.
Methods
Its methods: string ToString().
Decisions
public sealed class Decisions : IEnumerable<Decision>
The decisions for the rows of a table, one each: Liters for every product and plant. Sums over them, with coefficients from the rows, make the objective and the constraints.
Properties
Table Rows { get; }- The table whose rows the decisions are for.
Also: string Name { get; }, int Count { get; }.
Indexers
Its indexers: Decision this[int i] { get; }.
Methods
LinExpr Sum()- Σ decisions.
LinExpr Sum(string column)- Σ the column's value × decision, row by row (a missing value counts 0).
LinExpr Sum(Func<Row, double> coefficient)- Σ coefficient(row) × decision.
Decisions Where(Func<Row, bool> keep)- The decisions of the rows that pass.
IEnumerable<(string Key, Decisions Group)> By(string column)- The decisions per value of a column: per plant, per product, in the order the values come.
Decisions ZeroOrAtLeast(double minimum)- Each decision either 0 or at least a minimum (a production run, an order): an on/off choice per row, held by the decision's maximum.
LinExpr SumAtWorst(string column, string deviation, double budget, bool high = false)Σ column × decision at its worst, when each row's coefficient may be off by its deviation and at most budget rows are off at once (Bertsimas and Sim's budget of uncertainty; budget = the rows' count is box uncertainty, every row at its low end; 0 is the nominal sum). For what must be large (a maximized objective, a ≥ constraint) it is the lowest the sum can be; with high: true, for what must be small (a minimized cost, a ≤ constraint), the highest. Uplift with an interval: x.SumAtWorst("Uplift", "Uplift deviation", budget: 20). The decisions must be 0 or more.
column: Each row's nominal coefficient (an estimated uplift, a cost).deviation: How far each row's coefficient may be from it (a standard deviation, half an interval's width).budget: How many rows may be off at once (a fraction allowed): the more, the safer and the lower the plan's nominal value.high: The worst is the highest (costs), not the lowest (gains).
LinExpr SumAtWorst(Func<Row, double> nominal, Func<Row, double> deviation, double budget, bool high = false)- Σ nominal(row) × decision at its worst, each row's coefficient off by up to deviation(row), at most budget rows at once.
Also: IEnumerator<Decision> GetEnumerator(), string ToString().
LinExpr
public class LinExpr
A linear expression of decisions: Σ coefficient × decision, plus a constant (which may hold parameters). Comparing two (≤, ≥, ==) makes a constraint.
Methods
static LinExpr Sum(IEnumerable<LinExpr> items)- The sum of expressions.
Also: string ToString().
Operators
Its operators: static implicit operator LinExpr(double v), static LinExpr operator +(LinExpr a, LinExpr b), static LinExpr operator -(LinExpr a, LinExpr b), static LinExpr operator -(LinExpr a), static LinExpr operator *(LinExpr a, double k), static LinExpr operator *(double k, LinExpr a), static LinExpr operator /(LinExpr a, double k), static LinConstraint operator <=(LinExpr a, LinExpr b), static LinConstraint operator >=(LinExpr a, LinExpr b), static LinConstraint operator ==(LinExpr a, LinExpr b), static LinConstraint operator !=(LinExpr a, LinExpr b).
LinConstraint
public sealed class LinConstraint
expression ≤ / ≥ / == expression, as lo ≤ Σ a·x + constant ≤ hi.
Constraint
public sealed class Constraint
A constraint of the model: its name, its group (the limit it is one of) and what it says.
Properties
string Group { get; }- The limit it is one of ("Bottling hours").
string? Per { get; }- What it is for ("Bergen"), when the limit holds once per something.
Also: string? Describe { get; }, string Name { get; }.
Methods
Its methods: string ToString().
Parameter
public sealed class Parameter : LinExpr
A value the model reads that a scenario or a report can change: a capacity, a demand, a budget.
Properties
Its properties: string Name { get; }, double Value { get; set; }, string? Describe { get; }.
Methods
Its methods: string ToString().
Solution
public sealed class Solution
What a solve found: the status in words, the best value of the objective, the plan (a value for every decision, as a table of the decisions' rows), and what limits it: each constraint's use, slack and shadow price (how much one more of it would add).
Properties
Table ReducedCosts { get; }- Every decision with its value, bounds and reduced cost, and whether it sits at a bound: the decisions left at 0 with the largest reduced costs are the furthest from paying.
string Status { get; }- optimal, feasible (a plan, not proven best), infeasible, unbounded, stopped (no plan in time) or failed.
string Text { get; }- The status in words, with why when there is no plan.
double Objective { get; }- The objective's value.
double Bound { get; }- The best the objective could be (with whole numbers; the objective itself otherwise).
double Gap { get; }- How far the plan may be from the best (0 when proven best).
bool Relaxed { get; }- The LP relaxation's (OptimizationModel.SolveRelaxation): whole-number decisions keep their fractions.
IReadOnlyDictionary<string, double> Parameters { get; }- The parameters' values it was solved with.
IReadOnlyList<string> Conflicts { get; }- When infeasible: the constraints that cannot all hold, and by how much.
IReadOnlyList<string> Unbounded { get; }- When unbounded: the decisions that would grow without end.
Ranging Ranging { get; }- The solver's ranging (HiGHS), an LP's: how far each limit's bound may move with its shadow price unchanged, the objective at either end, and how far each decision's coefficient may move with the plan unchanged. Results.Save keeps it with the plan (sensitivity.json); Why says why there is none (whole numbers, a degenerate plan).
Table Plan { get; }- The plan: the rows of the first decisions with their values, and the values of every other set of decisions on the same rows; single decisions as rows of their own when there are no sets.
Table Limits { get; }- Every constraint: how much of it the plan uses, its limit, the slack, its shadow price (what one more of its limit changes the objective by) and whether it binds.
Table Robustness { get; }- Each robust sum (Decisions.SumAtWorst) in the plan: its value as estimated (nominal), at the worst the plan was protected against, and the worst found again from the plan (the budget's largest deviations): the price of robustness is the nominal less the protected.
Also: OptimizationModel Model { get; }, bool HasPlan { get; }, string Solver { get; }, double Seconds { get; }.
Methods
double ReducedCost(Decision d)- A decision's reduced cost: how much the objective changes per unit forced into it from where the plan has it (a product not made: how much its margin would have to rise before making it pays). An LP's; with whole numbers, the LP with them fixed as the plan chose (its quantities' reduced costs).
Table Sweep(string parameter, IEnumerable<double> values)- Parametric analysis: the model solved again at each value of a parameter, and for each the objective, its change from this plan, the parameter's shadow price there (what one more is worth) and the constraints at their limit. Where the shadow price changes, the plan's structure does: the range its shadow price holds over is between those values.
double Value(Decision d)- A decision's value (whole numbers rounded, but in the LP relaxation).
double Value(LinExpr e)- An expression's value: the objective's parts, a constraint's use.
Table Of(Decisions d)- The rows of a set of decisions with their values (a column named as the decisions).
static IReadOnlyList<string> Explain(JsonObject solved, int top = 5)- What limits a plan the engine solved again (its /api/optimize answer: status, what, maximize, objective, limits), in the words of
Solution.Explain. IReadOnlyList<string> Explain(int top = 5)- What limits the plan, in words: the binding constraints, the most valuable first, then the closest to binding.
Solution With(params (string Parameter, double Value)[] values)- The same model solved again with other parameter values (a scenario): ("Bergen hours", 1760).
ScenarioSet Scenarios(params Scenario[] scenarios)- Scenarios against this plan, each solved again with its parameter values and compared with it: solution.Scenarios(new Scenario("Bergen +10 %", ("Bergen hours", 1760)), new Scenario("Less Pils", ("Pils demand", 120000))).
JsonObject ToDisplay()- What the studio shows: the status, the objective, what limits the plan, the plan's first rows, the model in words.
Also: string ToString().
Ranging
public sealed class Ranging
How far a linear program's plan holds as its numbers move: the solver's ranging (HiGHS, the solver the engine solves plans again with). For each limit at its bound, the range of that bound in which its shadow price holds, and the objective at either end of it; for each decision, the range of its objective coefficient (a margin, a cost) in which the plan stays the same. An LP's only: with whole-number decisions there is none (Ranging.Why says so).
Properties
string Solver { get; }- The solver that ranged it ("HiGHS 1.12.0").
string? Why { get; }- Why there is no ranging (whole numbers, no plan, HiGHS not found, another set of shadow prices); null when there is.
IReadOnlyList<LimitRange> Limits { get; }- Every limit (constraint): its bound, its shadow price, and the range of the bound in which the price holds.
IReadOnlyList<CostRange> Decisions { get; }- Every decision: its value, its objective coefficient, and the range of that coefficient in which the plan stays.
Table LimitsTable { get; }- The limits as a table: Constraint, Bound, Shadow price, Binding, From, To, Objective at from, Objective at to.
Table DecisionsTable { get; }- The decisions as a table: Decision, Value, Coefficient, From, To.
Also: bool Available { get; }.
Methods
JsonObject ToJson()- As a plan version keeps it (sensitivity.json): open ends as null.
Also: string ToString().
CostRange
public sealed record CostRange(
string Name,
double Value,
double Coefficient,
double From,
double To)
A decision's value, its objective coefficient and the range of the coefficient in which the plan stays the same.
Properties
Its properties: string Name { get; init; }, double Value { get; init; }, double Coefficient { get; init; }, double From { get; init; }, double To { get; init; }.
LimitRange
public sealed record LimitRange(
string Name,
double Bound,
double Price,
bool Binding,
double From,
double To,
double ObjectiveAtFrom,
double ObjectiveAtTo)
A limit's bound and the range of it in which its shadow price holds (an open end: ±∞), the objective at each end.
Properties
Its properties: string Name { get; init; }, double Bound { get; init; }, double Price { get; init; }, bool Binding { get; init; }, double From { get; init; }, double To { get; init; }, double ObjectiveAtFrom { get; init; }, double ObjectiveAtTo { get; init; }.
Scenario
public sealed record Scenario(
string Name,
params (string Parameter, double Value)[] Values)
A scenario: its name and the parameter values it tries ("Bergen +10 %", ("Bergen hours", 1760)).
Properties
Its properties: string Name { get; init; }, (string Parameter, double Value)[] Values { get; init; }.
ScenarioSet
public sealed class ScenarioSet
Scenarios against a plan, written in a step: each solved again with its values, compared with the plan (the objective and how far from the plan's, what limits it, the rows that change). Results.Save(name, solution, scenarios: …) keeps them with the version; the Scenarios tab shows them.
var scenarios = solution.Scenarios( new Scenario("Bergen +10 %", ("Bergen hours", 1760)), new Scenario("Less Pils", ("Pils demand", 120000)));
Properties
Solution Base { get; }- The plan the scenarios are compared with.
Table Compare { get; }- Every scenario against the plan: its objective, the difference, its status and what it changed.
Also: IReadOnlyList<(Scenario Scenario, Solution Solution)> Items { get; }.
Indexers
Solution this[string name] { get; }- A scenario's solution, by its name.
Methods
Table Changes(string scenario)- The rows of the plan a scenario changes: their keys, and each decision before and after.
JsonObject ToDisplay()- What the studio shows: the plan and each scenario, its objective, what limits it and the rows that change.
Also: string ToString().
Routing
public static class Routing
Routes for vehicles, written in a step and solved with OR-Tools' routing: the places, the distance (or time) between each two, then the rules: how many vehicles from where, how far each may go, what each carries, when each place may be visited, what is picked up where and delivered where.
var routes = Routing.Plan(places, distances) // a tour of every place from the first (a TSP) .Vehicles(4) // or a fleet from the depot (a VRP) .Capacity(demands, [15, 15, 15, 15]) .Solve();
Distances are whole units (meters, minutes): fractions are rounded.
Methods
static RoutePlan Plan(IReadOnlyList<string> places, double[,] distance)- The places, named, and the distance from each to each (row: from, column: to).
static RoutePlan Plan(IReadOnlyList<string> places, long[,] distance)- The places, named, and the distance from each to each, in whole units.
static RoutePlan Plan(long[,] distance)static RoutePlan Plan(double[,] distance)- Places numbered 0, 1, 2 … and the distance from each to each.
static RoutePlan Plan(Table distances, string from = "From", string to = "To", string distance = "Distance")- A table of distances (an OAX or SQL step's): a row per pair, From, To and Distance. The places are the values in From and To, in the order they first come; a pair with no row cannot be travelled.
RoutePlan
public sealed class RoutePlan
A routing problem being written: the places, the distances, and the rules added to it.
Properties
Its properties: IReadOnlyList<string> Places { get; }.
Methods
RoutePlan Vehicles(int count, int depot = 0)- How many vehicles, all from and back to the depot (the first place by default).
RoutePlan Vehicles(int count, string depot)- How many vehicles, all from and back to the depot named.
RoutePlan StartsAndEnds(int[] starts, int[] ends)- Where each vehicle starts and where it ends (one each per vehicle).
RoutePlan MaxDistance(double max, double balance = 0)- How far one vehicle may go; balance (cost per unit of the longest route) evens the routes out.
RoutePlan Capacity(double[] demands, double[] capacities, string name = "Load")- What each place needs carried (its demand, in place order) and what each vehicle carries at most.
RoutePlan TimeWindows((double From, double To)[] windows, double wait = 30, double horizon = 30, double[,]? time = null)- When each place may be visited (from, to: in place order, the depot's for the vehicles' start), with the distances as times unless another matrix is given; wait: how long a vehicle may wait, horizon: how long a route may take.
RoutePlan PickupsAndDeliveries(params (int Pickup, int Delivery)[] pairs)- What is picked up where and delivered where: one vehicle does both, the pickup first.
RoutePlan PickupsAndDeliveries(params (string Pickup, string Delivery)[] pairs)- What is picked up where and delivered where, by the places' names.
RoutePlan Coordinates(double[] latitudes, double[] longitudes)- Where each place is (latitude and longitude, in place order): the studio draws the routes on a map.
RoutePlan Coordinates(Table places, string place = "Place", string latitude = "Lat", string longitude = "Lon")- Where each place is, from a table (an OAX step's): its place, latitude and longitude columns. Places not in it are not drawn; the stops (routes.Stops) then carry their latitude and longitude.
Routes Solve(double seconds = 10, string first = "auto", bool improve = false)Solves: the routes, or why there are none.
seconds: At most this long.first: How to find a first solution: auto, PathCheapestArc, ParallelCheapestInsertion, Savings, Christofides …improve: Keep improving it until the time is up (guided local search).
Routes
public sealed class Routes
What routing found: the routes, their distances, loads and times, or why there are none.
Properties
string SolverStatus { get; }- OR-Tools' own word for how the search ended.
long Objective { get; }- What the search made as small as it could: the distances, and the balance's cost when asked for.
long Distance { get; }- All the routes' distances added up.
long Longest { get; }- The longest route's distance.
long? Time { get; }- All the routes' times added up (with time windows).
long? Load { get; }- All the loads carried (with a capacity).
string Text { get; }- In words: the routes found, or why none were.
Table Stops { get; }- Every stop: Vehicle, Order, Place, its Load and Time window when the plan has them, its Lat and Lon when it has coordinates.
Table Summary { get; }- A row per vehicle: its stops, distance, load and time.
Also: RoutePlan Plan { get; }, IReadOnlyList<Route> Items { get; }, double Seconds { get; }, bool HasRoutes { get; }.
Methods
JsonObject ToDisplay()- What the studio shows: the routes in words, each vehicle's stops (the places' numbers), the places (with their coordinates when known: a map) and a table.
Also: string ToString().
Route
public sealed record Route(
int Vehicle,
IReadOnlyList<Stop> Stops,
long Distance,
long? Load,
long? Time)
One vehicle's route: its stops from start to end, its distance, its load and its time.
Properties
bool Used { get; }- Whether it goes anywhere (not only from its start to its end).
Also: int Vehicle { get; init; }, IReadOnlyList<Stop> Stops { get; init; }, long Distance { get; init; }, long? Load { get; init; }, long? Time { get; init; }.
Stop
public sealed record Stop(
string Place,
int Index,
long? Load,
long? TimeFrom,
long? TimeTo)
A stop on a route: the place, the load carried after it, when the vehicle may be there (from, to).
Properties
Its properties: string Place { get; init; }, int Index { get; init; }, long? Load { get; init; }, long? TimeFrom { get; init; }, long? TimeTo { get; init; }.