Skip to content

CLI, so many bugfixes, block structured systems, mhom exposed, user homotopy, two-level parallelism...#238

Open
ofloveandhate wants to merge 515 commits into
bertiniteam:developfrom
ofloveandhate:develop
Open

CLI, so many bugfixes, block structured systems, mhom exposed, user homotopy, two-level parallelism...#238
ofloveandhate wants to merge 515 commits into
bertiniteam:developfrom
ofloveandhate:develop

Conversation

@ofloveandhate

Copy link
Copy Markdown
Contributor

No description provided.

ofloveandhate and others added 8 commits June 18, 2026 21:23
…kObserver.on

Tracker events are templated on the emitter, so each precision exposes its own
SuccessfulStep/TrackingStarted/... class under observers.amp/double/multiple --
forcing users to know the precision just to name an event. Two ergonomic fixes,
both pure-Python (the templated-on-emitter C++ design that gives observers full
typed access to event.tracker() is untouched):

- bertini.tracking.events.<Name> bundles the three precision variants into a
  tuple, which works directly with isinstance() and CallbackObserver.on(). So
  `events.SuccessfulStep` works regardless of precision.
- CallbackObserver.on() now also accepts the event name as a string, resolved
  against the observer's own precision: obs.on("SuccessfulStep", cb).

Tests cover the tuple-union handle and the string form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds nag_algorithms/events.hpp with four events emitted during a solve:
AlgorithmStarted / AlgorithmComplete (around the whole Solve loop) and
PathBeginning / PathComplete (around ExecuteOnePath, the per-start-point unit;
PathComplete fires even when the pre-endgame leg fails). PathBeginning/Complete
carry the path index.

They are emitted on the non-templated AnyZeroDim base, so a SINGLE observer type
can watch every templated ZeroDim variant (no per-instantiation explosion). An
observer recovers the concrete solver from event.Get() -- in C++ via dynamic_cast,
in Python (next commit) automatically via RTTI. ZeroDim overrides ObservableIsA to
accept AnyZeroDim observers (in addition to its exact type).

The point: between a PathBeginning and its PathComplete, the solver runs everything
for that path -- main homotopy track AND endgame sub-tracks -- so an observer can
group them as one complete solution path (the basis for SolutionPathCollector).

(Event names are working placeholders; more events can be added by following the
same pattern.)

C++ tests: a ZeroDim observer sees AlgorithmStarted once, AlgorithmComplete once,
and matched PathBeginning/PathComplete per path; a tracker observer attached to a
ZeroDim is rejected. test_nag_algorithms (22) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…erver + lifecycle events)

The Python ZeroDim solvers now have add_observer/remove_observer (ObservableVisitor
on ZDVisitor), and a new bertini.nag_algorithms.observers submodule exposes the
single AnyZeroDim-based observer surface for every solver variant:
  - CustomObserver (subclassable base, shared_ptr holder -> co-owned when attached)
  - AlgorithmStarted / AlgorithmComplete / PathBeginning / PathComplete
    (PathBeginning/Complete carry .path_index()).

AnyZeroDim is registered and the concrete ZeroDim classes now declare
bases<AnyZeroDim>, so event.solver() (statically AnyZeroDim&) resolves via RTTI to
the concrete solver with its full python API -- confirmed by a test reading
e.solver().solutions() inside Observe(). The attach guard distinguishes observables:
a nag observer is rejected on a tracker and a tracker observer on the solver
(TypeError).

Tests in meta_observer_test.py cover the lifecycle event counts/indices, the
RTTI concrete-solver recovery, and the cross-observable guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…game) in one observer

bertini.nag_algorithm.SolutionPathCollector is the two-level meta-observer the dream
was after. Attach it to a ZeroDim solver: on each PathBeginning it spins up a fresh
tracking PathDataCollector and attaches it to the solver's tracker, and on the matching
PathComplete it harvests that collector into .series and detaches it. Since the solver
reuses one tracker for a path's main homotopy track AND its endgame sub-tracks, the
per-path collector captures the entire journey to t -> 0 -- one clean series per
solution path, endgame included, with no start-time filtering. It picks the right
precision's PathDataCollector via tracker.observers, and recovers the tracker from
event.solver().get_tracker() (RTTI-resolved concrete solver).

