Network Optimization
Find the cheapest way to run your network: where each customer is served from, which routes carry the volume, and how much each site handles or makes.
For a slow solve, read the guide Simplify a network optimization. It explains how to settle what your business already knows, and how to investigate complexity and numerical conditioning before proposing a change to your model.
When to use this¶
- Sourcing — decide which supplier or plant serves which customer, and compare the result with how you source today
- Routing — find the cheapest routes through the network, including routes you have never used, by giving the optimizer every lane it may consider
- Warehouse footprint — see which sites carry volume once each is capped by a throughput limit, and choose which optional sites to open using fixed operating costs from Map Location Policy
- Production allocation — when your network makes products, decide how much to produce where, with bills of materials and plant capacity respected
- Profit against emissions — rerun the same network with Objective set to Minimize Emissions, or to Weighted to weigh the two, and read the trade-off off the two scenarios
- What-if under a rule — cap a lane, keep a minimum on a full-truck lane, or fix a flow with Add Constraint, and see what the rest of the network does around it
- Partial service — decide whether serving every order is worth it when supply is short, by allowing partial fulfilment and reading which demand goes unserved
What you need¶
Four tables in the scenario. Without any one of them the run is refused:
- Locations, from Map Locations — every site a lane or policy names
- Lanes, from Map Lanes — the routes the optimizer may ship on, each with its per-unit cost
- Supply policy, from Map Supply Policy — where units can be sourced, at what cost and up to what capacity
- Demand policy, from Map Demand Policy — what must be served, and what serving it earns
Handling cost, from Map Handling Policy, and inventory carrying cost, from Map Inventory Policy, are optional and change the answer when they are there. Production data, from Map Bill of Materials and Map Production Policy, adds production decisions. Site policy, from Map Location Policy, adds site decisions.
The products are the ones Map Products lists. Every product a lane,
supply, demand, handling, inventory or recipe row names must be on that list,
and a lane whose product is ALL is open to every product on it. A scenario
that names a product the list does not hold, or names products and has no
list at all, is refused with the rows named; for a scenario saved without a
list, add Map Products to the workflow that builds it and run that workflow
again. Every one of those rows names its product, even in a network with one
product: a blank product is refused, so write ALL on a lane, handling or
inventory row that applies to every product. Every lane names its transport
mode too, even in a network with one mode.
The scenario also has to state its Unit, on the Create Scenario that saved it, and every quantity in it has to state its own. A scenario saved without a unit is refused; set Unit on that Create Scenario and run its workflow again. See Units below.
Continue toward the best plan¶
Your agent chooses further computations within your agreed objective, business requirements and resource budget. Network Optimization computes and verifies; it does not choose a strategy for you.
To continue the same problem, open Network Optimization and choose Reuse a previous plan. Select a saved optimization result and its version. The node has one scenario input: keep the original scenario, upstream rules and costs connected there. The selected plan supplies starting values, not requirements or fixed decisions. Its exact version stays selected when newer results appear; the settings link opens that saved result, and the canvas identifies it.
The saved candidate is checked against the current compiled problem. A changed
problem or missing candidate data is refused with a reason. Older results that
lack candidate data need a fresh solve first. This reuses values, not the earlier
search tree or proof bound; a cost ceiling alone does not supply a solution.
A timed-out search retains the checked starting plan if it finds no better plan.
Agents use the same setting, starting_solution_id, with an exact saved result id.
The run records that reference and preserves the original result.
Read the saved optimization report for solution quality. On an unsuccessful or time-limited computation, read the run's Optimization evidence finding for the termination phase, available bound and search statistics, and any rejected candidate. A better objective and a stronger bound are separate progress. Missing bounds mean unknown quality; a timeout is not proof of infeasibility. A positive precision target is not proof of optimality, and proof for fixed depot choices is not proof that those choices are best.
If the model is infeasible, the evidence may name a conflict between linear constraints and variable bounds. Analysis runs automatically after solver-proven infeasibility, with its own Conflict analysis time limit. The run's node activity reports the current phase, elapsed time and allowance to the app and agent. If analysis times out, increase that setting and enable Analyse conflicts without optimizing to rebuild the connected model and retry the analysis without another optimization search. Keep the inputs and rules unchanged when investigating the same failure. Pre-solve validation failures explain their known cause directly. A conflict identifies incompatible requirements, not which requirement you should change or all possible conflicts. An integer-only conflict may have no linear explanation; no conflict or an unfinished analysis does not prove integer feasibility. Debug mode can explore relaxed requirements separately; its results do not meet the original question.
At a decision point, discuss whether to use the best verified plan, continue with the same problem, or explore a concrete simplification. Explain the progress and uncertainty before spending more than the agreed budget. Repeating an unsuccessful experiment needs a reason to expect progress or useful evidence.
For a simplification such as regional demand aggregation, agree what detail, requirements and guarantees change before running it. Export the original workflow and import it as a separately named workflow, then edit that copy. Keep its results and optimality claims separate. A candidate from that version is an answer to the original only after validation against the original requirements; its proof does not automatically transfer. Repeat the decision when needed, using what the latest attempt actually established.
Settings¶
| Setting | Default | What it does |
|---|---|---|
| Objective | none | What the optimizer optimizes, one of the three below. You choose it: a node without one is refused when you validate or run the workflow. |
| Profit Weight | none | A number from 0 to 1. Asked for, and required, only when the objective is Weighted (Profit + Emissions). |
| Emissions Weight | none | A number from 0 to 1. Asked for, and required, only when the objective is Weighted (Profit + Emissions). |
| Allow partial demand fulfillment | off | Off, every demand point must be fully served or the run fails. On, the optimizer serves as much demand as is profitable and reports what it left unserved. |
| Debug mode | off | Off, a run whose model has no solution fails and says so. On, the optimizer loosens the model step by step until a solution exists, returns it and names everything it misses. See When the model has no solution. |
| Solver time budget (seconds) | 7200 |
The longest the whole solve may take, preparing the model included. A solve that reaches it stops there and keeps the best plan found so far, labelled with how close it is to the best possible; when it found no plan in time, the run says so. Between 60 seconds and eight hours; asking for more than the service allows is refused before the run starts, and the run's record shows what was asked and what was granted. |
| Precision target (%) | 1.0 |
Stop when the remaining possible improvement is within this percentage of the plan’s objective value. Enter any number from 0 to 100; smaller targets can take longer, and 0 requests a closed gap. Applies to mixed-integer solves. Existing workflows without this setting use the default on their next run. |
| Reuse a previous plan | none | Select an exact saved optimization result and version to seed this search. |
| Conflict analysis time limit (seconds) | 300 |
Independent allowance after an infeasible solve, also used for a conflict-only retry. |
| Analyse conflicts without optimizing | off | Rebuild the connected model and request linear conflict evidence without searching for a plan. |
| Activation bound time limit (seconds) | 5.0 |
Maximum time for each preliminary activation-bound calculation, within the overall solver time budget. |
Activation bound time limit (seconds) limits each preliminary calculation that bounds a yes/no decision: an Add Constraint maximum on origins, targets or active locations (single sourcing is at most one origin), a lane minimum that applies only when the lane is used, an optional site, and an open-ended last step of Add Costs. These calculations share the overall solver time budget. Raise this setting if the run reports that an activation-bound calculation timed out; adding a finite physical capacity can also simplify the calculation. Unlimited circulation may prevent a finite bound even when customer demand is finite: that diagnostic asks for a lane, supply, production or throughput capacity.
Every setting but Objective runs with the default above until you change it. Objective has no default: choose it on every Network Optimization node. See Choosing the objective below.
Allow partial demand fulfillment requires Maximize Profit, or Weighted with Profit Weight above 0. Validation refuses partial demand with Minimize Emissions or a zero profit weight: those objectives do not reward serving demand. Turn partial demand off to minimize emissions while serving every order, or choose one of the objectives that rewards service.
When every supplier has a capped supply and total supply is below total demand, the run fails. Turning Allow partial demand fulfillment on softens that: the optimizer then serves what it profitably can instead of failing. Otherwise cap fewer suppliers, or raise the caps. A supplier with no cap is unlimited, so this only bites when every connected supplier is capped.
When the model has no solution¶
Sometimes the model has no solution: no result can meet every rule, capacity and demand at the same time. A lane minimum asks for more than its supplier has, a budget is below what the demand costs to serve, or a customer has no lane reaching it. With Debug mode off, the run fails with a message saying so.
Turn Debug mode on and run again to find out why. The optimizer then loosens the model one step at a time, keeping each earlier step loosened, and stops at the first step where a solution exists:
- Your rules from Add Constraint nodes
- The minimum quantities on your lanes
- Supply capacity
- Production capacity
- Lane capacity
- Demand
Demand comes last, so a capacity that is too small is named as itself. For example: Lane Leipzig→Berlin Crossdock is too small: the solution ships 150 cases, 50 cases more than its capacity of 100 cases. Demand is left unserved only where no capacity could have served it, such as a customer no lane reaches.
The run completes with that solution. Its findings say what was loosened and name each thing the solution misses, by how much, in the scenario's Unit — or in the rule's own unit, where an Add Constraint rule states one. For example, in a scenario in pallets: Lane minimum P2→DC for B is not met: the solution reaches 60 pallets, 40 pallets short of the 100 pallets required.
At that step the solution misses as little as possible in total. Every unit, location or unit of money it misses counts the same, and none of your costs weighs in on how much it misses. A site with a large fixed cost is opened if opening it misses less. Of the solutions that miss that little, you get the best one for your objective, so where the misses fall, such as which orders go short, follows from your costs.
The solution does not meet everything the model asks for. Fix what the findings name, either the rule or the data it refers to. Then turn Debug mode off and run again.
What you get¶
An optimized scenario with the flows, sourcing and production the optimizer chose. Compare it with the scenario you started from to see what changed. Results are saved with the scenario by the Create Scenario after this node.
The scenario carries a label beside its name saying how far the plan can be trusted:
- Optimal: the solver proved that no better plan exists.
- Meets your 1% target (the figure is your Precision target (%)): the solver stopped because the remaining possible improvement was within your target. This is a successful stop, not a timeout. The label's detail shows that improvement as a share and, for a profit objective, as money.
- Within 1.8% of the best possible (the figure is the solve's own): the time budget ran out first. The plan is the best found, and the label says how much better a plan could still be, as a share and, for a profit objective, as money.
- Best found in time, quality unproven: the time budget ran out before the solver could say how far the plan is from the best possible, or while that distance was still too wide to state as a useful figure.
- Solution of a loosened model: debug mode loosened the model to find this plan, so it does not meet every rule you set. What it misses is in the penalties table.
The Optimization details table opens with the same label and sentence, so the scenario and its solve report never disagree. The same label and evidence are available to your agent. Saved results retain the settings and evidence from their own solve.
A plan is only optimal when its label says so. Large models with yes/no decisions, such as single sourcing over many customers, may not reach a proven optimum within their budget. Running the same workflow again can then give a different plan of similar quality, because the solver stops wherever the time runs out.
The scenario also reports the solve itself, in the following tables:
- Optimization details, one row: the objective as asked (type, direction, weights) and the value it reached; whether that value is proven optimal, within your precision target, or the best found before the time budget ran out, with the best bound, the gap between them and the precision target it was held to; how long the solve took against its budget; and the model's size before and after presolve. Presolve time is reported where the solver measures it, which is only for models with integer decisions. The smallest and largest coefficients in the model show how well scaled it is: a wide range is a reason to investigate numerical scaling, not proof of poor conditioning.
- Objective terms, one row per part of the objective: revenue, each cost, and emissions when your lanes carry them. Each row has its value, its unit (the Currency or Emissions Unit set on Create Scenario, where one is set), its weight in the objective and its contribution. The contributions add up to the objective value, so you can check the result by hand, a weighted objective included.
- Rules, one row per rule the plan was solved under: every rule of the Add Constraint nodes in front of this one, and every minimum shipment the lanes carry. A rule held For each has one row per member, named by the member, so a lane minimum for each source lists Hamburg, Rotterdam and Metz on three rows. Each row has the bound as you wrote it, what the plan reached, the slack between them (the room a satisfied rule leaves, as a number and as a share of the bound) and a status: binding when the plan sits on the bound, so the rule is shaping the plan and loosening it would change the answer; not binding when the plan leaves room, so the rule did nothing here; not used for a minimum that applies only when its lanes are used, on lanes the plan left idle; violated when debug mode loosened the rule, with how far the plan misses it. The numbers are in the rule's own unit, the scenario's currency for a budget, or a count of origins, targets or locations. This table is there only when the solve had rules.
- Penalties: what the solution misses, one row per bound it misses, with the bound, what the solution reached and the gap. Only debug mode misses a bound, so this table is there only when debug mode loosened the model. A rule it names is the violated row of the Rules table, missed by the same amount.
When the lanes carry emissions per unit, each flow carries its transport emissions in the flows table's Emissions column, whichever objective chose the plan; the Objective terms table's emissions row adds them up, together with any production emissions. They are in the scenario's emissions unit, which the lanes carry from the Create Scenario that saved them: every lane's rate must be in that one unit, because the plan adds them up.
Units¶
The optimization is built in the scenario's Unit, and every quantity it writes — flows, supply used, demand fulfilled, production — is in that unit.
The data may state other units. A lane's capacity and cost can be per pallet while demand is in bags and supply in tonnes. Each quantity is converted for its own product, using the conversions mapped with Map Units & Conversions and the built-in metric steps, and a cost per unit is converted with it. A limit that covers several products, such as the capacity of a lane open to every product or to a product group, weighs each product by its own conversion: with a pallet of cement at 600 kg and a pallet of nails at 50 kg, a three-pallet lane carries 1,800 kg of cement, or 150 kg of nails, or a mix.
A pallet is filled fractionally. The optimization does not round to whole pallets.
Handling and inventory carrying costs state the unit they are per, like every
other cost per unit. A location-wide ALL rate in another unit than the
scenario's is converted for each product with that product's own conversion.
The optimized scenario keeps the lanes, supply, demand and production rows as you stated them, in their own units, beside the results.
Save the optimized scenario with a Create Scenario whose Unit is the one it was optimized in. Saving does not convert the results, so a different Unit is refused. To optimize in another unit, set it on the Create Scenario that feeds this node and run again.
The run is refused, naming the table, rows, product and units, when a quantity states no unit or its unit has no conversion to the scenario's unit for its product. Nothing is assumed.
Lanes¶
The lanes table is the set of routes the optimizer may ship on. Include every route you want considered, including ones your history never used: that is how the optimizer discovers new sourcing options. If you have only historical shipments and no candidate lanes, build a lanes table from your location pairs, for example with distance-based unit costs.
A lane may also carry a Capacity, the most it may carry: blank is unlimited and zero closes the lane. A lane with a Minimum Quantity carries nothing or at least that much, as a full-truck lane does. Each such minimum is a yes/no decision for the optimizer, and debug mode loosens lane minimums in its second step.
Site decisions¶
Add Map Location Policy to define sites as include (forced open),
consider (optimizer decides) or exclude (closed). Open sites pay their fixed
operating cost once per planning period, even when idle; closed sites have no
activity. The decision is shared across all products and production recipes.
An absent policy preserves availability with no modeled fixed cost. All costs
must cover the same period as demand. Opening investments and closure charges
are outside this single-period model.
Location state reports open status and incurred fixed cost for sites with a policy: each open site's Total Fixed Cost is its fixed operating cost, and a closed site's is zero. Without site policy, optimization adds no site policy or state table. The Objective terms table reports the fixed operating cost of all open sites as its own row, beside the other costs. Profit and weighted objectives and total-cost budgets include the charge. An emissions-only objective still reports costs, but does not minimize them. Cost to Serve does not allocate site fixed costs.
Handling costs¶
When the scenario carries handling costs, the optimizer folds per-unit
facility costs into the total it is minimizing. A location's rate is either one
ALL row or one row per product; both at once is refused. Handling cost can flip a routing
decision: a high rate at a hub may make direct shipment cheaper than routing
through it.
Choosing the objective¶
Pick the objective; the direction comes with it. Every objective has one direction that means anything. You do not minimize profit, and you do not maximize emissions, so there is no direction to choose.
| Objective | What the solve does |
|---|---|
| Maximize Profit | Maximizes revenue minus the network's cost. With every order mandatory, revenue is fixed, so this is the cheapest plan that serves every order |
| Minimize Emissions | Minimizes total transport and production emissions. Needs emissions on the lanes or the production policy |
| Weighted (Profit + Emissions) | Maximizes Profit Weight × profit − Emissions Weight × emissions. Needs emissions on the lanes or the production policy |
The network's cost is every cost the optimizer models: supply, transport (including the cost Add Costs puts on lanes), handling, inventory carrying, production and fixed operating cost. Profit and the profit half of a weighted objective count all six.
To find the cheapest plan, choose Maximize Profit. With Allow partial demand fulfillment off, every order is served, so the revenue is the same in every plan and the most profitable plan is the cheapest one. To see cost alone in the objective value, leave revenue out of the demand policy: profit is then minus the cost. With partial demand allowed, Maximize Profit serves an order only where its revenue pays for serving it. Where not every order can be served, which ones go short follows from your costs as well: the plan serves those that earn the most after the cost of reaching them.
Emissions are transport emissions plus production emissions. Transport emissions are each lane's Emissions per Unit from Map Lanes times the flow on it. Production emissions are each plant's Emissions per Unit from Map Production Policy times what it makes. A scenario where neither lanes nor the production policy carry emissions is refused for the two objectives that use them. Once one row carries them, the check passes, and every lane or plant without emissions counts as emitting nothing.
Adding business rules¶
Wire one or more Add Constraint nodes the same way, between Create Scenario and this one, to cap a lane, limit how many origins serve each customer, or bound the budget. Each appends a rule the solve respects; the rules are listed on that node's page. A rule whose filter matches nothing in the scenario, a filter that cannot be read, a parameter the rule does not take, and a rule with a missing parameter are each reported and stop the run. Every rule that applied says what it covered, and a rule held For each says which members it left out.
Adding costs¶
Wire an Add Costs node the same way to price a lane in steps — by the truckload, or at a rate that changes with volume — instead of by one cost per unit. The cost counts as transport cost here and in the result. A cost the model cannot charge (steps with a gap, a lane priced twice) is reported here and blocks the solve; a cost that falls as more is shipped is reported as a warning and honoured.
Workflow wiring¶
This node takes scenario data. Wire it after Create Scenario, Load Scenario, Add Constraint or Add Costs, never straight after a Map node. Its result is kept by a Create Scenario after it; a workflow without one is refused.
flowchart LR
n1["Import Data (locations.csv)"] --> n2["Map Locations"]
n3["Import Data (lanes.csv)"] --> n4["Map Lanes"]
n4 --> n5["Create Scenario"]
n5 --> n6["Network Optimization"]
n6 --> n13["Create Scenario"]
n7["Import Data (supply.csv)"] --> n8["Map Supply Policy"]
n9["Import Data (demand.csv)"] --> n10["Map Demand Policy"]
n2 --> n5
n8 --> n5
n10 --> n5
n11["Import Data (products.csv)"] --> n12["Map Products"]
n12 --> n5
Transport flows from Map Transport are optional observation data and can be wired into the same Create Scenario.
Every location a lane, policy or flow names must be one of the scenario's locations. A row naming one that is not there is refused, by Create Scenario when the scenario is built and by this node for a scenario that reached it another way, with the rows and the unknown names listed. The optimizer never adds a location to match a name.
Starting from a baseline¶
When you start from an existing scenario, the optimizer needs per-unit cost and revenue rates on the policy tables: supply, demand, lanes and handling. A baseline built from observed history carries realized totals instead. Those record what happened; they are not the rates the optimizer uses to decide what should happen.
Feeding such a baseline straight in does not give an answer you can use:
- A missing supply cost stops the run. Priced at zero, that supplier would look free and the optimizer would source everything from it.
- A missing unit revenue stops the run when partial demand is allowed, because at zero revenue no order is worth serving. With partial demand off, which is the default, the run proceeds and only the reported objective is understated.
- A missing lane cost stops the run, with the lanes named.
- A handling row with no rate charges nothing at its site, so a baseline missing handling rates under-prices those sites.
The fix is to derive the per-unit rates from the observed history first, with a chain of Scenario Script steps:
flowchart LR
n1["Load Scenario"] --> n2["Scenario Script (derive supply rates, into Scenario Data)"]
n2 --> n3["Scenario Script (derive demand rates, into Scenario Data)"]
n3 --> n4["Network Optimization"]
n4 --> n5["Create Scenario"]
Each step writes the per-unit rate as a weighted average over the observed history. The Create Scenario at the end saves the optimized plan, and its Unit must be the baseline's; see Units. Where the baseline has no cost data at all for a concept, the rate is left empty and the run says so. That is a gap in your baseline, and naming it beats quietly pricing the cost at zero.
Common mistakes¶
- Wiring this node to a Map node instead of through Create Scenario
- Leaving Objective unset — the workflow is refused until you choose one
- Choosing Minimize Emissions or Weighted with Profit Weight 0 while Allow partial demand fulfillment is on. Validation refuses this combination because the objective gives no reward for serving demand. Turn partial demand off, choose Maximize Profit, or use Weighted with Profit Weight above 0
- Choosing Minimize Emissions or Weighted without emissions on the lanes or the production policy. The run stops and asks for them
- Entering a weight above 1 or below 0, or as a percentage such as
80. The panel refuses it; enter0.8 - Giving emissions to only some lanes or plants. The optimizer reads the others as emitting nothing and moves volume onto them
- Missing the lanes table, or leaving a lane's or a supplier's cost blank — the run is refused with the rows named
- No Create Scenario after this node, or one whose Unit differs from the scenario's — the run is refused
- A lane or policy naming a location spelled differently from Map Locations (
Hamburg DCbesideDC Hamburg) — the run is refused with the row named - A row naming a product spelled differently from Map Products (
Riesling 0.75besideRiesling 0,75) — the run is refused with the row named, rather than planning a second product with demand and no supply - Leaving out either Map Supply Policy or Map Demand Policy — each is a refusal on its own
- Two rows for the same thing: two locations with one name, two demand or supply rows for one product at one location, two production rows for one recipe at one location — the run is refused with both rows named, rather than keeping one of them. If the rows are parts of one total, aggregate them before the Map step
- Supply for a product that no lane carries, no demand asks for and no recipe uses — the run is refused with the rows named, since that supply could never enter the plan
- Leaving the product or the transport mode blank, even in a network with one product or one mode — the run is refused with the blank rows named
- Loading a baseline and optimizing without first deriving per-unit rates — baselines built from observed history are usually missing them
Simplify a network optimization¶
Simplify a network optimization has its own page in the Optimization section.
Messages this node can show¶
- Carrying cost period missing or unknown
- Component can’t reach plant
- Constraint applied
- Constraint left members out
- Constraint matches nothing
- Constraint not met
- Constraint parameter unknown
- Constraint rule settings unusable
- Constraint type unknown
- Cost falls as quantity grows
- Cost steps incomplete or overlapping
- Demand left unserved
- Lane capacity too small
- Lane minimum not met
- Location missing from Locations
- Location not on any lane
- Model difficulty profiled
- Model loosened to solve
- Model unsolvable even when loosened
- Network numbers too far apart
- Network shortfall totals
- Objective needs emissions data
- Optimization evidence
- Policy price absent
- Precision target met
- Product missing from Products
- Production capacity too small
- Quantity below zero
- Recipe makes several products
- Recipe missing from Bill of Materials
- Recipe product unknown
- Recipe units disagree
- Recipe uses its own product
- Required value blank
- Row stated twice
- Rules cannot all be met
- Scenario table absent
- Source and target identical
- Supply capacity exceeded
- Supply has no use
- Two lanes set one connection
- Unit conversion missing
- Unit conversions unusable
- Unit not stated
- Value breaks a table rule
The table this node makes is checked again with the rest of the scenario when Create Scenario assembles it: see messages about the scenario.
A run gives this node 125 minutes, unless its settings set a different limit. A node that takes longer stops with Time limit reached.
Like every node, it can also stop on the checks before a run and the run's limits.