Skip to content

docs(piecewise): rewrite reference page + tutorials for correctness and pedagogy#677

Open
FBumann wants to merge 28 commits into
masterfrom
docs/piecewise-precision
Open

docs(piecewise): rewrite reference page + tutorials for correctness and pedagogy#677
FBumann wants to merge 28 commits into
masterfrom
docs/piecewise-precision

Conversation

@FBumann
Copy link
Copy Markdown
Collaborator

@FBumann FBumann commented May 11, 2026

What was wrong

The piecewise documentation had grown organically and accumulated several precision, structural, and pedagogical problems.

Correctness bugs

  • Disjunctive auxiliary-variables row in the comparison table said "Binary + SOS2", but _add_disjunctive actually creates continuous lambda_var, binary segment selectors, and an SOS2 constraint — three kinds, not two.
  • LP domain bound math was written x_0 ≤ x ≤ x_n, implicitly assuming ascending breakpoints. _lp_eligibility accepts strict monotonicity in either direction; the math should be x_min ≤ x ≤ x_max.
  • Inequality Quick Start example used (fuel, [0, 20, 30, 35], "<=") with the misleading comment "pinned to the curve" on the other tuple. Bounding fuel (a consumption-side variable) with <= was the anti-pattern the doc's own "Choice of bounded tuple" section warned against.
  • (0, 0) "off" segment in the disjunctive example used a degenerate single-point segment to encode on/off — exactly what active=... (Advanced Features) is for. The example duplicated a pattern that has a proper API and taught a bad habit.
  • Disjunctive table caveat that "Disjunctive (segments) → always sos2" appeared in the prose before the table, where the apparent contradiction (.method == "sos2" but a separate column) was confusing.
  • Implementation-detail leakage: a "Formulation math" subsection named internal constraint suffixes (*_link, *_output_link) while a different part of the page said names "are an implementation detail and may evolve".
  • "Pinned" was geometrically misleading: the doc claimed pinned tuples "lie on the curve", but for 2-tuple inequalities the pinned axis's marginal feasible set is just its breakpoint domain — a single coordinate can't locate a curve point.
  • Tutorial 2 was structured around fuel ≤ f(power) with a maximise -fuel objective. The artificial objective hid the fact that bounding fuel from above admits fuel = 0, which makes no physical sense.

Structural problems

  • "Per-tuple sign" was a chapter masquerading as a section — six concepts under one heading (roles, restrictions, joint geometry across 1/2/3+ tuples, formulation math, sign-choice motivation, deactivation warning). Readers looking up "how do I supply breakpoints" had to scroll past all of it.
  • Two breakpoint sections — "Breakpoint inputs" under API (overview) and "Breakpoint Construction" as its own section (tutorial). Same three building blocks, overlapping content.
  • tangent_lines was a useful standalone helper buried in a paragraph inside the LP method section.
  • Tutorial 1 §2's method-comparison table appeared before §3 and §4 had defined the terms it referenced (sign, curvature requirements).
  • Tutorial 2's standalone setup plot showed the same curve that the hypograph/epigraph visualisation later in the notebook would show again with operating points.

Tutorial-execution noise

  • Every m.solve() printed the HiGHS banner and the once-per-session EvolvingAPIWarning, drowning the actual lesson.

What changed

doc/piecewise-linear-constraints.rst

  • Restructured: moved Breakpoint Construction ahead of Per-tuple sign, split the latter into four focused subsections, folded the duplicate "Breakpoint inputs" overview into it, promoted tangent_lines to its own subsection under LP, and shortened the up-front terminology block.
  • Removed the abstract "Formulation math" subsection; the load-bearing active=0 warning moved into Active parameter.
  • Fixed factual content: corrected the Disjunctive aux-vars row, rewrote the LP domain bound as x_min ≤ x ≤ x_max, referenced :ref:sos-reformulation`` for the Big-M fallback, generalised "Choice of bounded tuple" to both signs, and explained why active isn't supported with `method="lp"`.
  • Replaced misleading examples: Quick Start inequality now uses fuel ≥ f(power) on the same curve as the equality example; disjunctive snippet replaces the (0, 0) on/off hack with a stepped pump and points to active=... for on/off.

