Release 3.0.0: merge develop → main - #283
Merged
Merged
Conversation
Update the parameter-homotopy, moving-slice, and user-product-of-linears tutorials to call the first-class nag_algorithm.HomotopySolver (the continuation primitive) instead of the user_homotopy forwarder, and reference it consistently with :func:. Identical signature, clearer name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…stalls The wheel ships the bertini2 CLI at bertini/_bin/ via a SKBUILD-gated install rule, but a from-source EDITABLE install (`pip install -e .`) does not run install(), so the console-script shim had no binary to hand off to and the CLI smoke test skipped. Add a non-wheel POST_BUILD step on bertini2_exe that copies the freshly-built CLI into the source-tree python/bertini/_bin/bertini2 (where an editable install resolves the package), guarded on the package dir existing. Now every build path delivers the `bertini2` command: wheel (`pip install .`), editable (`pip install -e .`), and plain in-tree builds. python/bertini/_bin/ is gitignored. CLI smoke tests now run + pass locally (2 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tag the bertini2_exe install rules COMPONENT cli, so a user who wants only the solver -- no Python, no library/headers -- can `cmake --install build --component cli` and get just bin/bertini2 (the exe statically links libbertini2.a, so it is self-contained re: our code; it still needs Boost/GMP/MPFR/MPC/MPI like any from-source build). A plain `cmake --install` still installs everything. Verified: --component cli installs only bin/bertini2 (runs); the default install still ships the exe + library + headers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document that the bertini2 CLI is pure C++ (no Python dependency) and how to get it from each build path -- wheel (pip install bertini2), from-source pip (install . / -e .), the Python-free CLI-only install (cmake --install --component cli), and the full C++ dev install -- plus the shared-library expectations and threads-only/MPI note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up on the over-determined filter, for regeneration-cascade use. - SolutionMetaData gains `is_nonsolution`: a finite, successful endpoint that is NOT a solution of the target -- the junk squaring up an over-determined system introduces. The filter now sets THIS (orthogonal to is_finite) instead of is_finite=false, so nonsolutions stay geometrically finite and no longer masquerade as at-infinity. The finite / real / singular accessors exclude them; a new Nonsolutions() / nonsolutions() surfaces them; SolveReport gains num_nonsolutions. - New `solutions(**flags)` getter on both ZeroDimSolver and HomotopySolver: finite genuine solutions by default, with category keywords -- singular / nonsingular, real / nonreal (a finite solution is returned iff its conditioning class AND its realness class are enabled), infinite=True to add at-infinity endpoints, nonsolution=True to add the junk. all_solutions() stays as the raw per-path list. is_nonsolution is bound, added to to_dataframe columns, and documented in the ZeroDimSolver tutorial. Tests: C++ over-determined test asserts the flag / Nonsolutions / report count; Python feasibility_test covers solutions() filtering by realness and nonsolution opt-in. C++ suites pass (OMP=1); Python suite 547 passed / 1 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…"junk" -> nonsolutions The over-determined test asserted an exact nonsolution count (Nonsolutions == all - 2), which assumes every extra root of the squared system stays finite. How many of the squaring's extra roots land finite vs. diverge to infinity is RNG/gamma-dependent, so that passed locally but failed the Ubuntu C++ job. Assert the ROBUST partition instead: FiniteSolutions + Nonsolutions + InfiniteSolutions == all endpoints (every endpoint is exactly one of genuine finite solution / finite nonsolution / at infinity), plus solutions() == 2 (the genuine roots). Same fix in the Python feasibility test and the tutorial doctest. Also rename the user-facing "junk" wording to "nonsolutions" throughout (report line, docstrings, comments) per review. C++ suites pass (OMP=1 + threaded); Python suite 547 passed / 1 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…perating-zone test ComputeCOverK drew a FRESH random probe vector on every call, so the c/k operating-zone estimate was non-deterministic: consecutive estimates disagreed by probe noise alone, which could certify (or stall) the zone spuriously and churned an mpfr allocation each call. It now reuses a single fixed probe (new member c_over_k_probe_, generated once per precision, re-precisioned in place), so the estimate is reproducible and CheckForCOverKStabilization measures actual sample stabilization. Effect on the actual default start system (RootsOfUnity), cyclic-5 AMP, seed 1: 3.37s -> ~3.06s with the number of escalating paths dropping from 2 to ~0 (the noisy probe had been driving spurious precision escalation), plus the per-call mpfr churn is gone. Also adds CauchyConfig::num_consecutive_same_cycle_number (default 2): refuse to accept a converged Cauchy approximation until the cycle number has reported the same value that many times in a row, so a coincidental approx_error dip on an unreliable low-precision cycle count is not mistaken for convergence. Exposed in the python bindings; verified not to regress the adaptive case. Scope note: this does NOT fix the linear-product TotalDegree start system, whose Cauchy endgame still stalls near t=0 (double and adaptive) -- that is a separate, known issue for a later sprint. Earlier "TotalDegree" validation through the python facade inadvertently exercised RootsOfUnity, because ZeroDim(sys, startsystem='totaldegree') maps to the default constructor / default start system. C++ ctest green; python suite green (1 pre-existing numpy-env failure unrelated). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…homogenization + interop Adds core/test/classes/start_system_interop_test.cpp (9 cases) exercising the start-system layer at the corners, to establish sanity around the linear-product TotalDegree and its multi-group generalization MHomogeneous: - EVERY start point of TotalDegree and MHomogeneous is verified to be a root of the start system, on its patch, in double AND high multiprecision (not a sampled few). At 100 digits the patch residual is < 1e-90, confirming start points are correctly homogenized and patch-fitted across the precision range -- i.e. there is no start-point patch-normalization defect. - MHomogeneous on a single affine variable group reproduces TotalDegree's Bezout count (product of the function degrees), across several degree tuples -- MHom is a generalization of total degree, and the two must agree on one group. - The blend homotopy H = (1-t) target + gamma t start vanishes at every start point at t=1, for both TotalDegree and MHomogeneous on the same target (homotopy interoperability). - Corners: single-variable degree-d (exactly d start points), Homogenize/Dehomogenize roundtrip, and the construction guards (non-square and homogeneous-group targets must throw). These run only the start-system layer (no full ZeroDim solve), so they are fast and cannot hang. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r hang-prone solves) macOS has no timeout(1), and some solves can spin/churn indefinitely (notably the linear-product TotalDegree start system, whose Cauchy endgame stalls near t=0). This wraps any command with a wall-clock limit and SIGKILLs the whole process group on overrun (exit 124), so a hung solve can't peg a core forever. The b2-mcp server bakes the same behavior into its tool runner; this is the plain-CLI equivalent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…_time_seconds) Adds SolutionMetaData::path_time_seconds -- the wall-clock time to execute a whole path (pre-endgame tracking + endgame), stamped in ExecuteOnePath around both exit points with a monotonic steady_clock. Each path writes only its own metadata slot, so there is no cross-path race in a threaded/distributed solve; it is plumbed through FullPathResult (Pack/Store + serialize) so the manager-worker and MPI paths carry it too. Excluded from SolutionMetaData::operator== (timing is not an identity field). Exposed to Python as the read-write `path_time_seconds` attribute and added to to_dataframe()'s column set, so per-path cost shows up in the solutions dataframe alongside condition_number and max_precision_used (e.g. sort paths by time, or correlate time with precision escalation). Verified: default cyclic-5 solve stamps all 120 paths (sum ~3.0s), C++ ctest 10/10 green, python suite green (1 pre-existing numpy-env failure unrelated). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…segfaulting
Manually composing a homotopy with a structured-block start system -- e.g.
(1-t)*target + gamma*t*TotalDegree, exactly the gamma-trick in the manual-endgame tutorial --
SEGFAULTED. System::operator+= and operator*= only touched PolyBlock().Functions(), but the
linear-product TotalDegree (and MHomogeneous) keep their functions in a ProductsOfLinearsBlock, not
the PolynomialBlock. So:
* operator*= (gamma*t*td) silently multiplied an EMPTY PolynomialBlock -> a wrong result (the start
system left unscaled), no crash;
* operator+= then indexed rhs.PolyFunctions() (size 0) up to lhs's function count -> out-of-bounds
read -> segmentation fault.
Both operators now detect a structured block (HasStructuredBlocks) and, in that case, expand every
block to function-tree nodes via NaturalFunctionsAsNodes(), operate on those, and store the result as
a single PolynomialBlock (a function-tree system that evaluates and tracks correctly). The
pure-polynomial fast path is unchanged, so existing systems are unaffected.
Verified: the previously-segfaulting (1-t)*gw + t*gamma*TotalDegree now builds a correct homotopy
(all 6 start points solve H at t=1 to ~5e-11), the manual_endgame_usage doctest no longer crashes,
C++ ctest 10/10 green, python suite green (1 pre-existing numpy-env failure unrelated).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mplated names After the de-templating (the start system is no longer baked into the ZeroDim type), the bound class names dropped the start-system suffix: ZeroDimCauchyDoublePrecision (not ...TotalDegree), etc. Update the doctest assertions accordingly and note that the start system is now a construction choice, so it is not part of the class name. (The separate precision_matters doctest -- at-infinity paths counted as num_failed -- is being handled elsewhere; untouched here.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…#263) System::Function(index) dereferenced PolyBlockPtr() with no null check, so it segfaulted on a system built from a slice (a pure LinearFormsBlock, with no PolynomialBlock). Route Function() and GetNaturalFunctions() through NaturalFunctionsAsNodes() so every block type (polynomial, linear forms, products of linears, randomization, blend) is expanded to a function-tree node on demand, in sync with NumNaturalFunctions(); out-of-range now throws std::out_of_range (-> Python IndexError) instead of crashing. Add System::Slices() (bound as system.slices()) to back out the linear-form slices embedded in a system -- the inverse of Slice.as_system() -- without exposing the Block variant to Python. Tests: function_accessor_test.py covers function(i) across all five block types plus slices() round-trip and node/block eval agreement; C++ system_blocks suite gains structured-block Function()/Slices() cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The over-determined filter flags is_nonsolution (a dedicated, orthogonal metadata field) rather than is_finite=false, so nonsolutions stay geometrically finite and are surfaced by Nonsolutions() / counted in num_nonsolutions; it is load-bearing for the regeneration cascade. Also note the additive solutions(**flags) getter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
) A system built from a slice printed every function as the opaque placeholder `c.[x, y, z, 1]`, hiding the coefficients. Keep the placeholder (structure stays legible) and print the actual coefficient matrix just below it as a named-expression-style `c =` legend: 4 significant figures in the default / terse describe, full working precision under describe(verbose=True). The same applies to a randomization block's `R` matrix, which was previously gated behind verbose -- it now shows in the default describe too. PrintCoeff gains a significant-digits parameter (terse=4, verbose=current precision; the master coefficient matrix is stored far above working precision, so its full string would be thousands of digits). Each structured block truncates its terse listing after kTerseRowCap (10) rows with a "... (k more)" note so a large slice does not flood the terminal; verbose prints everything. products-of-linears and blend keep their existing structural terse output (they carry no inline coefficients to shorten). Tests: system_printing_test (Python + C++) updated for the new default output and extended with terse-vs-verbose digit-width and row-cap-truncation cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_homotopysolver Split ZeroDim into HomotopySolver (engine) + ZeroDimSolver (algorithm)
# Conflicts: # python/docs/source/tutorials/precision_models.rst
…-timing Cauchy c/k probe determinism, start-system sanity tests, per-path timing
…ystem fix: function() on slice-derived systems + system.slices() (#263)
feat: show low-precision coefficients in system describe (#264)
The `H.eval_time_derivative(...)` block in the moving-slice tutorial failed under `sphinx -b doctest`: the preceding adaptive `solver.solve()` leaves the homotopy H at double precision (16), while the multiprecision evaluation point is created at the multiprec literal default (20), so the direct eval raised "precision of input point in SetVariables (20) must match the precision of the system (16)". Match H to the evaluation point's precision before the direct eval. (Using `bertini.default_precision()` does NOT work here: the AMP solve leaves the global default at 16, which disagrees with the multiprec literal default of 20 -- a latent precision-state inconsistency worth a separate look, but out of scope for this doctest fix.) Found by running the full tutorial doctest suite locally (it is not in the per-push PR CI, only the dispatched build_docs workflow): 147 tests, now 0 failures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ecision fix(docs): moving_slice eval_time_derivative doctest precision mismatch
Foundation for fixing and locking down the C++ API docs. The docs had rotted (stale @param/@tparam, signature drift) and nothing caught it: the Doxyfile runs EXTRACT_ALL=YES with all warnings off, and the doc build only runs at publish time, never on PRs. - tools/doclint.sh: cheap Doxygen-only lint (no compile/wheel/graphviz). Pass 1 is a zero-tolerance error gate for *wrong* docs (param/signature mismatches, broken refs); Pass 2 is a monotonic ratchet on the undocumented-entity count vs tools/doc_undocumented_baseline.txt. - .github/workflows/doc_lint.yml: runs the lint on every PR with a pinned Doxygen (the ratchet count is version-specific). First foothold of a broader linting initiative. - Doxyfile: wire CITE_BIB_FILES to the shared bibliography so \cite resolves; build_docs.yml installs bibtex so citations render in the published docs. - Remove the orphaned legacy core/doc/ (stale bertini.doxy.config, its generated_documentation/, dead review .tex, dangling images_common). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drive the doc-lint error gate (tools/doclint.sh pass 1) from 115 warnings to zero. All changes are comment-only. Fixes fall into a few patterns left behind by past refactors: - Stale @param names that no longer match the signature (Predict, HermiteInterpolateAndSolve, CircleAdvanced, EnsureAtUniformPrecision, RandomMp, the linear-forms / products-of-linears blocks, Initializing, TransformToSPlane, MakeHomotopy/MakeMovingHomotopy, the System Eval/JacobianInPlace family). - @return documented on void in-place functions; dropped. - Orphaned doc blocks describing removed functions (AddFunction overloads, a stale NumRealSlots block); removed. - Malformed markup: \param[out] typo, an unterminated ``` fence (system.hpp), a stray code-span backtick closed by an apostrophe (number.hpp), <file>/<tt> parsing, an unresolved #factors_i link, \url, and missing \defgroup titles. - extern-template instantiation blocks hidden from Doxygen with \cond (they are not API and confused the member matcher). Seed tools/doc_undocumented_baseline.txt at 1821 -- the monotonic ratchet floor that Phase C will drive toward zero. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the cheap Doxygen-only doc lint a prerequisite of every job that compiles the C++ core, so a documentation error fails the run in ~1 minute and no expensive build/test job ever starts. - doc_lint.yml gains a workflow_call trigger and is invoked by build_and_test.yml as the `doc_lint` job; test_cpp_unix and test_cpp_windows (the first compilers) now `needs` it, so the wheel jobs that depend on them are transitively gated too. - Scope doc_lint.yml's standalone push/pull_request triggers to the doc-config/tooling paths that build_and_test.yml already paths-ignores (Doxyfile, doc_resources, tools/doclint.sh, the baseline, the workflow), so doc-only changes are still linted without double-running on code PRs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scaffold (no revisions yet) so an upcoming whitespace/indentation pass -- which will touch nearly every header -- can be kept in one isolated, formatting-only commit and excluded from `git blame`, instead of burying real authorship. GitHub honours this file automatically. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…doctest The doctest convention used python/docs/_doctest_build (not matched by the build/ ignore patterns, so a forgotten one shows as untracked). Ignore _doctest_build/ and switch the documented convention to python/docs/build/doctest, which is already under the gitignored build/ dir. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Reference The docs reorg (merged #72) switched html_theme to 'furo', but nothing installed it (CI's build_docs and the dev env both lacked it), so the HTML build failed with 'no theme named furo found'. Add furo to build_docs.yml. Also, per review: - Drop 'sphinx_rtd_theme' as a loaded extension (conf.py) and from the CI install -- it did nothing once furo became the theme; the release-in-sidebar comment is now theme-agnostic. - Lift the Full API Reference: api.rst previously autosummary'd a single 'bertini' entry, so the page was one table row and the real reference lived a click away at _autosummary/bertini. api.rst now shows the bertini module surface inline (automodule) plus the recursive submodule index -- one page, no extra click. HTML build verified clean with -W (warnings-as-errors, as CI runs it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he period
The module docstring's first line rendered as a de-emphasized paragraph
('bertini – Python bindings for Bertini 2.') under the automodule directive.
Formatting it as an RST section header (underline) makes it a proper <h2> on the
Full API Reference page, and the trailing period is gone.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Boost 1.90 (up from 1.87, commit 54958ef) made 'b2 install' hard-error on boost_mpi when no MPI toolset is present ('No best alternative for .../boost_mpi'), where 1.87 silently skipped it. 215fd3e fixed this on the Linux wheel build with --without-mpi but not the macOS one, so the macOS-14 wheel build started failing. bertini uses raw MPI via its own headers, not boost.mpi, so building it is pointless -- skip it, exactly as Linux does. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Boost version was hard-coded in 13 places, which is exactly why the 1.87->1.90 bump (54958ef) went wrong: the companion --without-mpi fix (215fd3e) reached the Linux b2 install but not macOS, because there was no single place to change. Now BOOST_VERSION is one env var (mirroring EIGENPY_VERSION); the underscored tarball form (boost_1_90_0) is derived in-shell ($BOOST_VERSION in run: steps, the baked ${{ env.BOOST_VERSION }} in the cibuildwheel commands) since GitHub expressions have no string replace(); the one cache path that needed the tarball name is now a version-agnostic glob. A future bump is a single, atomic edit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…the Cauchy endgame doctrine - ADR-0045 + 0047 gained dated update notes: the two-store layout + single results.json evolved into three truth stores (history/ + results/ + definitions/); results.json and RESULTS.txt are retired. The original design text stays for the record. - ADR-0048 (new): the Cauchy endgame's divergence handling — watch the extrapolated ENDPOINT (not loop samples), truncate ONLY in the operating zone, no pole-growth truncation (the acceptance gate alone cures junk-success). Records caught the cyclic-6 regression; B1 gave ground truth; refines #70. Restores cyclic-6's 156 solutions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Structured output directory: records, resume, provenance
…omotopy, two-level parallelism... (#238) This PR merges the entire `ofloveandhate/b2` `develop` line into upstream — ~70 internal PRs. It's large, so this description indexes the work by theme, links each internal PR, and lists the upstream issues it closes. Going forward I'll open PRs directly against `bertiniteam/develop` so this roll-up isn't needed again. ## Highlights - **~10× faster multiprecision** eval (tiered SLP arithmetic, allocation-free eval, CSE, in-place LU) and **5–7×** faster well-conditioned AMP solves by staying in double where safe. - **Content-addressed function trees and systems** — hash-consing/interning, symbolic Jacobian, `Seal()`. - **Parallel-by-default solving** without requiring MPI, plus MPI serialization & reproducibility fixes. - **Friendlier Python surface**: flattened namespace, solutions in user coordinates, `to_dataframe`, sympy bridge, Unicode identifiers, revamped config model. - The **`bertini2` CLI ships in the PyPI wheel** and emits Bertini-1.7-compatible solution files. - **Durable, resumable output with provenance**: every solve writes a plain-text **structured output directory** — content-addressed inputs, one results file per run, walkable provenance chains — and consults it first, so a killed run *finishes* on rerun. Content-identity digests (systems, configs, seeds) make `seed=42` mean the exact same homotopy forever. - Substantial **tracker/endgame** correctness (Cauchy security & pole-zone guards, predict/correct rewrite, adaptive-numeric-type AMP endgames). - **Executable, verified docs** (doctest), a C++ doc-lint gate, and ADRs for load-bearing decisions. ## Upstream issues closed - Closes #156 — pi computed inefficiently → ofloveandhate#21 - Closes #239 — ship the (non-MPI) CLI in the wheel → ofloveandhate#46 - Closes #253 — refresh solving-at-scale timing tables → ofloveandhate#56 - Closes #255 — observer subsystem refactor → ofloveandhate#23 - Closes #256 — Concatenate using cloned systems → ofloveandhate#26 - Closes #258 — `ZeroDim…TotalDegree` not iterable → ofloveandhate#30, #31, #33 - Closes #259 — nondeterministic GMP SIGABRT (uninitialized mpfr/mpc slot) → ofloveandhate#32 - Closes #263 — segfault `.function()` on a slice-derived system → ofloveandhate#51 - Closes #264 — printing a system converted from a generic slice → ofloveandhate#52 - Closes #51 — system generator (classic input export) → ofloveandhate#26 - Closes #74 — parser builds generic, not integer, power nodes → ofloveandhate#40, #7 - Closes #87 — centralize/seed random number generation → ofloveandhate#17 - Closes #123 — add `__str__` methods to many pybertini types → ofloveandhate#7 - Closes #128 — expose the m-hom start system to pybertini → ofloveandhate#18 - Closes #130 — tutorial on endgame usage → ofloveandhate#22 - Closes #131 — tutorial: solve a system start-to-finish → ofloveandhate#22 - Closes #133 — tutorial on user-defined homotopies → ofloveandhate#22 - Closes #148 — pybertini complexes not pickleable → ofloveandhate#21 - Closes #210 — build a System from a list of functions → ofloveandhate#3, #65 - Closes #228 — stringification produces too many parentheses → ofloveandhate#7 - Closes #232 — improve CI build time → ofloveandhate#15 - Closes #235 — endgames need a genuinely adaptive precision mode → ofloveandhate#55 - Closes #236 — make observers from Python with callbacks → ofloveandhate#23 - Closes #243 — auto-randomize-and-filter overdetermined inputs → ofloveandhate#49, #19 - Closes #247 — reparseable export (Bertini-1 classic format) → ofloveandhate#26, #34, #39 - Closes #249 — rewrite predictor/corrector (per-track probe, pure kernel) → ofloveandhate#43 - Closes #251 — non-mutating (copy-on-write) Simplify → ofloveandhate#25, #7 - Closes #71 — TotalDegree constructor wrong for a non-affine variable group → ofloveandhate#48 - Closes #95 — Reset wrapper for generic nodes (mooted by SLP-only evaluation) → ofloveandhate#6 - Closes #100 — variable-values-random-by-default (mooted by SLP-only evaluation) → ofloveandhate#25 - Closes #107 — optimize resetting of nodes in the function tree (mooted by SLP-only evaluation) → ofloveandhate#25 - Closes #116 — mark leaf core types `final` (node hierarchy de-virtualized) → ofloveandhate#6 - Closes #129 — move the start system out from under `system` → ofloveandhate#48, #49 - Closes #132 — user homotopy missing its link to start points → ofloveandhate#18, #49 - Closes #154 — precision consistency when homogenizing variables → ofloveandhate#25, #26 - Closes #198 — computational inefficiency in predict/correct FullStep → ofloveandhate#43 - Closes #199 — rename ODE predictor stages/variables → ofloveandhate#43 - Closes #260 — user-homotopy crash (duplicate parameter functions) → ofloveandhate#33 - Closes #261 — `user_homotopy()` parameters excessive → ofloveandhate#49 ## Internal PRs by theme ### Multiprecision & evaluation performance - ofloveandhate#3 — grabbag of precision & step-size improvements (folds in the CLI/MPI/pytest work of fork #4). - ofloveandhate#5 — run well-conditioned AMP paths in double precision (5–7×). - ofloveandhate#40 — tiered SLP arithmetic + allocation-free mp eval (~10×). - ofloveandhate#41 — mimalloc + GMP allocator, AMP start-at-double, cheaper norms/temporaries. - ofloveandhate#44 — AMP Criterion B uses the Newton residual, not size-proportion (cyclic-5 11s → 3.5s). - ofloveandhate#58 — stop over-reading the SLP instruction tape by one word in `Eval`. - ofloveandhate#60 — SLP CSE: product power-fold + value-numbering (30–45% faster mp eval). - ofloveandhate#71 — stateful in-place multiprecision LU solver (replaces Eigen in the tracker). <img width="2290" alt="Tiered SLP arithmetic roadmap" src="https://github.com/user-attachments/assets/bded1a18-6abb-4bb0-acb1-36307ff01f53" /> ### Function tree & content-addressed systems - ofloveandhate#6 — eliminate diamond inheritance; de-virtualize the node hierarchy. - ofloveandhate#7 — function-tree introspection, precedence-aware printing, simplified differentiation, Hessian. - ofloveandhate#8 — fix `float()`/`complex()` on multiprec scalars (numpy-recursion segfault). - ofloveandhate#25 — hash-consing the function tree: interning, canonical variables & ordering, functional `Simplified()`. - ofloveandhate#37 — rename function-tree node `Float` → `Complex`. - ofloveandhate#38 — standardize scalar numeric type names (real/complex × dbl/mp). - ofloveandhate#59 — symbolic Jacobian in the core + `random_matrix` + nullvector criticality. - ofloveandhate#64 — hash-consing of Systems: content identity, `Seal()`, interning. <img width="588" alt="critical_points curve — two interlocking circles" src="https://github.com/user-attachments/assets/1bf07625-e959-4f7e-9f31-036b5c77195c" /> ### Trackers, endgames & adaptive precision - ofloveandhate#42 — fix inverted Cauchy `SecurityLevel` guard (truncate diverging paths). - ofloveandhate#43 — collapse predict/correct to a `StepMetadata` API + shared per-path probe. - ofloveandhate#50 — Cauchy c/k probe determinism, start-system sanity tests, per-path timing. - ofloveandhate#55 — adaptive-numeric-type AMP endgames + well-conditioned random coefficients. - ofloveandhate#69 — fully initialize the default `AMPConfig` (deterministic epsilon/Phi/Psi). - ofloveandhate#70 — Cauchy pole-component operating-zone check — never `Success` at a non-root. <img width="1000" alt="AMP endgame session work" src="https://github.com/user-attachments/assets/73d46ced-fccc-4775-82cb-2223ed67ec7c" /> <img width="928" alt="AMP endgame numeric-type flowchart" src="https://github.com/user-attachments/assets/e612a7a2-38f1-4e9a-8a81-d9de88e44511" /> ### Solve pipeline, start systems & randomization - ofloveandhate#12 — solutions in user coordinates by default; internal coords explicit; `homogenize_point` lift (ADR-0013). - ofloveandhate#17 — unify `RandomMp` onto the shared `ThreadEngine` so one seed controls all types. - ofloveandhate#18 — block-composed System: fold the polynomial path into blocks; MHom + linear algebra on blocks; expose m-hom start system. - ofloveandhate#19 — randomize overdetermined systems via a first-class `RandomizationBlock`. - ofloveandhate#48 — de-template ZeroDim on the start system; add linear-product TotalDegree. - ofloveandhate#49 — split ZeroDim into `HomotopySolver` (engine) + `ZeroDimSolver` (algorithm); auto square-up & filter. ### Parallelism - ofloveandhate#21 — ZeroDim crossed-path resolution, speculative full-path parallelism, reproducibility, pickling & MPI-portability. - ofloveandhate#36 — MPI-less shared-memory threading: parallel-by-default solves. - ofloveandhate#56 — fix parallel (MPI) multihomogeneous solve serialization; refresh scaling tables; restore `%` comment parsing. ### Witness sets, slices & NID enablement - ofloveandhate#34 — expose `WitnessSet` + a block-backed `Slice` (enable NID); classic slice/witness emission. - ofloveandhate#51 — `function()` on slice-derived systems + `system.slices()`. - ofloveandhate#52 — show low-precision coefficients in system `describe`. ### Python API & user experience - ofloveandhate#13 — sympy bridge: exact two-way conversion + solve round trip. - ofloveandhate#28 — config UX: string-valued fields, `owner.update(**fields)` router, `slice_` rename, `ZeroDimConfig` de-template. - ofloveandhate#29 — `FixedPrecisionConfig.precision`. - ofloveandhate#30 — ZeroDim solution access: `all_solutions`, `infinite_solutions`, `to_dataframe`. - ofloveandhate#32 — `to_dataframe` keeps the whole solution in one column. - ofloveandhate#62 — Unicode + emoji identifiers, name validation, collision-safe path variables. - ofloveandhate#65 — flatten the public Python API (breaking, pre-release); `System.add_functions`. - ofloveandhate#66 — config-model cleanup: `max_num_refinements` + single-source `final_tolerance`. ### CLI, distribution & versioning - ofloveandhate#39 — emit Bertini-1.7-compatible solution files + external-solver benchmark. - ofloveandhate#46 — ship the `bertini2` CLI in the PyPI wheel (`pip install bertini2` → `bertini2` on PATH). - ofloveandhate#61 — single-source `VERSION` file + full version in C++/CLI. - (fork #4, folded into #3 — CLI exe, MPI parallelism, seeded RNG, pytest suite, benchmarks.) ### Bugfixes (cross-cutting) - ofloveandhate#14 — zero-dim coordinate-test tolerances respect the values' working precision. - ofloveandhate#26 — post-#25 grab-bag: precision fixes, Bertini-1 emitter, Concatenate fix, gamma conditioning, CI fallout. - ofloveandhate#31 — friendly error when a solver is passed to `user_homotopy`. - ofloveandhate#33 — reject equations in the wrong block of a moving homotopy. - ofloveandhate#47 — add pandas to test deps; document the numpy-reduction gotcha for mp dtypes. - ofloveandhate#53 — fix `moving_slice` `eval_time_derivative` doctest precision mismatch. ### Documentation, ADRs & CI - ofloveandhate#9 — single CI run per PR push; secret-gated GitLab sync for all forks. - ofloveandhate#10 — remove expression syntax from a workflow comment (it broke file validation). - ofloveandhate#11 — ADR-0010/0011/0012 for the function_tree overhaul; index + 0006 updates. - ofloveandhate#15 — ccache for bertini2 compiles (linux+macos wheels, unix C++ tests). - ofloveandhate#16 — explicit template instantiation of the endgame/ZeroDim universe (ADR-0014). - ofloveandhate#20, #22, #24 — documentation improvements; make tutorial code blocks executable & verified (doctest). - ofloveandhate#23 — observer subsystem refactor + Python path-visualization/callback observers. - ofloveandhate#27 — ADRs (gamma, fixed-multiple precision) + cyclic-5 cross-software regression test. - ofloveandhate#35 — resolve macOS compiler warnings. - ofloveandhate#54 — add a C++ doc-lint gate + fix rotted Doxygen docs. - ofloveandhate#63 — one-folder-per-tutorial, complete runnable examples, release-refresh mechanism. - ofloveandhate#67 — one-time deterministic regeneration of committed plot images. ### Structured output directory: records, resume, provenance - ofloveandhate#68 — every solve writes a durable, plain-text **structured output directory** and consults it first, so a crashed solve *finishes* on rerun (ensure-answered `solve()`: recorded paths recall instead of recompute). The directory is self-describing and needs no software to read. - **Content identity for reproducibility**: versioned canonical encodings + SHA-256 digests for Systems (`b2sysenc`), configs (`b2cfgenc`), and seed-rooted randomness (`b2rand`) — equal objects digest equally *forever*, across machines, compilers, and library versions; append-only version registries make an un-bumped encoding change fail loudly. `seed=42` names the exact homotopy — same gamma, patch, and start points — everywhere. - **Three truth stores**: `history/` (what was asked, when), `results/` (one append-only JSONL file per run — every computed path with its endpoint and metadata), `definitions/` (content-addressed inputs: systems, configs, givens); `README`/`INDEX` are derived, rebuildable views. - **Reproducible sessions**: effective per-solve seeds make every recorded run replayable standalone; "a solve leaves the session as it found it" (default precision + RNG streams restored on exit); `recall` events narrate re-asks; records are **on by default** for the CLI, `bertini.solve`, and the bare `ZeroDimSolver`/`HomotopySolver` classes. - **Casual surface**: `bertini.solve` / `save` / `load` / `annotate`; `Solution` = points that remember their provenance; chained solves thread provenance across runs, walkable back to canonical start labels or user-supplied givens; pandas + networkx navigation of the records. - **Cauchy endgame corrections** (found via the records' own `function_residual` and a cyclic-6 audit against Bertini 1, refining #70): the security check watches the extrapolated **endpoint** for divergence to infinity (not the loop samples) and truncates **only in the operating zone**; the pole-growth truncation is dropped in favor of the operating-zone acceptance gate alone — cyclic-6 recovers its full 156 nonsingular solutions. - ADR-0042..0047; record-format spec at `docs/records/b2rec-1.md`.
…pped A prerelease tag (.dev/a/b/rc) correctly skips check_timing_freshness, but GitHub propagates a *skipped* `needs` as a skip under the default `if: success()`. That silently skipped build_and_test and every downstream publish/docs job -- v3.0.0.dev9 reported success yet distributed nothing. Gate build_and_test on the freshness job's *result* instead of relying on implicit success(): run unless cancelled, require check_version to succeed, and block only when the freshness check actually ran and failed (skipped == satisfied). Real releases still gate on stale tutorial timings; prereleases now build and publish as intended. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…267) The `v3.0.0.dev9` publish run reported success but distributed nothing: every real job (build/test, TestPyPI, docs) was **skipped**. **Cause:** a prerelease tag correctly skips `check_timing_freshness`, but GitHub propagates a *skipped* `needs` dependency as a skip under the default `if: success()`. So `build_and_test` skipped, and everything downstream with it. **Fix:** gate `build_and_test` on the freshness job's *result* rather than implicit `success()` — run unless cancelled, require `check_version` to succeed, and block only when the freshness check actually **failed** (skipped == satisfied). Real releases still gate on stale tutorial timings; prereleases now build and publish to TestPyPI as intended. Regression introduced in #63 when the freshness gate was added (dev7/dev8 predate it). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Two release-blocking bugs in publish.yml, both surfaced while getting v3.0.0.dev9 out: A) Nothing published even after build_and_test ran. The prior fix put a custom `if:` on build_and_test, but it is a reusable-workflow caller (`uses:`), and a conditional on such a job breaks the implicit `success()` gating of every downstream job -- so publish-to-testpypi (and pypi/docs) skipped despite build_and_test succeeding. The working v2.0.1.dev2 config had no conditional here. Restore that: drop build_and_test's `if:`, and move check_timing_freshness's gate from a job-level `if` to a *step-level* `if`. The job now always SUCCEEDS for prereleases (step skipped) instead of being skipped, so nothing propagates a skip. Real releases still fail the job when timings are stale, which correctly skips the build. B) The is_prerelease classifier used `\.(dev|a|b|rc)[0-9]+$`, which requires a leading dot -- only `.devN` has one. `aN`/`bN`/`rcN` attach directly (3.0.0rc1), so they were misread as FINAL releases and would route to real PyPI + a signed GitHub Release instead of TestPyPI. Match the validation regex: `(\.dev[0-9]+|a[0-9]+|b[0-9]+|rc[0-9]+)$`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…skips)
records_test.py and the doc-example tests importorskip("matplotlib"); it was absent from
all three test-dep lists, so those tests silently SKIPPED. Add matplotlib to the Linux
CIBW_TEST_REQUIRES and the macOS/Windows test installs, add numpy to mac/Windows for
parity, and drop a duplicated `networkx` on the Windows line.
This closes the matplotlib skips on all platforms. The remaining skip source is mpi4py
(whole of test_mpi_zerodim.py + test_doc_example_scripts.py), which needs an MPI runtime
per platform -- handled next as part of the Linux complete-env image, then macOS/Windows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Linux wheel matrix recompiles Boost.Python + eigenpy every run (most of the ~hour), and actions/cache can't hold them (10 GB/repo ceiling, hit before). Bake them into a GHCR image instead -- separate storage quota, layer-cached pull. - docker/manylinux-deps/Dockerfile: manylinux_2_34 + Boost (per-CPython Boost.Python) + eigenpy at /opt/deps/<tag>, mirroring build_and_test.yml's proven BEFORE_BUILD recipe. - .github/workflows/build-ci-image.yml: build+push to ghcr.io/bertiniteam/b2-manylinux-deps, tagged by toolchain key (boost/eigen/eigenpy+manylinux); runs on docker/ changes or dispatch. - README: the ABI contract (tag encodes toolchain; main CI asserts a match -- ADR-0006), the one-time make-package-public step, and size/mac/Windows follow-ups. Consumer wiring (CIBW_MANYLINUX_X86_64_IMAGE + assert-match) lands separately, once the image is published and confirmed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beyond Boost/eigenpy, bake the CPython-independent system layer so the Linux wheel run needs (almost) nothing at runtime and reaches 0 skipped tests: - OpenMPI (openmpi + openmpi-devel, on PATH + ldconfig) so `pip install mpi4py` builds in cibuildwheel's isolated test venv -- which un-skips test_mpi_zerodim.py and test_doc_example_scripts.py (currently skipped for lack of mpi4py). - ccache and a pinned patchelf 0.17.2.1 (the stock patchelf corrupts the bundled CLI's ELF -> SIGSEGV), both moved off CIBW_BEFORE_ALL_LINUX. README updated: complete-env scope + how main CI consumes it. Consumer wiring (CIBW_MANYLINUX_X86_64_IMAGE + assert-match + gutted BEFORE_ALL/BEFORE_BUILD) lands once the image is published and confirmed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first image build failed at eigenpy's cmake: `python.cmake:602 Failed to detect scipy`. eigenpy's configure requires scipy; the image installed only numpy per Python, whereas the working CIBW_BEFORE_BUILD recipe installs `numpy scipy`. Add scipy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, furo-correct footer
The Sphinx front page (furo sidebar header) was showing the git commit SHA as the
"version" -- and the literal 'unknown' whenever git wasn't available (wheel installs,
shallow clones). Fix conf.py to derive version/release from the actual package:
importlib.metadata.version("bertini2") (defined for wheels, matches what autodoc
documents), falling back to the top-level VERSION file. The full string is kept, so a
prerelease suffix like .dev9 / rc1 shows in full.
The commit SHA is demoted to footer-only build provenance, omitted entirely when it
can't be resolved (no more 'unknown' or broken commit links in any build path).
The footer override was also dead: it targeted sphinx_rtd_theme's `extrafooter`
block, but the theme is now furo (rtd removed). Replace _templates/footer.html with
_templates/page.html overriding furo's own `{% block footer %}` (verified rendering in
a real furo build).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ocs.py) Folds a freshly-built single-version site/ into a persistent store as /vX.Y.Z/, mirrors the newest release to /stable/, and regenerates the root landing + versions.json. The version directories present ARE the truth; versions.json and the root index are derived, rebuildable views (mirrors the records doctrine). Dependency-free stdlib; rejects prereleases (they get no durable directory). 12 unit tests (stable-moves, date preservation, re-release idempotency, derived-view rebuild, semver ordering). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
build_docs now folds each real release's site into the docs-store branch via assemble_versioned_docs.py (/vX.Y.Z/ + /stable/ + root landing), pushes it, and uploads the whole multi-version store as the Pages artifact -- so history accrues and old versions are never rebuilt. No Pages settings change: deployment stays on actions/deploy-pages, the docs-store branch is just the durable byte store. Deploy is gated to public releases (X.Y.Z); prerelease/dev builds build-only, never clobbering the versioned site. Grants the docs job contents:write (here and in publish.yml's caller) to push the store branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…walk sphinx-build -b html -W (warnings-as-errors) fails when autosummary can't import a package module: bertini.sympy_bridge needs sympy and bertini.records needs networkx, neither of which the docs job installed. Latent because docs only deploy on real releases and the last one (2.0.2) predates these modules; surfaced by a deploy:false validation build. Both are already declared in the CI test deps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
macOS has no container, so the Linux image trick doesn't apply; the analog is prebuilt Boost+eigenpy tarballs published as GitHub Release assets (off the 10 GB Actions-cache ceiling) and downloaded by the wheel job instead of recompiling Boost per Python. build-macos-deps.yml builds per CPython on macos-14, mirroring CIBW_BEFORE_BUILD_MACOS exactly (same /tmp/deps-py prefix + hardcode-dll-paths so a downloaded bundle extracts back in place for delocate), and uploads deps-macos14-arm64-<cpXY>-<toolchain-key>.tar.gz to the `ci-deps` prerelease. Producer only; consumer wiring (download-or-build fallback in CIBW_BEFORE_BUILD_MACOS + open-mpi/mpi4py for 0 skips) lands once the bundles are published. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CIBW_BEFORE_BUILD_MACOS: download the prebuilt Boost+eigenpy bundle from the ci-deps release (built by build-macos-deps.yml) and extract to /tmp/deps-py instead of compiling Boost per Python. Build-from-source FALLBACK if the asset is missing (e.g. a version bump before the bundle refreshes) -- the asset name encodes the toolchain, so a bump 404s and rebuilds, never a silent ABI mismatch. The bundle keeps hardcode-dll-paths=/tmp/deps-py/lib so delocate is happy. - macOS test job: brew install open-mpi + pip install mpi4py so the MPI test modules run (at 1 rank, guarded by Get_size()>1) instead of skipping -- matching the Windows job, whose env already ships mpi4py and runs them. Added `-rs` to surface any remaining skips. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Windows wheel build already wraps clang-cl with ccache (USE_CCACHE=ON default; ccache ships in environment-win.yml) and builds correctly -- but its cache dir was ephemeral, so every run recompiled bertini cold. Point CCACHE_DIR at the workspace and cache it (loose key, like the Unix jobs), and print ccache --show-stats to measure the hit rate. Low risk: ccache is already active on this job, so persisting the dir can only warm it, never change correctness. (The -DUSE_CCACHE=OFF added in 87bb4c6 was a precaution on the C++ *test* job only, and is unrelated to the /WHOLEARCHIVE static-lib fix in that commit.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The single-Dockerfile producer built Boost+eigenpy for all 5 CPythons serially -- ~an hour, and the run we cancelled was still on cp310 after 85 min. Split it: - build-python-deps.sh: builds one CPython's Boost+eigenpy into /opt/deps/<tag> and tars it. - build-ci-image.yml: a `build-deps` MATRIX runs that script per tag in parallel (inside the manylinux container via `docker run`, dodging the node-in-container issue), uploading a deps-<tag>.tar.gz artifact each; `assemble-and-push` downloads them and builds a thin assembly image that just installs the system layer (openmpi/ccache/patchelf/eigen) and unpacks the tarballs -> ~2 min. Dropped the registry buildcache (assembly is cheap). - Dockerfile: reworked from the inline per-Python loop to the COPY-and-unpack assembly. Wall-clock is now one Python's build, not five in series. `deps/` is gitignored. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add .github/ci-deps-versions.env as the ONE place the C++ dep versions live, read by all three CI workflows: - build_and_test.yml: a small `set_versions` job reads the file and exposes boost/eigen/ eigenpy as outputs; the two jobs that build from source (test_cpp_unix, build_macos_ubuntu_wheels) consume them as job-level env. Removed the workflow-level BOOST_VERSION/EIGENPY_VERSION and the hardcoded eigen-3.4.0 literals. - build-ci-image.yml / build-macos-deps.yml: load the file into $GITHUB_ENV per job, and add the file to their push path filters. So a version bump in one file (a) reruns build_and_test, (b) rebuilds the Linux image, and (c) rebuilds the macOS tarballs -- no more editing versions in four places, and no more forgetting to rebuild the prebuilt deps after a bump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Run CI against just one platform without editing the workflow, e.g.: gh workflow run build_and_test.yml -f os=macos-14 # or linux / windows / all set_matrix narrows the unix matrix on os=linux|macos (empty on os=windows); the three standalone Windows jobs gate on the same input. Empty/`all` (push, PR, workflow_call) keep the full behavior. Makes single-OS validation cheap -- e.g. re-running just macOS to confirm the tarball consumer without the whole matrix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Point CIBW_MANYLINUX_X86_64_IMAGE at ghcr.io/bertiniteam/b2-manylinux-deps pinned to the toolchain-key tag (from the single-sourced versions). BEFORE_ALL_LINUX drops from install-eigen/download-eigenpy to a fast image sanity assert; BEFORE_BUILD_LINUX drops from compiling Boost+eigenpy per Python to symlinking the active Python's prebuilt prefix (/opt/deps/<tag> -> /opt/deps/current, since CIBW_ENVIRONMENT is static). Env points at /opt/deps/current. Add mpi4py to the test-requires (image ships OpenMPI) so the MPI modules run at 1 rank instead of skipping; -rs surfaces any remaining skips. Net: Linux wheels stop recompiling Boost every run, and reach 0 skiptests. If the pinned tag isn't built (version bump w/o image rebuild), the image pull fails loudly -- the ABI assert. Stacked on the single-source-versions branch (needs EIGEN_VERSION for the tag). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_wheels_linux_macos excludes ubuntu, so an ubuntu-only os matrix (a non-develop PR base, or -f os=linux / os=windows once the os input lands) expands to zero combinations and FAILS the run with no job to point at -- exactly what turned the Linux-consumer PR CI red despite every job passing. Guard it with contains(os, 'macos-14') so it cleanly skips instead. Also unblocks the os-targeting input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Promote
developtomainfor the 3.0.0 release. Everything here was validated by the rc1 publish run (green, 15 wheels live on TestPyPI) on the new fast prebuilt-deps CI.Merge immediately —
mainrequires a PR but 0 approvals and no required status checks, so no need to wait on this PR's CI (it re-validates the exact code rc1 already proved; I'll cancel it after merge to spare runners). Use a merge commit to preserve history.After this merges: bump
VERSION → 3.0.0on main (version-only PR, no CI), then tagv3.0.0→ publish.yml → real PyPI + Sigstore GitHub Release + first versioned docs deploy.🤖 Generated with Claude Code