Illustrate prescriptive sensitivity and conflict analysis in templates#78
Illustrate prescriptive sensitivity and conflict analysis in templates#78chriscoey wants to merge 17 commits into
Conversation
Extend three prescriptive-optimization templates to demonstrate the new sensitivity (LP marginals) and conflict (IIS) analysis available on solve(): - factory_production: a maximize-profit product-mix LP that reads capacity shadow prices and product reduced costs / basis status from a single solve. - supplier_reliability: a minimize-cost sourcing LP with capacity and demand shadow prices, lane reduced costs, plus supplier-disruption scenarios. - cicd_runner_allocation: a binary assignment MIP that diagnoses an infeasible maintenance outage via conflict (IIS) membership. Each marginal or conflict joins back to its grounding entity by key through constraint back-pointers. Validated end-to-end against the solver. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ispatch, sharper marginals prose - Aliased + sorted marginal and conflict tables so each README's expected-output matches actual stdout deterministically (baseline and scenario displays) - Dispatch on conflict_status (read the IIS only for CONFLICT_FOUND) instead of asserting a single value, with a clear reason on other outcomes - Correct the degenerate-breakpoint and one-way shadow-price wording; add a product-name uniqueness guard and tighten the plan tolerance - Silence the benign rules-in-a-loop warning from the scenario sweep, by message Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The docs preview for this pull request has been deployed to Vercel!
|
The constraint back-pointer that joins a marginal or conflict membership to
its entity's data is now declared explicitly via satisfy(keyed_by={...})
rather than detected automatically. Declare the keys in all three templates
and align the docstring/README wording. Variable back-pointers are unchanged
(still derived from field names).
Validated end-to-end: supplier_reliability and factory_production return the
documented optima and marginals; cicd_runner_allocation returns CONFLICT_FOUND
with the documented IIS (six stranded high-CPU jobs + the ubuntu-xlarge cap).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The expected-output header claimed the conflict diagnosis is stable, but which six of the seven stranded jobs the IIS names is solver-dependent (observed across runs); only the statuses, costs, and binding cap are stable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The rules-created-in-a-loop false positive arises only while solve_allocation rebuilds the Problem per scenario, so suppress it with warnings.catch_warnings() inside that builder instead of mutating process-wide warning state at module scope. Warning behavior everywhere else in the run is now untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The two min_cpu=8 jobs are compatible with only ubuntu-xlarge and self-hosted-linux, so state the high-CPU compatibility claim as a bound rather than implying all seven jobs match all three big Linux runners. Restate the where= comment accurately: solve_for treats an empty where= and None identically. Note in the README why the template's else branch raises where the copyable snippet prints. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerate v1/README.md to pick up the rewritten template descriptions; extend cicd_runner_allocation's description and experience level to cover the conflict-analysis addition. Add explanatory messages to the bare objective asserts so a data edit fails with expected-vs-got instead of a bare AssertionError, and drop a stray internal reference from a comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The maintenance-outage logic hard-codes the two offline runners and the surviving ubuntu-xlarge cap; if runners.csv renames any of them the where= exclusion silently excludes nothing and the outage stays feasible, failing later with an unrelated message. Mirror the existing workflow-side drift guard for the runner side, and note when the capacity-shadow-price requires get validated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Note why the complementary-slackness frame keeps the supplier column (select returns distinct rows, so an identifying column preserves one row per lane), why the baseline order quantities are not pinned (cost-equal alternate optima), and which Allocation fields serve the feasible vs infeasible read paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
State the practical reason for the supplier column (identifiable rows, names the offender on failure) without asserting projection semantics that differ across backends. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- State that marginal rates come without RHS/coefficient ranging, so the validity range is not reported - Note that conflict=True must be requested up front or on a fresh build; an already-solved Problem cannot add it on a re-solve - Simplify the LP/QP parenthetical in the supplier sensitivity note Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the baseline assert's pattern so a missing objective fails as an AssertionError with the scenario named, not an opaque TypeError. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ison Use the double-dash convention of the sibling templates throughout factory_production and the remaining cicd/supplier stragglers. Rewrite the factory<->supplier basis-status comparison so the supplier side names the priced-out lane role (NONBASIC_AT_LOWER) instead of reusing 'binding product', which maps to the wrong role in that template. Drop an unresolvable 'this portfolio' referent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 1.5x narrative credited the saving to high-CPU jobs, but the 1.0x output already places all seven on self-hosted; the saving comes from the four low-CPU jobs that move off the ubuntu runners. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cafzal
left a comment
There was a problem hiding this comment.
Review — sensitivity + conflict template examples
Re-reviewed against the upstream feature PyRel #1617, now merged to main (b120f95c3). All three scripts py_compile clean and ruff passes; frontmatter vocab is valid. API usage matches the merged source — solve(sensitivity=True) with an objective present, solve(conflict=True) on the MIP, keyed_by declared on satisfy(), attribute names correct, no conflict_message. The factory (maximize) / supplier (minimize) sign-convention contrast is correct, and the cicd outage is provably infeasible against the sample CSVs (its assert guards hold).
Required after release (not a merge blocker)
relationalai==1.0.14 predates the sensitivity/conflict API. Once the release that ships #1617 is out, bump the dependency floor at all six spots, then re-run all three templates against the released build:
v1/factory_production/pyproject.toml:12,v1/supplier_reliability/pyproject.toml:12,v1/cicd_runner_allocation/pyproject.toml:12(therelationalai==1.0.14pins)- README prerequisite lines:
factory_production/README.md:68,supplier_reliability/README.md:62,cicd_runner_allocation/README.md:59(the>= 1.0.14lines)
Exact version is not knowable yet — #1617 is on the repo's 1.8.x line, the customer SDK line is 1.0.x, and the feature also gates on the rai-solver-service #246 producer deploy. Until pinned, the READMEs' expected-output blocks remain static rather than live-captured.
Minor (non-blocking)
- README "How it works" snippets show
.inspect()where the scripts use.to_df()/print(.inspect()appears in no script). Make verbatim or mark illustrative. supplier_reliabilityindustry: "Supply Chain"is off the dominant portfolio convention"Supply Chain & Logistics"(pre-existing; the PR didn't touch it).
- Pin relationalai==1.9.0 — the first release that ships solve(sensitivity=) / solve(conflict=) — in the three pyprojects and README prerequisites. - Note under each README readback snippet that .inspect() is the quick interactive form; the scripts materialize the same selects with .to_df(). - supplier_reliability: industry frontmatter to "Supply Chain & Logistics", matching the portfolio convention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All three templates now run end-to-end against the released sensitivity/conflict interface: each script exits 0 with all assertion guards holding. Statuses, objectives, shadow prices, reduced costs, and the conflict membership match the previous expected-output blocks exactly. The rows the READMEs hedge as tied-optima-dependent are updated to the captured solutions: the supplier baseline/scenario order splits and basis statuses, and the equal-cost ubuntu job split in the runner scenarios. Also name SupplierA among the suppliers absorbing demand in the without_SupplierC scenario. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
All the post-release items are in. The dependency floor is bumped at all six spots: the three pyproject pins are I re-ran all three templates end-to-end against the released build: each exits 0 with all assertion guards holding. The README expected-output blocks are now live-captured from those runs. Statuses, objectives, shadow prices, reduced costs, and the conflict membership matched the previous blocks exactly; the rows the READMEs hedge as tied-optima-dependent (order splits, basis statuses, the equal-cost ubuntu job split) were updated to the captured solutions. |
What
Extends three existing prescriptive-optimization templates to demonstrate the new sensitivity (LP marginals) and conflict / IIS (infeasibility diagnosis) analysis on
solve():solve(sensitivity=True)reads each factory's capacity shadow price and each product's reduced cost + basis status, joined back to its entity by key. A binding factory vs. a slack one shows the zero/nonzero shadow-price contrast, and the maximize objective mirrors the sign conventions of the minimize example below.solve(conflict=True)returns the conflict set (IIS): the stranded jobs and the binding runner cap, read back by entity key.Each marginal or conflict joins to its grounding entity by key through the constraint's declared back-pointer (
keyed_by={...}onsatisfy), mirroring the automatic variable back-pointers. Each README walks through the model, the marginals/conflict, and how to act on them. All three runners were validated end-to-end against the solver, including assertion-level checks on objectives, marginal signs, basis statuses, and IIS membership.Merge gating
These examples exercise the sensitivity and conflict APIs added in the upcoming PyRel release (RelationalAI/PyRel#1617), so the runners only work against a build that includes that API. Before merge, once that release ships: raise each template's
relationalaidependency floor to the first version that includes sensitivity/conflict (the pins still say1.0.14, which predates the API), and re-run the three templates against the released build.🤖 Generated with Claude Code