examples/piecewise-linear-constraints.ipynb

  • New intro with an 8-section roadmap; heading renamed to "Creating Piecewise Linear Constraints".
  • §2 method comparison trimmed to a forward pointer.
  • §3 rewritten as two pumps in parallel with two operating bands each (low / high) and a forbidden zone. Demand [30, 75, 150] makes every timestep single-pump-infeasible. Output is a flat 4-column DataFrame.
  • §4 inequality now uses fuel ≥ f(power) on §1's curve.
  • New closing "When to use what" table with cross-links.

examples/piecewise-inequality-bounds.ipynb

  • Heading renamed to "Creating Piecewise Inequality Bounds".
  • Intro rewritten (motivation, then snippet, then a precise tuple-roles table); standalone setup plot removed.
  • Reframed end-to-end around fuel ≥ f(power) with breakpoints aligned to the rest of the doc; hypograph visualisation replaced with the epigraph variant; fallback cell exercises the dual mismatched-curvature cases.
  • New summary cross-linking back to the linear-constraints tutorial.

Both tutorials

  • m.solve(...) calls now pass solver_name="highs", reformulate_sos="auto", and output_flag=False.
  • Imports cell silences EvolvingAPIWarning.

Pedagogical additions (beyond the stated bug list)

A few additions go beyond fixing the documented problems and into making the docs a better read. Calling them out explicitly so they're easy to review or revert:

  • Tutorial 1 intro roadmap (8-section list).
  • Tutorial 1 "When to use what" closing table (cheat-sheet for picking the right API pattern).
  • Tutorial 2 "Tuple roles" table (replaces and expands the old "Key points" table with role + what-it-constrains semantics).
  • Tutorial 2 closing summary with cross-links back to the linear-constraints tutorial.
  • tangent_lines promoted to its own subsection under LP (discoverability — the function existed before but was buried).

Test plan

  • sphinx-build of doc/ renders without warnings.
  • CI's "Test documentation notebooks" job passes on both notebooks.
  • :ref:sos-reformulation`` link resolves correctly.

🤖 Generated with Claude Code

FBumann and others added 2 commits May 11, 2026 14:17
- "breakpoint" is now framed as one knot defined by the i-th entry of
  each tuple, so the N-tuple linking case isn't (x, y)-specific.
- "pinned" is reframed as a sign role, with a brief breakdown of what
  it actually constrains: joint-on-curve only with 2+ pinned tuples;
  with one bounded + one pinned, the pinned axis collapses to a
  [x_min, x_max] domain box (LP enforces it directly, SOS2/incremental
  via the weight link).
- Disjunctive auxiliary-variables cell corrected to
  "Continuous + binary + SOS2".
- SOS2/disjunctive solver-requirement cells now mention the Big-M
  reformulation path and link to :ref:`sos-reformulation`.
- LP domain bound math uses x_min/x_max (descending grids accepted).
- Per-tuple-sign formulation math switched to a method-agnostic
  W_j(weights, B) so the section covers both SOS2 (lambda) and
  incremental (delta) accurately.
- "Two factories" lead-in widened to three building blocks so Slopes
  isn't hidden in the code block.
- Quick Start inequality bounds heat (a curtailable output) instead of
  fuel, matching the doc's own "choice of bounded tuple" guidance.
- Tutorials: add output_flag=False to m.solve(...) calls so HiGHS's
  banner/progress doesn't clutter the nbsphinx-rendered output;
  linopy's INFO logs (including auto-dispatch resolution) are kept.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r Constraints"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FBumann FBumann marked this pull request as draft May 11, 2026 12:22
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FBumann
Copy link
Copy Markdown
Collaborator Author