The tutorial now leads with SolutionPathCollector (attach to the solver) as the clean
way to collect and plot every path, and the regenerated figure shows each of the six
z^6-2z^2+2 paths running all the way into its solution (the endgame carries the last
leg). The tracker-level PathCollectionObserver is kept and mentioned as the lower-level
building block.

Tests: one series per solution path with the expected indices; whole-path series capture
at least as many steps as the main-track-only filter. Full observer/nag/zero_dim suites
green; docs build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ax.set_aspect(1.0) so Re/Im are scaled equally; regenerated the figure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on number

New section solving the cyclic-3 system in three variables and plotting the paths
in (Re x, Re y, Re z) space, each drawn as a Line3DCollection coloured along its
length by the condition number (log scale, shared colorbar) -- showing where the
tracking is worst-conditioned (near the solutions / in the endgame). Uses the same
SolutionPathCollector and the diagnostics() condition-number column, with a 1:1:1
box aspect. Committed figure cyclic3_paths.png; docs build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…orn) section, SVG+PNG figures

- Seed every example with bertini.random.set_random_seed(...) so a reader reproduces
  exactly the pictures shown.
- New section "Watching the Cauchy endgame at a singular solution": solves Griewank-Osborn
  (triple root at the origin), selects the singular paths from the solver's own metadata
  (m.is_singular / m.path_index -- no hand-rolled coordinate thresholds), and plots one
  path's x-coordinate on a LOG-RADIAL scale so the geometrically-shrinking Cauchy loops
  stay visible as a spiral winding into the singularity (coloured by |t|).
- Render every tutorial figure as SVG (crisp in the built docs) and also keep a PNG
  alongside for local viewing; figure directives reference the SVG.
- z^6 plot: add set_box_aspect(1) so the box is square (with 1:1 data scaling).

Docs build clean; figures regenerated from seeded scripts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consistent lifecycle-event naming: AlgorithmStarted/AlgorithmComplete and
PathStarted/PathComplete. Renamed the C++ event class, the emission point, the
python-exposed class, SolutionPathCollector, and the tests/tutorial. C++
test_nag_algorithms and the python meta_observer suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ofloveandhate and others added 30 commits June 29, 2026 14:23
…late

De-template ZeroDim on the start system; add linear-product TotalDegree
First stage of splitting the zero-dim machinery into two roles. Introduce
algorithm::HomotopySolver / algorithm::ZeroDimSolver as alias templates over the
existing policy-parameterized ZeroDim (RefToGiven and CloneGiven respectively),
and migrate the whole public surface onto the new, honest names:

- Python bindings expose ZeroDimSolver<EG><Prec> (was ZeroDim<EG><Prec>) and
  HomotopySolver<EG><Prec> (replaces the ...UserHomotopy classes).
- Python wrapper: ZeroDim(...) facade -> ZeroDimSolver(...); first-class
  HomotopySolver(...) facade; user_homotopy() kept as a thin forwarder.
- The old ZeroDim name and the legacy class-name shims are DELETED outright
  (no alias, no deprecation) -- loud break by request.
- All tests, examples, and tutorials re-pointed to the new names.

Internal C++ (the ZeroDim template body, ETI, blackbox, output::Classic) is
unchanged here; a follow-up collapses the aliases into two concrete, composed
classes and retires the system-management policy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mSolver

Turn the policy-parameterized ZeroDim<T,E,S,Policy> into two concrete classes,
removing the system-management policy as a template parameter of the solver:

- HomotopySolver<T,E,S> is the continuation engine: it holds the homotopy, start
  system, and target by reference (the old RefToGiven layout, now its only
  layout) and owns all the machinery -- pre-endgame tracking, endgame, crossing
  resolution, classification, reporting, threaded + MPI solve.  The MPI
  system-broadcast is now a virtual DistributeSystems() hook (no-op here).

- ZeroDimSolver<T,E,S> is the algorithm: it clones the user's target, homogenizes
  and patches it, builds a start system (via the injected factory) and a homotopy
  (an OwnedHomotopy base built BEFORE the engine base), then IS-A HomotopySolver
  over those owned systems -- reusing the engine, not duplicating it.  It
  overrides DistributeSystems() to broadcast its owned systems under MPI.
  ConsistencyCheck moved here; RankCheck()/SquareUp() are stubbed (next).

