Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
Documentation build overview
87 files changed ·
|
FBumann
added a commit
that referenced
this pull request
Sep 25, 2026
> **Prompt:** And prepare a todo list for me the same way. Also start with 0.1.0rc1 > [!NOTE] > The following content was generated by AI. This is the release PR for `0.1.0rc1`, the release candidate for the first PyPI upload. It is stacked on #1751. It stays a draft until PyPI can take the package. One direct reference is left, the `linopy` extra, and #1755 removes it (step 1). ## The first PyPI release, step by step ### 1. What blocks the upload PyPI refuses a distribution that names a git URL in its metadata. - [x] mathspec is on PyPI, and `pyproject.toml` depends on `mathspec>=0.1.0` (#1754). The parity job reads the corpus tag from that floor. - [x] Merge #1755. It takes linopy out of the package and keeps it as the test oracle: the `linopy @ git+…@master` reference moves to the `dev` group, which PyPI never sees. It also deletes `allow-direct-references`, so hatchling refuses a direct reference at build time, before PyPI would. ### 2. One-time setup **PyPI** - [x] On pypi.org, go to Account → Publishing → "Add a new pending publisher" → GitHub, and fill in: - PyPI project name: `specsolve` - Owner: `fluxopt` - Repository name: `specsolve` - Workflow name: `release.yaml` - Environment name: `pypi` **GitHub**, under fluxopt/specsolve Settings - [x] Environments: create or edit `pypi`. Under "Deployment branches and tags", choose "Selected branches and tags" and add `main`. Add the people who may approve a release as required reviewers. - [x] After #1751 merges: Rules → the `main` ruleset. Require `ci`, `Conventional commit subject` and `Changelog line`. A required check must run on `main` once before you can require it. - [x] Issues → Labels: create `no changelog`. **Clean-up that the release does not need** - [ ] Delete the repository variables `AUTO_RELEASE` and `PUBLISH_TO_PYPI` if they exist. Turn off "Allow auto-merge" unless you want it for other PRs. - [x] Remove `specsolve` from the release app's (`fluxopt-release-bot`) repository access. If no other repository uses the app, uninstall it and delete the secrets `APP_CLIENT_ID` and `APP_PRIVATE_KEY`. - [x] Delete the branches `release-please--branches--main--components--farkas` and `release-please--branches--main--components--linopy-yaml`. - [x] Close #1675 and #1676. #1751 replaces both. - [ ] Open PRs owe a changelog line after #1751 merges: #1737, #1527, #1516 and #1541. #1541 (drop the linopy lane for linopy's own `from_spec`) conflicts with #1755; close it or rebase it. ### 3. Before this PR merges: what the PyPI page shows - [x] `[project.urls]` has only `repository`. Add `Documentation`, `Issues` and `Changelog`, as energy-models/mathspec#707 did. - [x] The README has two relative links, `CONTRIBUTING.md` and `docs/about/prior-art.md`. On PyPI they do not resolve. Make them absolute. - [x] `src/specsolve/relational/parquet.py` has two error messages that say "while the package is on 0.0.1aN". `tests/test_archive.py` asserts the same. Reword them to "before 1.0". - [x] RELEASING.md, "Relaxed while in early development": it says to tighten CI before 0.1.0, starting with a Python matrix behind the gate job. Do that, or change the sentence. ### 4. This PR - [x] Merge #1751, then #1755. Then this PR's base must be `main`. GitHub retargets it when #1751's branch is deleted; if the branch stays, retarget by hand. - [x] Mark the PR ready. - [x] Set the date in `## 0.1.0rc1 (2026-09-25)` to the day you merge, if that is not the 25th. - [x] Edit the notes under the heading. Add the lines of the PRs merged since. The text becomes the GitHub release notes, as written. - [x] Check that `ci` is green. Its step "Check the changelog headings" prints `releases 0.1.0rc1 on merge`. - [x] Review, then squash-merge. ### 5. After the merge - [ ] Actions → **Release**, the run for the merge commit: - [ ] `Tag the version the changelog names` created the tag `v0.1.0rc1` and the GitHub release `0.1.0rc1`, marked as a pre-release. - [ ] `Build` passed, and its check printed `specsolve-0.1.0rc1`. - [ ] `Publish to PyPI` waits for approval: "Review deployments" → `pypi` → Approve. - [ ] https://pypi.org/project/specsolve/0.1.0rc1/ exists. The README renders, and the project links work. - [ ] Smoke test in a fresh environment: ```bash python -m venv /tmp/ss && /tmp/ss/bin/pip install specsolve==0.1.0rc1 /tmp/ss/bin/python -c "import specsolve, mathspec; print(specsolve.__version__, mathspec.__version__)" # 0.1.0rc1 0.1.0 /tmp/ss/bin/pip install specsolve # finds no stable version until 0.1.0 exists ``` - [ ] If a job failed, fix the cause, then use "Re-run failed jobs" on that run. If `0.1.0rc1` reached PyPI broken, release `0.1.0rc2`. ### 6. Then 0.1.0 - [ ] Open the next release PR, titled `chore: release 0.1.0`. It renames the next `## Upcoming version` to `## 0.1.0 (date)`, with notes that say this is the first release on PyPI. Merge it and approve the upload as above. - [ ] Smoke test `pip install specsolve` without a version. It should install `0.1.0`. ### 7. Follow-ups - [ ] 83 links in the tree point at `math-spec.readthedocs.io`. The redirect keeps them working. Point them at `mathspec.readthedocs.io`. - [ ] Optional: a conda-forge recipe. - [ ] Patch releases to an older version line. No path exists yet; mathspec tracks the same gap in energy-models/mathspec#710. <details><summary>What this PR changes, what merging does, gates</summary> **The changes:** - `CHANGELOG.md`: an empty `## Upcoming version`, then `## 0.1.0rc1 (2026-09-25)`, with a paragraph and #1748's line. - `AGENTS.md`: "The project is `0.0.1aN` and holds no compatibility promise" becomes "The project holds no compatibility promise before 1.0". One sentence is added: a release that breaks a model file or an import raises the minor version, and its notes name the break. - `CONTRIBUTING.md`: the same rule, in the same words. **Why a release candidate:** it goes through the same tag, GitHub release, build, approval and trusted-publishing upload as `0.1.0`. The only addition is `--prerelease` on the GitHub release. pip skips it unless someone asks for it, so a broken first upload reaches no user. The version sorts after `0.0.1a358` and before `0.1.0`. **What merging does:** `release.yaml` finds `0.1.0rc1` with no tag. It creates `v0.1.0rc1` and a GitHub pre-release, builds from the tag, checks that the wheel and the sdist are `0.1.0rc1`, and waits for approval on `pypi` before the upload. **Gates:** `python -m tools.changelog check` prints "releases 0.1.0rc1 on merge", and `notes 0.1.0rc1` prints the section above. The suite on #1751's commit is in that PR. This commit changes only prose and the changelog. **Not verified:** the name `specsolve` on PyPI. The PyPI API cannot be reached from this container, so check that the name is free when you add the pending publisher. The type is `chore`, which owes no changelog line. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016Pv7LzSgzt7Yn2K3ioyJXw Co-authored-by: Claude <noreply@anthropic.com>
… traced as a polygon `sps.project(spec, sources, x=, y=, at=, binaries=)` traces the region two named quantities can reach and returns a `Region`: vertices, hull, pieces, edges and optimum as tidy frames, and `plot()` as a plotly figure (the `[plot]` extra). `PolarsEngine.binding` and `PolarsEngine.expression_dims` read what a solve sits on and a named expression's dims off the built model. `docs/region.md` walks it on a three-unit CHP plant. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KVqZodsEAc7AMYGDyu73b5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011BopadYhLNiGHaa37K5pC1
FBumann
force-pushed
the
claude/feasible-region-analysis-26xcox
branch
from
September 25, 2026 17:22
5e07749 to
cda2fd8
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011BopadYhLNiGHaa37K5pC1
Documentation build overview
4 files changed+ region/index.html± about/architecture/index.html± about/changelog/index.html± reference/api/index.html |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
The following content was generated by AI.
sps.project(spec, sources, x='heat', y='power', at={'t': 5})traces the region two named quantities can reach. It returns aRegionof tidy frames and a plotlyplot().binaries='each'traces every binary combination as its own piece.docs/region.mdwalks it on a CHP plant.vertices(piece, vertex, x, y)0pieces(piece, variable, dims…, value)edges(piece, edge, kind, name, dims…, side)atnamesoptimum(piece, x, y)hull(vertex, x, y)Every frame keeps its schema whether the binaries were free or traced apart.
label(piece)spells a piece for a legend, such asrunning[chp]=1, running[boiler]=1, running[peaker]=0.How it works
The probe.
projectadds ordinary declarations to the caller's spec: three scalar weights, two named expressions, and a one-hot selection parameter per axis whereatfixes a coordinate. The objective becomesx_direction * x_axis + y_direction * y_axis + objective_weight * (the file's own), maximised. Four compass directions enclose the region. Then each hull edge is probed along its outward normal: a solve past the edge is a new vertex, a solve on it settles the edge. The trace ends when every edge is settled. All solves run on the fast path withkeep='progress': one load, however many vertices. The optimum is one more solve with the directions at zero and the file's objective weighted±1. A name the spec already declares is refused.Edges.
PolarsEngine.binding(result, tolerance)reads which bounds and rows a solve sits on, off the built model'shandoff.colsandhandoff.rows. An edge is bound by what is tight at both its ends. Rows at coordinates other than thoseatnames are left out, and so are binaries.Binaries. A binary makes the region a union of polygons, and a directional solve finds only the hull.
binaries='each'adds two rows per binary,b >= b_at_leastandb <= b_at_most, with both sides as data. A combination is then a right-hand side pushed onto the loaded solver, soloadsstays at one. The pinned columns come from the first free solve's primal, so a column awhereremoved is not pinned. An infeasible combination is left out. Above 10 columns (1024 traces) it refuses and namesatas the way to ask about fewer.integervariables are never pinned.The picture.
plot()needs the new[plot]extra (plotly). Each piece is a filled polygon in its own colour under its label. Hovering a vertex reads its coordinates. Hovering the middle of an edge reads the rows ofedges, such ascapacity[0, chp] at its upper. The optimum is a diamond naming its piece.plot(figure, name='hour 1')draws onto an existing figure with the next colours.The page.
docs/region.mdruns at build time like the other tutorial pages. It traces a gas well feeding a CHP, a boiler and a peaker, each with a minimum load, over four hours. It reads every edge offedges, stacks every hour on one figure, traces the five feasible commitment states of eight, and breaks the model twice on purpose: no cap is named at the first direction, and a boiler that cannot idle namesminimum_load. A smallshow(figure)helper printsfigure.to_html(include_plotlyjs='cdn')intohtml="true"blocks.tests/test_tutorials.pyruns the page and holds each claim.Rebase onto main 68234d2, and gates
The branch was 88 commits behind, across the
lpspec→specsolverename. Its own changes are squashed into one commit and ported by hand:projection.pymoved tosrc/specsolve/.lpsis nowsps,math_specis nowmathspec, and the name-clash check reads the spec'srelationssection.bindingandexpression_dimsfollow main's internals:model.handoff,program.expressions,PolarsCompiler(Scope(...)),SpecsolveError.src/admits noAny(refactor: only the linopy lane and the solver sinks say Any #1699), so 19 annotations inprojection.pyuse main'sBuildable,Source,LabelandMapping[str, object]. A_section()helper narrows the spec dict._shapeimports plotly itself.sum(gen, by=gen_bus, over=generator, into=bus), because mathspec refuses the short form.docs/region.ipynbis nowdocs/region.md. Its tests moved fromtest_notebook.pyintotests/test_tutorials.pywith their assertions unchanged.::: specsolve.projectand::: specsolve.Region. The list of names the probe adds moved intoproject's docstring.uv.lockwas regenerated withuv lock. It adds plotly and narwhals only. The dev and docs pixi features take theplotextra.Gates, in a uv venv with
.[plot]and the dev and docs groups (pixi is unavailable in the sandbox):ruff check .andruff format --check .: clean.pyrefly check: 0 errors.pytest -n 8: 3929 passed, 334 skipped, 1 xfailed, 10 failed. The 10 are gurobi and xpress cases that fail withModuleNotFoundErrorin the sandbox; they fail the same way on main.test_projection.py: 44 passed.test_tutorials.py: 15 passed.zensical build --strict: clean; the region page holds 4 plotly figures.Not checked: the figures in a browser, whether their inline scripts run under Zensical's
navigation.instant, and that plotly.js loads from the CDN once per figure (four times on the page).Departures and what was not done
projection.pyreadsmodel._programandmodel._engine: to learn a named expression's dims after the build, and what binds at a solve. The language exports nodims_of, and a walker over lowered nodes would be a second home for the dim algebra.atis shared by both axes), rays for an unbounded region (it raises and names the direction), pinningintegervariables, a matplotlib figure, committed figures.atnaming no declared dimension, a probe name the spec declares,aton a scalar quantity,atnaming a dim the quantity does not carry, an infeasible model, an unbounded direction, the solve cap.🤖 Generated with Claude Code
https://claude.ai/code/session_011BopadYhLNiGHaa37K5pC1