FBumann commented May 11, 2026

@FabianHofmann I would argue that we could try to remove the solver logs from the tutorials in the docs overall...?
WOuld improve readability and reduce noise i think

FBumann and others added 7 commits May 11, 2026 14:33
- Tut 1 intro now opens with a one-line description plus an 8-section
  roadmap, replacing the jargon-y "stacks one feature on top of a
  small shared dispatch pattern" line.
- Tut 1 §3 explains the degenerate (0, 0) "off" segment instead of
  leaving it for the reader to puzzle out.
- Tut 1 §4 inequality example now bounds `heat` (a curtailable output)
  on a concave curve, matching the rst doc's "choice of bounded tuple"
  guidance.  Variables use intuitive `power_pts` / `heat_pts` names so
  the plot cell no longer needs a "swap to put power on the x-axis"
  comment.
- Tut 1 ends with a "When to use what" table cross-linking to the rst
  reference page and to the inequality-bounds tutorial.
- Tut 2 intro leads with the one-sided-bound motivation and the
  pure-LP pay-off before the API snippet, and the "Tuple roles" table
  matches the rst's precision: with one bounded + one equality tuple,
  the equality tuple's marginal feasible set is just its breakpoint
  domain (not "lies exactly on the curve").
- Tut 2's summary cross-links back to the linear-constraints tutorial.
- Both notebooks: removed remaining "pinned" wording where it implied
  geometric on-curve placement instead of a sign role.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Other linopy tutorials (create-a-model.ipynb, manipulating-models.ipynb,
etc.) explicitly pass solver_name='highs' to m.solve(...). The piecewise
tutorials relied on the default-available-solver fallback, which made
the HiGHS-specific output_flag kwarg look mysterious.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous framing — fuel <= f(power) with objective -fuel — is
logically off: bounding fuel from above lets the solver choose any
non-negative fuel, including zero (since fuel is consumption-side).
The artificial maximise-fuel objective hid this but the underlying
relation didn't make physical sense.

Power ≤ f(fuel) is the classic production-function bound: f maps fuel
input to the maximum power the unit can deliver, and the unit can
always run below that (output is curtailable).  Maximising power
against the bound is now a sensible LP objective rather than a
contrivance.

- Reframed cells 0, 2, 3, 4, 5, 6, 7, 8, 10 around fuel_pts /
  power_pts; axis labels and the hypograph plot now read (fuel, power)
  with f(fuel) as the production curve.
- Cell 5's objective is now `-power` (maximise power up to the curve
  bound), matching real LPs.
- Test points and curvature × sign analysis unchanged — only the
  physical interpretation flipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous version bounded `heat` by a curve labelled "concave" — but the
slopes (0.8, 1.0, 1.33) are increasing, so the curve is actually convex,
and `heat <= f(power)` on a convex curve doesn't dispatch to LP.