ETI, blackbox switches, output::Classic, and the C++ tests are updated to the two
concrete types.  common/policies.hpp still defines the now-unused CloneGiven /
RefToGiven (deleted in a follow-up that also rehomes the StartSystemFactory and
severs the NID placeholder).

C++ tests pass (OMP=1); full Python suite green (518 passed, 14 skipped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… sever NID

The system-management policy is fully retired now that ZeroDim's policy parameter
is gone:

- StartSystemFactory / MakeStartFactory move out of the deleted policy:: into
  start_system:: (bertini2/system/start_base.hpp) -- they are start-system
  selection, not system ownership, and the base header is all they need. Every
  call site (ZeroDimSolver default, blackbox switches, python bindings, C++
  tests) is updated.
- NumericalIrreducibleDecomposition drops its SystemManagementP/CloneTarget base
  (it was placeholder scaffolding whose Solve() throws) and owns a cloned target
  inline. Its template is now <Tracker, Endgame, System>; the binding is unchanged.
- common/policies.hpp (CloneGiven / RefToGiven / CloneTarget / SysMgmtPolicy) is
  deleted, its #includes removed (config.hpp, zero_dim_solve.hpp, NID), and the
  CMake header list updated. No references to the policy namespace remain.

Core + all tests + bindings build; C++ nag/NID/blackbox suites pass (OMP=1);
Python suite green (518 passed, 14 skipped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…check

Implement the two ZeroDimSolver feasibility behaviors (previously stubbed):

- SquareUp(): an over-determined target (more equations than the affine dimension)
  is randomized down to square via System::Randomize so a start system can track
  it.  The original system is kept; after the solve, ZeroDimSolver overrides the
  now-virtual PostEGAction to re-evaluate the ORIGINAL system at each finite
  endpoint and drop the extraneous solutions the squaring introduces (their
  function_residual is reported; is_finite -> false, so they leave finite/real/
  singular accessors).  Exposed: was_randomized() and randomization_matrix().

- RankCheck(): a system that is square by equation count can still be
  positive-dimensional (e.g. a repeated equation).  At a generic point a
  zero-dimensional system has a full-rank Jacobian, so a rank-deficient Jacobian
  there raises a clear error.  (Under-determined systems are already rejected by
  ConsistencyCheck, now with a friendlier message.)

To support the post-solve filter, PostEGAction is protected+virtual and the
per-endpoint metadata / endpoints are protected so ZeroDimSolver can read them.

Tests: C++ (square solves w/o randomization; over-determined squared + filtered to
the genuine roots; under-determined raises; positive-dimensional square raises) and
a Python feasibility_test mirroring them.  C++ nag suite passes (OMP=1); full
Python suite green (518 passed, 14 skipped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…solution filter

New tutorial python/docs/source/tutorials/zerodim_solver.rst (doctest-runnable, in
the toctree): ZeroDimSolver on a pleasant square system, an over-determined system
(squared up, extraneous solutions filtered out -- was_randomized / finite_solutions),
and an under-determined system (the helpful refusal).

Writing it surfaced a precision bug in the extraneous-solution filter: when a prior
AMP solve left the dehomogenized point at a higher precision than the original
(precision-16) system, the filter's Eval threw a precision mismatch.  Fixed by
evaluating the original system in double precision -- the filter only needs to tell a
tiny residual from an O(1) one, which double does robustly regardless of the
solution's tracked precision.

C++ nag suite passes (OMP=1); full Python suite green (521 passed, 14 skipped);
the tutorial's testcode blocks all run clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…note

ADR-0040 records the split rationale: two concrete classes replacing the
policy-parameterized ZeroDim, why inheritance over a composed member (avoids
duplicated Configured state), where the new feasibility behaviors live, the
policies.hpp deletion + StartSystemFactory rehome + NID severance, and that
SystemView is obviated.  Backfill the README index (0037-0040) and note the
rename in ADR-0039.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
…bertiniteam#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>
…ertiniteam#264)

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() (bertiniteam#263)
feat: show low-precision coefficients in system describe (bertiniteam#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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment