Skip to content

Illustrate prescriptive sensitivity and conflict analysis in templates#78

Open
chriscoey wants to merge 17 commits into
mainfrom
prescriptive-sensitivity-conflict-examples
Open

Illustrate prescriptive sensitivity and conflict analysis in templates#78
chriscoey wants to merge 17 commits into
mainfrom
prescriptive-sensitivity-conflict-examples

Conversation

@chriscoey
Copy link
Copy Markdown
Member

@chriscoey chriscoey commented Jun 2, 2026

What

Extends three existing prescriptive-optimization templates to demonstrate the new sensitivity (LP marginals) and conflict / IIS (infeasibility diagnosis) analysis on solve():

  • factory_production — a maximize-profit product-mix LP. One 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.
  • supplier_reliability — a minimize-cost sourcing LP. Reads supplier-capacity and product-demand shadow prices plus supply-lane reduced costs / basis status, then re-solves supplier-disruption scenarios.
  • cicd_runner_allocation — a binary assignment MIP. When a maintenance outage makes the schedule infeasible, 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={...} on satisfy), 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 relationalai dependency floor to the first version that includes sensitivity/conflict (the pins still say 1.0.14, which predates the API), and re-run the three templates against the released build.

🤖 Generated with Claude Code

chriscoey and others added 2 commits June 2, 2026 14:26
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>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

The docs preview for this pull request has been deployed to Vercel!

✅ Preview: https://relationalai-docs-tkoj4zdke-relationalai.vercel.app/build/templates
🔍 Inspect: https://vercel.com/relationalai/relationalai-docs/6KTAcrgtwokoJNNWYKLmCCG3tMbK

chriscoey and others added 8 commits June 3, 2026 23:45
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>
@chriscoey chriscoey marked this pull request as ready for review June 4, 2026 07:37
@cafzal cafzal self-requested a review June 4, 2026 15:26
- 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>
chriscoey and others added 4 commits June 4, 2026 11:41
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>
Copy link
Copy Markdown
Collaborator

@cafzal cafzal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (the relationalai==1.0.14 pins)
  • README prerequisite lines: factory_production/README.md:68, supplier_reliability/README.md:62, cicd_runner_allocation/README.md:59 (the >= 1.0.14 lines)

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_reliability industry: "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>
@chriscoey
Copy link
Copy Markdown
Member Author

All the post-release items are in. The dependency floor is bumped at all six spots: the three pyproject pins are relationalai==1.9.0 and the three README prerequisite lines say >= 1.9.0, with 1.9.0 being the release that ships the sensitivity/conflict API. The two minor items are also addressed: each README readback snippet now notes that .inspect() is the quick interactive form while the scripts materialize the same selects with .to_df(), and supplier_reliability's industry tag is aligned to "Supply Chain & Logistics".

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants