docs(piecewise): rewrite reference page + tutorials for correctness and pedagogy#677
Conversation
- "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>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@FabianHofmann I would argue that we could try to remove the solver logs from the tutorials in the docs overall...? |
- 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>
yes, totally |
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>
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>
e58dcd4 to
825af88
Compare
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>
6d3b99f to
c2fe7f6
Compare
…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>
…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>
| works here. | ||
|
|
||
| For a single on/off gate on one continuous curve, prefer ``active=...`` | ||
| (see *Advanced Features* below) — using a degenerate ``(0, 0)`` segment |
There was a problem hiding this comment.
"advanced features" should likely be a ref link
There was a problem hiding this comment.
Now links directly to the active parameter
| - At most one tuple may carry a non-equality sign — a single bounded side. | ||
| - With **3 or more** tuples, all signs must be ``"=="``. | ||
|
|
||
| Multi-bounded and N≥3-inequality use cases aren't supported yet. If |
There was a problem hiding this comment.
this could go into a info block
| - :func:`~linopy.segments` — **disjoint** operating regions with gaps | ||
| between them (e.g. forbidden zones); selects the disjunctive | ||
| formulation automatically. | ||
| - :class:`~linopy.Slopes` — per-piece slopes plus an initial ``y0``, |
There was a problem hiding this comment.
I don't see a more detailed explanation on their usages. it would be helpful to know how they show up in the code. add a paragraph what their individual purpose is (either individually or combined) or ref examples (like the notebook)
There was a problem hiding this comment.
I rewrote the section as a whole. now starts with a brief intro thats closely related to our data mdoel (breakpoints or segments), followed by how to pass breakpoints or segments. Slopes is framed as a convenience for breakpoints (which it is. We resolve it to breakpoints)
1. Forward pointer to the Active parameter section now uses a proper
`:ref:` link. Added a `_piecewise-active:` label above the
subsection so it survives reorganisation.
2. The Restrictions block under "Roles and restrictions" became a
`.. note::` admonition (matching the pattern in sos-constraints.rst
and gpu-acceleration.rst).
3. Rewrote Breakpoint Construction. Previously six subsections
organised around input-shape and example type (From lists / factory
/ From slopes / Per-entity / Disjunctive / N-variable); now four
organised around the three building blocks themselves:
- ``breakpoints()`` — connected curve, with use case (efficiency
curves, heat rates, cost curves) and the per-entity option folded
in as a subsection.
- ``segments()`` — disjoint operating bands.
- ``Slopes`` — marginal-cost specification.
- N-variable linking (kept separate; orthogonal to input type).
Each subsection opens with the use case it addresses.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fa58ae0 to
1753923
Compare
Fabian's review raised three points (#677 (review)): 1. "Advanced features" forward-pointer should be a ref link. Added `_piecewise-active:` label above the Active parameter subsection; the disjunctive forward-pointer now uses `:ref:`piecewise-active`` (renders as a clickable internal link). 2. Restrictions block could be an info block. Converted to `.. note::` (matching the existing pattern in sos-constraints.rst and gpu-acceleration.rst — the codebase doesn't use `.. info::`, only note/warning). 3. Building blocks need more detail on usage. Restructured Breakpoint Construction. Originally six subsections organised around input shape (From lists / breakpoints() factory / From slopes / Per-entity / Disjunctive / N-variable). Now organised around the two factories with Slopes folded in as a convenience: - `breakpoints()` — connected curve. Opens with use case (efficiency curves, heat rates, cost curves). Two bold-inline sub-blocks: **Per-entity curves.** (dict input) and **Specifying by slopes.** (Slopes wrapper). - `segments()` — disjoint operating bands. Opens with use case. Trimmed redundant trailing prose; kept the bounded-tuple compatibility note as a one-liner. - N-variable linking — kept separate (orthogonal to input type). Added a two-sentence signpost at the top of Breakpoint Construction that transitions from the API signature: names both factories with cross-refs and positions Slopes as a slope-input stand-in for `breakpoints()` (not a separate building block — Slopes resolves to a breakpoints array internally). Plus minor editorial cleanups from the iteration: - Dropped the SOS2 jargon from the `segments()` opener (replaced with "continuous interpolation within the chosen band" — the SOS2 method is introduced later in Formulation Methods). - Dropped the accepted-datatypes enumeration from the `breakpoints()` opener (redundant with examples and the function's docstring). - Dropped the ambiguous "prices per MWh per operating step" example from the Slopes block — both electricity-specific and confusable with the disjunctive/VSD case from §3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d186853 to
532dab6
Compare
|
@FabianHofmann Its ready. |
…ces (#680) * docs: silence HiGHS console output in tutorial notebooks 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> * docs: silence HiGHS console output in piecewise tutorials too Extends the log-silencing scope to the two piecewise tutorials, which together call m.solve() nine times. Same transformation as the other notebooks — output_flag=False as a HiGHS-specific kwarg forwarded via **solver_options. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: fix broken toctree, refresh API reference, and clean up references - Add doc/coordinate-alignment.nblink so the index.rst toctree entry resolves to examples/coordinate-alignment.ipynb. - Update api.rst to match the current public API: add the missing solver classes (COPT, Knitro, MindOpt, PIPS, cuPDLPx), expose top-level helpers (align, merge, options, EvolvingAPIWarning, PerformanceWarning), add the missing Model methods (add_sos_constraints, reformulate_sos_constraints, compute_infeasibilities, format_infeasibilities), add Variable methods (to_linexpr, fix/unfix, relax/unrelax), add sections for QuadraticExpression, Objective, and RemoteHandler, remove the duplicate Variables.integers, and fix the "hook" -> "hood" typo. - contributing.rst: replace stale Black reference with ruff, correct the nblink example (proper JSON, right path, fixed RST indentation that was breaking pygments), and use pre-commit run --all-files. - benchmark.rst: fix the rendered objective, which read as a product of two variables; corrected to the actual linear benchmark (2x + y with x - y >= i-1, matching benchmark_linopy.py). - prerequisites.rst: add SCIP, give MOSEK a description, drop the dangling "-" after MindOpt, remove the outdated HiGHS-platforms claim, and clarify what the [solvers] extra actually pulls in. - conf.py + index.rst: bump copyright to 2026 and fix the "contnuous" typo on the landing page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: silence HiGHS console output in tutorial notebooks 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> * docs: silence HiGHS console output in piecewise tutorials too Extends the log-silencing scope to the two piecewise tutorials, which together call m.solve() nine times. Same transformation as the other notebooks — output_flag=False as a HiGHS-specific kwarg forwarded via **solver_options. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…uide landing (#681) * docs: silence HiGHS console output in tutorial notebooks 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> * docs: silence HiGHS console output in piecewise tutorials too Extends the log-silencing scope to the two piecewise tutorials, which together call m.solve() nine times. Same transformation as the other notebooks — output_flag=False as a HiGHS-specific kwarg forwarded via **solver_options. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: fix broken toctree, refresh API reference, and clean up references - Add doc/coordinate-alignment.nblink so the index.rst toctree entry resolves to examples/coordinate-alignment.ipynb. - Update api.rst to match the current public API: add the missing solver classes (COPT, Knitro, MindOpt, PIPS, cuPDLPx), expose top-level helpers (align, merge, options, EvolvingAPIWarning, PerformanceWarning), add the missing Model methods (add_sos_constraints, reformulate_sos_constraints, compute_infeasibilities, format_infeasibilities), add Variable methods (to_linexpr, fix/unfix, relax/unrelax), add sections for QuadraticExpression, Objective, and RemoteHandler, remove the duplicate Variables.integers, and fix the "hook" -> "hood" typo. - contributing.rst: replace stale Black reference with ruff, correct the nblink example (proper JSON, right path, fixed RST indentation that was breaking pygments), and use pre-commit run --all-files. - benchmark.rst: fix the rendered objective, which read as a product of two variables; corrected to the actual linear benchmark (2x + y with x - y >= i-1, matching benchmark_linopy.py). - prerequisites.rst: add SCIP, give MOSEK a description, drop the dangling "-" after MindOpt, remove the outdated HiGHS-platforms claim, and clarify what the [solvers] extra actually pulls in. - conf.py + index.rst: bump copyright to 2026 and fix the "contnuous" typo on the landing page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: reorganize toctree into basic→advanced sections and rewrite user-guide landing page Split the previously flat 16-item User Guide bag into focused sections so users move from install to advanced features in a clear order: Getting Started → User Guide (core building blocks) → Advanced Features → Tutorials → Solving → Troubleshooting → Benchmarking → Reference Rewrite user-guide.rst from a one-paragraph stub into a roadmap landing page: it groups the core notebooks (variables, expressions, constraints, coordinate alignment, manipulating models) into a recommended reading order and points outward to advanced topics, tutorials, remote/GPU solving, and troubleshooting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: fix broken admonitions in notebooks and configure intersphinx Three RST admonitions in notebook markdown cells had a blank line between the directive and a tab-indented body. CommonMark then ate the indented block as a code block, so nbsphinx saw an empty directive ("Content block expected for the …" build errors). Fix by removing the blank line and using a 3-space indent — the convention already used by the working admonitions in the same notebooks (e.g. creating-variables cell `..note::\n Since we did not …`). - creating-expressions.ipynb cell 13: restored `.. important::` on coordinate-determination semantics. - creating-expressions.ipynb cell 17: restored `.. tip::` pointing at `.add/.sub/.mul/.div` with the `join` parameter and the coordinate-alignment guide. - creating-variables.ipynb cell 42: re-added a corrected `.. note::` on `coords=` being ignored when supplied alongside pandas objects. Dropped the stale "New in version 0.3.6" framing and the broken "is ignored is passed" wording from the original. conf.py: configure intersphinx_mapping for python, numpy, pandas, xarray, scipy, and dask. The intersphinx extension was already loaded but had no mapping, so cross-references like :class:`xarray.DataArray` or :func:`numpy.ndarray` were silently unresolved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: downgrade coordinate-determination admonition from important to note The coordinate-determination behaviour is regular alignment semantics, not a sharp pitfall — note is the right level. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Revert "docs: downgrade coordinate-determination admonition from important to note" This reverts commit e44cbd3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: empty commit to retrigger CI Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: rename toctree captions and reorder Examples below Solving - Benchmarking → Comparisons. The two children (performance and syntax) both compare linopy with JuMP and Pyomo, not "benchmark" in the regression-tracking sense. - benchmark.rst H1: Benchmarks → Performance comparison, so the page title matches the section framing (syntax.rst was already "Syntax comparison"). - Tutorials → Examples. The contents are end-to-end worked problems and a migration guide; "Tutorials" overloads with the rest of the docs (every notebook is tutorial-style). - Move Examples below Solving so the section flow is Getting Started → User Guide → Advanced Features → Solving → Examples → Troubleshooting → Comparisons → Reference. The user now knows how to both build and run a model before being handed a full worked problem. Update the user-guide.rst cross-reference accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: move Examples directly under User Guide Sit Examples right after the core building blocks so users move from "I learned the mechanics" to "show me a complete worked problem" before tackling Advanced Features and Solving. Final sidebar order: Getting Started → User Guide → Examples → Advanced Features → Solving → Troubleshooting → Comparisons → Reference Reorder the corresponding bullets in user-guide.rst so the prose matches the sidebar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: bridge Getting Started → User Guide and rename landing page H1 Three small fixes that tighten the boundary between the two top sections without changing what each one covers: - Append a "Where to next" cell to create-a-model-with-coordinates, pointing into the five User Guide notebooks. The coordinates notebook is deliberately a shallow tour, so the handoff is now explicit rather than implied by toctree order. - Rewrite the user-guide.rst opening to acknowledge what the reader just did in Getting Started, framing the User Guide as the depth pass on the same surface. - Rename the user-guide.rst H1 from "User Guide" to "Overview" so the sidebar entry under the "User Guide" caption no longer duplicates the caption name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: fix malformed first cell of solve-on-remote notebook Cell 0 had two lines that contained literal "\n" sequences as text (plus a stray trailing double-quote), so the markdown rendered as one long line and Sphinx emitted four warnings about "SSH:nbsphinx-math" file-not-found and inline interpreted text. Rewrite the cell with proper newline-separated lines and turn the inline reference to solve-on-oetc.ipynb into a :doc: cross-reference. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: use markdown links for cross-refs in notebook markdown cells :doc: only resolves inside RST contexts (rst files and the body of RST directives like .. tip::). In plain markdown cells nbsphinx needs markdown links to the .ipynb files, which it then rewrites to the rendered .html targets. - create-a-model-with-coordinates: the new "Where to next" cell now uses [text](other.ipynb) for the five User Guide notebooks. - solve-on-remote: the inline :doc:`solve-on-oetc` introduced in the previous fix is now a markdown link too. Verified the build rewrites all five links to .html targets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: collapse "Where to next" to a single pointer at the User Guide overview Listing the five User Guide notebooks at the end of the coordinates notebook duplicated the bullet list the overview page already maintains. Single forward pointer keeps the User Guide overview as the source of truth and pushes the reader through the deliberate intro on that page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: restructure api.rst — task-oriented top, classes under the hood, advanced bottom Replace the flat alphabetic per-class dump with a layered structure that puts the surface 90% of users reach for at the top, the supporting classes in the middle, and the genuinely rare-use surface at the bottom. Task-oriented top sections (Model methods grouped by what the user is doing): Creating a model Inspecting a model Modifying a model Solving Post-solve access (Model post-solve accessors + status enums) Diagnostics IO Top-level helpers (align, options) Classes under the hood: Variable / Variables / LinearExpression / Constraint / Constraints / Objective / Piecewise Each gets a small thematic split inside (Attributes / Operations / Conversion / Post-solve / etc.) rather than an alphabetic dump. Advanced section at the bottom for surface that most users will not reach for: QuadraticExpression CSRConstraint Bulk variable operations (Variables.fix/unfix/relax/unrelax) Auto-reformulation (Model.reformulate_sos_constraints) Remote solving (RemoteHandler) Warnings (EvolvingAPIWarning, PerformanceWarning) Curated to drop internal escape hatches and helpers: - Model.to_gurobipy/to_highspy/to_mosek/to_cupdlpx (power-user escape hatches) - Model.linexpr (arithmetic on Variables is the natural path) - Constraints.format_labels, .coefficientrange (internal diagnostics) - LinearExpression.from_rule, .from_constant (niche constructors) - Constraint.freeze / .mutable, CSRConstraint.freeze / .mutable (CSR backend plumbing) - ScalarVariable, ScalarLinearExpression (internal types) - solvers.PIPS (stub that raises NotImplementedError) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: add docstrings for properties surfaced in api.rst autosummary tables The new api.rst structure exposed 14 properties whose autosummary table cell was blank because they had no docstring. Add a single-line description to each: - Model.is_linear / is_quadratic / type - BaseExpression.vars / coeffs / const (fixes both LinearExpression and QuadraticExpression entries) - Objective.is_linear / is_quadratic - PiecewiseFormulation.method / convexity (added as inline attribute docstrings on the dataclass fields) - OptionSettings class docstring (so the top-level `options` instance picks up a description) PiecewiseFormulation: also dropped the duplicated literal value lists from the class-level Attributes block and from the new attribute docstrings, referencing the PWL_METHOD / PWL_CONVEXITY type aliases instead so there is a single source of truth for the allowed values. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: surface PWL_METHOD / PWL_CONVEXITY type aliases in api.rst The :data:`PWL_METHOD` and :data:`PWL_CONVEXITY` references added in the previous commit rendered as plain <code> rather than as hyperlinks: their docs target didn't exist yet, and the role was being looked up in the wrong module. - Add docstrings to PWL_METHOD, PWL_METHODS, PWL_CONVEXITY, and PWL_CONVEXITIES in linopy/constants.py. - List all four in api.rst under the Piecewise subsection so autosummary generates dedicated pages. - Switch the cross-references in PiecewiseFormulation.method / .convexity and in constants.py to the fully-qualified form (:data:`~linopy.constants.PWL_METHOD`) so they resolve from whichever module they're rendered in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: dissolve the api.rst Advanced umbrella; each item gets a natural home The Advanced section was a subjective bucket. Each entry now sits where it belongs: - QuadraticExpression moves into Classes under the hood alongside LinearExpression — it's a class with its own surface, not a power-user appendix. - CSRConstraint moves into Classes under the hood next to Constraint — alternative storage backend, but a regular documented class. - Variables.fix / unfix / relax / unrelax return as a Bulk modify subgroup under the Variables container. - Model.reformulate_sos_constraints joins Modifying a model — it transforms the model in place. - Remote solving and Warnings become small top-level sections of their own at the end of the page rather than nested under Advanced. Preamble updated to drop the now-stale "Advanced section at the bottom" reference and to signal the actual top-level structure (task-oriented top, supporting classes below). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: keep page TOC depth 2; expand right-side TOC to L2 site-wide Inline ``.. contents::`` directive on api.rst stays at depth 2 — the page-top TOC is the executive summary, not a complete map. For navigation into a section, bump sphinx-book-theme's ``show_toc_level`` to 2 in ``html_theme_options``. The right-side "On this page" panel now shows H3 entries by default rather than only expanding the section the user has scrolled into. Applies site-wide; the other pages have shallow H3 structures so this is a usability win across the board. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: rename "Classes and types" → "Other classes and types" Makes the structural role explicit: these are the classes and types not already covered by the task-oriented top sections. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: standardise H3 subgroup vocabulary across api.rst class sections Align the subgroup names that appear in multiple class sections so a reader who learns the scheme once can scan across classes. Six shared labels emerge — Structure, Construction, Modification, Operations, Conversion, Post-solve access — used wherever they fit. - Variable: "Modifying state" → "Modification" (match Variables, Constraints). - LinearExpression: "Building blocks" → "Structure" (match Constraint, QuadraticExpression below); "Manipulation" → "Operations" (match Variable). - QuadraticExpression: replace the flat list with the same subgroup shape as LinearExpression — Structure, Conversion, Post-solve access. - CSRConstraint: replace the flat list with the same subgroup shape as Constraint — Structure, Post-solve access, Conversion. Also move the solver-status enums out of Post-solve access into their own subsection (Solver status and result types) under Other classes and types: SolverStatus / TerminationCondition / Status / Solution / Result are types you compare Model.status against, not accessors. Model keeps its own task vocabulary (Building / Inspecting / Modifying / Solving / Diagnostics / IO / etc.) because it's structurally different from the data-type classes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: revert show_toc_level=2 in book-theme options Roll back to the default (1). The right-side page TOC will only expand the section the reader has scrolled into, not all H3 entries across the page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: restructure api.rst as Model-first and tighten the curated surface Two changes in one pass: (1) Make api.rst per-class throughout. Drop the artificial split between task-oriented top sections and "Other classes and types". Model becomes the first H2 (with its task labels — Building / Inspecting / Modifying / Solving / Post-solve / Diagnostics / IO — as H3 subsections), and every supporting class becomes a sibling H2 of Model. Preamble rewritten to frame the page: Model is the entry point; supporting classes document the types reached via ``model.<attr>`` accessors. (2) Tighten the surface further. Drop entries that are internal, implicit-by-arithmetic, or trivial: - Variables.add / .remove, Constraints.add / .remove — internal mechanisms used by model.add_*; users never call them. - Variable.sanitize, Constraints.sanitize_missings — internal cleanup helpers in the solver pipeline. - LinearExpression.to_constraint, QuadraticExpression.to_constraint — almost always implicit via <=, >=, == on expressions. - LinearExpression.to_quadexpr — niche conversion only meaningful if you're already deep in quadratic forms. - Model.get_problem_file / .get_solution_file — debugging-only temp-file accessors. - solvers.Solver (abstract base, never instantiated) and solvers.quadratic_solvers (trivial list). With Solver and quadratic_solvers removed, the "Solver interface" section only contained available_solvers, so collapse it into a single "Solvers" section with the implementation classes. Also reordered the H3 subgroups inside each class so the most-used entries appear first. Post-solve access leads on the expression / constraint classes (the .solution and .dual readers are typically what a reader is looking for); Aggregate access + Bulk modify lead on the Variables container. Top-level helpers moved to the bottom as "Utilities" (align, options) — they're stragglers, not entry points. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: move piecewise construction helpers into the Piecewise section The piecewise construction helpers (breakpoints, segments, Slopes) were previously listed under Model > Building a model because they are used alongside Model.add_piecewise_formulation. Move them into the Piecewise section instead — they live in linopy.piecewise and a reader looking for "how do I build a piecewise formulation" expects everything piecewise in one place. Split the Piecewise section into four small subsections (Construction helpers / PiecewiseFormulation / Low-level helper / Type aliases) so the helper functions, the return type, the standalone tangent_lines, and the PWL_METHOD / PWL_CONVEXITY type aliases all sit under clearly labelled groups instead of one mixed flat list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: harmonise Variables container labels with Variable; fix preamble cross-refs - Rename Variables container subgroups so they match Variable's vocabulary where the role is the same: "Aggregate access" → "Attributes" "Bulk modify" → "Modification" (the "Bulk" qualifier is clear from context — the methods live on the container) "Inventory by type" → "Inventory" (matches Constraints' equivalent subsection) - Drop the redundant prose note "Container-wide analogues of Variable.fix, etc." — same reason; clear from context. Also fix the preamble cross-references. The autosummary entries register the documented entities under their full module paths (linopy.model.Model, linopy.variables.Variable, …), so the bare ``:class:`Model``` refs were rendering as plain styled code rather than hyperlinks. Use the qualified ``~linopy.<module>.<name>`` form for class refs (display stays "Model" / "Variable" / "Constraint" / "Objective") and the ``Target <full.path>`` form for methods and attributes so the link text reads "Model.add_variables" etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: api reference link --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
What was wrong
The piecewise documentation had grown organically and accumulated several precision, structural, and pedagogical problems.
Correctness bugs
_add_disjunctiveactually creates continuouslambda_var, binary segment selectors, and an SOS2 constraint — three kinds, not two.x_0 ≤ x ≤ x_n, implicitly assuming ascending breakpoints._lp_eligibilityaccepts strict monotonicity in either direction; the math should bex_min ≤ x ≤ x_max.(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.active=...(Advanced Features) is for. The example duplicated a pattern that has a proper API and taught a bad habit..method == "sos2"but a separate column) was confusing.*_link,*_output_link) while a different part of the page said names "are an implementation detail and may evolve".fuel ≤ f(power)with amaximise -fuelobjective. The artificial objective hid the fact that bounding fuel from above admitsfuel = 0, which makes no physical sense.Structural problems
tangent_lineswas a useful standalone helper buried in a paragraph inside the LP method section.sign, curvature requirements).Tutorial-execution noise
m.solve()printed the HiGHS banner and the once-per-sessionEvolvingAPIWarning, drowning the actual lesson.What changed
doc/piecewise-linear-constraints.rstBreakpoint Constructionahead ofPer-tuple sign, split the latter into four focused subsections, folded the duplicate "Breakpoint inputs" overview into it, promotedtangent_linesto its own subsection under LP, and shortened the up-front terminology block.active=0warning moved intoActive parameter.x_min ≤ x ≤ x_max, referenced:ref:sos-reformulation`` for the Big-M fallback, generalised "Choice of bounded tuple" to both signs, and explained whyactiveisn't supported with `method="lp"`.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 toactive=...for on/off.examples/piecewise-linear-constraints.ipynb[30, 75, 150]makes every timestep single-pump-infeasible. Output is a flat 4-column DataFrame.fuel ≥ f(power)on §1's curve.examples/piecewise-inequality-bounds.ipynbfuel ≥ 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.Both tutorials
m.solve(...)calls now passsolver_name="highs",reformulate_sos="auto", andoutput_flag=False.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:
tangent_linespromoted to its own subsection under LP (discoverability — the function existed before but was buried).Test plan
sphinx-buildofdoc/renders without warnings.:ref:sos-reformulation`` link resolves correctly.🤖 Generated with Claude Code