Diagnosed with Explainer

Explainer takes a model that already exists and tells you why it behaves as it does — why it has no solution, what the smallest change is that would give it one, and how much the inputs can move before the answer stops holding.

Its three use cases need a model with something to diagnose, so the tabs below add two delivery contracts to the facility model. That is enough to make it infeasible, and enough to walk all three.

Modeler left the facility model with every test passing. Explainer picks up where that stops being true — when the model you are running comes back infeasible, and the question is not what should the model be but why does this one have no answer.

To get there, the model needs a reason to fail. Sales has committed to two delivery contracts with plant 4: warehouse 2 takes its full 18 from plant 4, and warehouse 1 takes at least 8 from plant 4 as well. Both are ordinary business requirements, added to the model as two more constraints:

model.addConstr(ship[4, 2] >= 18, name="contract_w2")
model.addConstr(ship[4, 1] >= 8, name="contract_w1")

The model now has 11 constraints instead of 9. It also has no solution:

Model status: INFEASIBLE

Nothing in that message says which of the eleven constraints is at fault. With 11 you could find it by inspection; the real network has thousands, and that is the situation Explainer is built for.

What you upload

Explainer takes the model file and computes the IIS itself — you do not need to call computeIIS first:

facility_contracts.lp

Alongside it, upload the context. This is the part that changes the quality of the answer rather than just its speed: the same constraint is immovable in one business and negotiable in another, and nothing in an LP file says which. Here that means the source code and the specification Modeler produced — the specification already records, in business terms, that demand must be met exactly and why.

This exchange, and the two that follow it in the next tabs, are driven for real against the Explainer MCP tool — not invented — with the model, the code and the specification attached exactly as described above.

facility_contracts.lp, facility_modeler.py, facility_spec.md attached

This came back infeasible after we added two delivery contracts with plant 4. The contracts are commitments sales already made, so I would rather not break them. Capacity is a physical limit on the plant.

The user has given me the model, the implementation, and the specification, plus a ranking of what is negotiable: contracts are committed but are business policy, capacity is physical. Computing the IIS on the model file first, then reading it against that ranking.

Computing the IIS.

The IIS is small — 3 constraints and 2 bounds, all of them about plant 4. That is a clean result: the conflict is local, and none of the demand constraints are involved. Searching the knowledge base for the relevant guidance before writing this up.

The IIS contains exactly 3 constraints and 2 bounds, all confined to plant 4:

Subject To
 capacity[4]: ship[4,1] + ship[4,2] + ship[4,3] + ship[4,4]
              - 19 open[4] <= 0
 contract_w2: ship[4,2] >= 18
 contract_w1: ship[4,1] >= 8
Bounds
 open[4] [B, LB=0, UB=1]
 ship[4,3] >= 0
 ship[4,4] >= 0

The root cause. The two contracts together commit plant 4 to shipping at least 18 + 8 = 26 thousand units. Plant 4’s capacity is 19. There is no assignment of any variable that satisfies both — you are 7 thousand units short, and no other plant can help, because the contracts name plant 4 specifically.

Two things worth noticing about this IIS:

• The demand constraints are not in it. Total demand is 67 against 96 of capacity, so the network as a whole is fine. This is not a supply shortfall — it is a routing commitment that exceeds one plant.
• open[4] is in the IIS but is not the problem. With open[4] = 1 the capacity row reads ≤ 19; with open[4] = 0 it reads ≤ 0, which is worse. Both branches fail, which is why the binary appears without offering an escape.

The non-negativity bounds on ship[4,3] and ship[4,4] are in the IIS because they rule out the one remaining escape: borrowing negative shipments on those lanes to free up room on the other two. Ignore them as remediation candidates — shipping a negative quantity is not a thing.

Since contracts are negotiable as a last resort and capacity is not, there is no configuration that satisfies both constraints as stated. The full report, including ranked remediation options, is saved to the workspace as facility_contracts_infeasibility_report.md — the next tab.

Why the names matter

That diagnosis is readable because the constraints have names. Modeler named them demand, capacity, contract_w1 and contract_w2, so the IIS names the business rules directly. Had they been unnamed, the same IIS would have come back as R7, R10, R11 — mathematically identical, and telling you nothing about which commitment to renegotiate.

Explainer flags this when it sees it: part of the report is a note on whether constraint naming is good enough to interpret, and where it is not. It is the cheapest thing you can do in advance to make an infeasibility diagnosable.

When the IIS is too expensive to compute

Computing an IIS means repeatedly re-solving subsets of the model, so on a large model it can take considerably longer than the original solve. If it does — or if the model is too large to upload — compute it locally and upload just the result:

gurobi_cl ResultFile=facility_contracts.ilp facility_contracts.lp
model.computeIIS()
model.write("facility_contracts.ilp")

The ILP file holds only the irreducible subsystem — for this model, 3 constraints out of 11 — so it stays small no matter how large the model was:

facility_contracts.ilp

Explainer accepts it in place of the model file, compressed if you like: gz, bz2, zip, 7z, and xz.