Skip to content

test: a program that leaves a side of a bound open builds with that side unbounded in both engines - #1737

Merged
FBumann merged 1 commit into
mainfrom
fix/open-bound-is-none
Sep 25, 2026
Merged

FBumann merged 1 commit into
mainfrom
fix/open-bound-is-none

Conversation

@FBumann

@FBumann FBumann commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Prompt: Shouldn't the engine (specsolve) handle this? We can drop inf fully…? Or why do we need to keep the constant inf in program?

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 is None, so the engine now chooses what stands for it in a solve.

  • linopy lane: builder._bound hands linopy -inf or inf, and the gap check reads only the sides that are stated.
  • relational compiler: bounds writes the infinity into the lb or ub column.
  • Current pin: a bound the program states is read as before, so math-spec alpha.122 is 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) and test_a_side_the_program_leaves_open_is_an_infinite_column (tests/test_compiler.py) each build a VariableDeclaration with lower=None, upper=None. Both failed on main before the change:

  • linopy lane: parameters_of walked None;
  • compiler: unsupported node NoneType in bounds.

Both pass after it. Deleting the None branch in either reader brings its test back to that failure.

Gates run, in a uv environment with the linopy extra, because pixi install cannot fetch the conda-to-PyPI mapping in this sandbox:

  • ruff check . passes, and ruff format --check . passes.
  • pyrefly check: 0 errors.
  • pytest -n auto: 3799 passed, 10 failed. The 10 are the gurobi and xpress cases of test_diagnostics.py and test_relational.py. They fail the same way on unchanged source, because gurobipy and xpress are not installed here.

Not run: pixi run check as such, the sweep, and test-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

@codspeed

codspeed Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 24 untouched benchmarks
⏩ 82 skipped benchmarks1


Comparing fix/open-bound-is-none (233cd36) with main (8c783ca)2

Open in CodSpeed

Footnotes

  1. 82 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

  2. No successful run was found on main (68234d2) during the generation of this report, so 8c783ca was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@FBumann FBumann mentioned this pull request Sep 25, 2026
33 tasks
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
FBumann force-pushed the fix/open-bound-is-none branch from 203f8ba to 233cd36 Compare September 25, 2026 17:07
@read-the-docs-community

Copy link
Copy Markdown

@FBumann FBumann changed the title feat(engine): a program that leaves a side of a bound open builds with that side unbounded test: a program that leaves a side of a bound open builds with that side unbounded in both engines Sep 25, 2026
@FBumann
FBumann merged commit e78c1a3 into main Sep 25, 2026
15 of 16 checks passed
@FBumann
FBumann deleted the fix/open-bound-is-none branch September 25, 2026 19:01
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