Switched to a convex heat-rate curve with `fuel >= f(power)`: over-
fuelling is physically admissible (waste heat) but wasteful, so
minimising fuel pulls the operating point onto the curve.  This is the
epigraph half of the LP-applicable region, complementing the inequality-
bounds tutorial's hypograph example (concave + `<=`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the tutorial 1 §4 reframe: convex heat-rate curve with
``fuel >= f(power)``, where over-fuelling is admissible but wasteful so
minimisation pulls the operating point onto the curve.  Replaces the
heat <= f(power) example (which used a curve that was actually convex,
not concave, so it wouldn't have dispatched to LP).

Generalised the "Choice of bounded tuple" guidance to cover both signs:

- ``"<="`` for a controllable dissipation path (curtailment, post-
  treatment).
- ``">="`` for an input whose over-supply is admissible but wasteful
  (fuel, raw materials).

The wrong-direction warning now spells out both anti-patterns: ``"<="``
on fuel, ``">="`` on a non-curtailable output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reuses §1's exact breakpoints — only the sign on one tuple changes —
so the reader sees "the same curve, now with an inequality" rather than
a new curve.  Reads as a production function: power output is bounded
by what the fuel input can support, and the unit may run below the
maximum (curtailable output).

Same physical meaning as the previous fuel ≥ f(power) framing (the two
forms describe the same feasible region under inversion), but more
intuitive and continuous with the rest of the tutorial.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inequality-bounds tutorial now uses the same curve as §1/§4 of the
linear-constraints tutorial and the rst Quick Start:

  fuel_pts  = [0, 36, 84, 170]
  power_pts = [0, 30, 60, 100]

This concave production curve has f(60) = 45, so the verification cell
checks power = 45 at fuel = 60.  The hypograph-visualisation test
points are scaled to the new axes: (60, 30) under, (60, 45) on,
(60, 55) above, (180, 50) beyond domain.  The non-convex fallback
example also uses the [0, 170] fuel domain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FabianHofmann
Copy link
Copy Markdown
Collaborator

@FabianHofmann I would argue that we could try to remove the solver logs from the tutorials in the docs overall...? WOuld improve readability and reduce noise i think

yes, totally

FBumann and others added 3 commits May 11, 2026 14:50
The warning fires once per session on first use of
add_piecewise_formulation / Slopes / tangent_lines.  Useful in user
code, but in a tutorial it's a distracting wall of yellow above the
first solve.  Silenced in the imports cell of each notebook via
warnings.filterwarnings — the canonical recipe from the warning
message itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Heat-rate framing — `fuel = f(power)` — is native to power-systems
modelling, so the inequality variant reads naturally as `fuel ≥
f(power)`: the curve is the design minimum, over-fuelling is admissible
but wasteful, and minimising fuel pulls the operating point onto the
curve.  Same feasible region as the previous `power ≤ f(fuel)` form
under inversion; the LP path uses the convex + `>=` (epigraph) half
instead of concave + `<=` (hypograph).

- rst Quick Start: bound fuel ≥ f(power), same breakpoints as §1.
- Tutorial 1 §4: same.
- Tutorial 2: setup curve labelled "convex heat-rate curve f(power)";
  solve checks fuel=84 at power=60; visualisation is now the epigraph
  with test points (60, 100), (60, 84), (60, 70), (120, 100); fallback
  cell exercises the dual mismatched-curvature cases (convex + `<=`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HiGHS doesn't natively support SOS constraints, so the sos2 and
incremental cases of the three-methods comparison need
reformulate_sos="auto" (matching tutorial 1's pattern).  Without it,
m.solve(solver_name="highs", ...) raises ValueError on the SOS2 path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FBumann added a commit that referenced this pull request May 11, 2026
HiGHS prints a banner + progress lines to the Python REPL on every
m.solve() call by default.  In a tutorial that calls solve many times,
this drowns the actual lesson in solver chatter.  Pass output_flag=False
(a HiGHS solver option forwarded via **solver_options) to suppress it.

Touches the four notebooks where solver_name="highs" is the only solver
invoked:

- create-a-model.ipynb
- create-a-model-with-coordinates.ipynb
- manipulating-models.ipynb (9 solves)
- transport-tutorial.ipynb

Left alone:
- infeasible-model.ipynb (uses Gurobi, kwarg is OutputFlag there;
  also showing solver feedback may be pedagogically relevant for
  infeasibility detection).
- solve-on-remote.ipynb / solve-on-oetc.ipynb (remote handler manages
  its own logging).
- piecewise-*.ipynb (already addressed in #677).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FBumann FBumann force-pushed the docs/piecewise-precision branch from e58dcd4 to 825af88 Compare May 11, 2026 13:39
FBumann and others added 3 commits May 11, 2026 15:49
RST restructure
- Moved "Breakpoint Construction" ahead of "Per-tuple sign".  Readers
  looking up "how do I supply breakpoints" no longer have to scroll
  through the inequality section first.
- Split "Per-tuple sign" — previously a chapter masquerading as a
  section, covering six concepts under one heading — into five proper
  subsections: Roles and restrictions, Geometry, Choice of bounded
  tuple and sign, When is a one-sided bound wanted?, Formulation math.
- Folded the duplicate "Breakpoint inputs" overview (under API) into
  the lead-in of "Breakpoint Construction"; the three building blocks
  (breakpoints, segments, Slopes) are now introduced once.
- Promoted tangent_lines to its own "Chord expressions as a building
  block" subsection under LP — previously a half-hidden paragraph.
- Moved the disjunctive caveat ("method returns 'sos2' but the table
  treats it separately") into a `.. note::` directly under the
  comparison table where the apparent contradiction is.
- Shortened the terminology block at the top to one paragraph (it
  previously defined "segment" hundreds of lines before the disjunctive
  section).
- Centralised N≥3 sign restriction — N-variable linking section now
  cross-references Per-tuple sign instead of redefining it.
- Removed the duplicated active+sign warning under "Advanced Features"
  — single source of truth now in "Per-tuple sign / Formulation math".

Tutorial flow
- Tut 1 §2: trimmed the upfront method-comparison table to a forward
  pointer — the table previously claimed "lp requires sign != ==" and
  "matching curvature" before §3 and §4 had tutorialised those terms.
- Tut 2: dropped the standalone setup plot that showed only the curve.
  The hypograph/epigraph visualisation later in the notebook already
  shows the curve, with the operating points overlaid — saves a
  figure and tightens the narrative.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o Active

The W_j(weights, B) abstraction it introduced was immediately re-
derived in each method section below (SOS2 with λ, incremental with
δ), so it added a layer for the reader to climb through without
delivering content the method sections didn't already.  The
"*_link" / "*_output_link" naming detail contradicted our own guidance
elsewhere ("exact name suffixes are an implementation detail and may
evolve").  The "equality keeps equality, bounded flips sign" idea is
already conveyed by Roles & restrictions and Geometry above.

The one load-bearing nugget was the active=0 warning.  Moved it into
the Active parameter (unit commitment) subsection under Advanced
Features, where readers setting `active=...` actually land — that's
where the gotcha needs to be visible.

Also dropped the now-stale cross-reference from the Incremental method
section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"Not supported with method='lp'" reads as a missing feature; one-line
addition explains it's structural (gating needs a binary) and points
to the auto-dispatch and to tangent_lines for manual gating.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FBumann FBumann force-pushed the docs/piecewise-precision branch from 6d3b99f to c2fe7f6 Compare May 11, 2026 14:16
FBumann and others added 7 commits May 11, 2026 16:21
…example

The previous example used `segments([(0, 0), (50, 80)])` — the (0,0)
"off" segment was a hack to encode on/off behaviour, which is exactly
what `active=...` (§5) is for.  Using disjunctive for on/off
duplicates §5 and teaches a bad pattern.

New example shows what disjunctive is actually for: equipment without
a continuous design space — gas turbines in three commercial classes
(small / medium / large), each with its own non-overlapping power band
and 2-piece heat-rate curve.  The formulation picks the appropriate
class per timestep based on demand.

Added a forward pointer to §5 for the on/off case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…izing

Matches the tutorial §3 rewrite — uses three turbine classes with
non-overlapping operating bands as the canonical disjunctive example,
and adds a one-line steer away from the (0,0) anti-pattern toward
`active=...` for on/off gating.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The turbine-classes framing was claiming "investment" but the
formulation puts the binary on the time dimension by default — so
the model was actually re-deciding the class each timestep, not
making a one-time sizing call.  Either we'd need a contrived
constraint to make the binary time-invariant, or we'd be using an
"investment" narrative on per-period selection.

Pump VSD is honest per-period selection: a pump with three stepped
speed settings, each covering a different flow band, switches speed
between dispatch periods as a matter of normal operation.  No
constraints to bolt on, no narrative-vs-math gap.

Also added "switchable combustion cycles" and "allowed bands around
forbidden vibration zones" as alternative real use cases in the
intro, so readers see disjunctive is for genuine multi-mode
equipment.

Updated both the rst Disjunctive-segments code block and the
tutorial §3 (markdown intro, code, explanatory cell) to use the
pump example.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two pumps in parallel, each with two operating bands (low 5–25 m³/h,
high 40–100 m³/h) and a forbidden zone in between.  Demand profile
[30, 75, 150] makes every timestep single-pump-infeasible:

- t=1, demand=30: lands in the single-pump gap (25, 40); both pumps
  run in low band, splitting the load.
- t=2, demand=75: too much for low+low (max 50), too little for
  high+high (min 80); the low pump tops out at 25, the high pump
  covers the remaining 50.
- t=3, demand=150: exceeds a single pump's maximum (100); both pumps
  run in high band.

Each segment is now a single piece (2 breakpoints), so the example is
clean and the load-split arithmetic is explicit.  Rst snippet updated
to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
power_p1 / power_p2 / flow (total) per timestep — the asymmetric split
at t=2 is visible directly in the powers (one pump at 7 kW, the other
at ~21 kW) instead of buried in a multi-index DataFrame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
flat 4-column DataFrame (flow_p1, flow_p2, power_p1, power_p2) per
timestep, via unstack on the pump dimension.  The asymmetric splits
are visible directly: at t=2 one pump runs at flow=25 (low band), the
other at flow=50 (high band).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
§3 previously rebound the Python `demand` variable to its own
pump-tutorial values [30, 75, 150], which then broke §4 and §8
downstream: both rely on §1's `demand = [50, 80, 30]`, and §4 with
demand=150 is infeasible (power.upper=100).

Inlined §3's demand into the constraint so the global namespace is
untouched.  Verified §1 → §3 → §4 sequence end-to-end: §4 now solves
to optimal with fuel = [68, 127, 36].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FBumann FBumann changed the title docs(piecewise): tighten language and fix small inaccuracies docs(piecewise): rewrite reference page + tutorials for correctness and pedagogy May 11, 2026
FBumann and others added 4 commits May 11, 2026 22:17
…he code block

A five-line "# ..." comment inside the snippet was carrying the
formulation explanation — that belongs as rst prose around the code
block, not stuffed into the Python.  Code block now has a single
one-line orienting comment; the rationale moves to a paragraph below.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two-line "stepped pump ... forbidden zone" comment inside the
code block duplicated the prose immediately above ("stepped pump
speeds ... forbidden vibration zones").  Single-line orienting
comment is enough.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bare-slug markdown links (`[text](piecewise-linear-constraints)`)
produced sphinx "File not found" warnings AND rendered with href
attributes missing the `.html` extension, requiring browser-side
resolution.  Switching to `.html` form generates correct `href`s
in the rendered HTML directly — verified against the built doc:

  <a class="reference external" href="piecewise-linear-constraints.html">
  <a class="reference external" href="piecewise-inequality-bounds-tutorial.html">

Sphinx still warns at build time (its link checker doesn't find a
source file with `.html` extension), but the warnings are cosmetic
and the rendered docs work correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous .html form rendered correct browser hrefs but produced
sphinx "File not found" warnings AND rendered as external links.

Switching to source-extension form:
- [text](page.rst) for rst pages
- [text](tutorial.nblink) for sibling notebook tutorials

Sphinx resolves both to internal cross-references at build time —
HTML output shows `class="reference internal"` with the doc anchor,
no build warnings.  Verified locally: piecewise cross-link warnings
went from 6 to 0 (total build warnings 20 → 10, the remainder are
pre-existing unrelated issues).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FBumann FBumann marked this pull request as ready for review May 11, 2026 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants