test: a program that leaves a side of a bound open builds with that side unbounded in both engines - #1737
Merged
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
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>
…h that side unbounded math-spec's program is to state an open side as None rather than as an infinite constant (energy-models/mathspec#689), leaving what stands for it in a solve to the engine. Both lanes now read None as the infinity on that side: the linopy lane hands linopy -inf or inf, and the relational compiler writes it into the lb or ub column. A bound the program states is read as before, so the current pin is served unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aGN1gzy2vbqGchcQ8TbfV
FBumann
force-pushed
the
fix/open-bound-is-none
branch
from
September 25, 2026 17:07
203f8ba to
233cd36
Compare
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.
Both lanes read a bound the program leaves open (
None) as the infinity on that side. energy-models/mathspec#689 makes a program say an open side isNone, so the engine now chooses what stands for it in a solve.builder._boundhands linopy-inforinf, and the gap check reads only the sides that are stated.boundswrites the infinity into thelborubcolumn.alpha.122is served unchanged. A program built by hand exercises the new case today.Tests, gates, what follows
Tests.
test_a_side_the_program_leaves_open_builds_as_linopy_s_open_bound(tests/test_linopy_lane.py) andtest_a_side_the_program_leaves_open_is_an_infinite_column(tests/test_compiler.py) each build aVariableDeclarationwithlower=None, upper=None. Both failed onmainbefore the change:parameters_ofwalkedNone;unsupported node NoneType in bounds.Both pass after it. Deleting the
Nonebranch in either reader brings its test back to that failure.Gates run, in a uv environment with the
linopyextra, becausepixi installcannot fetch the conda-to-PyPI mapping in this sandbox:ruff check .passes, andruff format --check .passes.pyrefly check: 0 errors.pytest -n auto: 3799 passed, 10 failed. The 10 are thegurobiandxpresscases oftest_diagnostics.pyandtest_relational.py. They fail the same way on unchanged source, becausegurobipyandxpressare not installed here.Not run:
pixi run checkas such, the sweep, andtest-bench.What follows: the math-spec pin bump, once a release carries #689. With it every YAML-built program reaches these branches, and the two tests above stop being the only ones that do. The bump is
build, following the pin.Generated by Claude Code