Contents Menu Expand Light mode Dark mode Auto light/dark, in light mode Auto light/dark, in dark mode Skip to content
Bertini 4.0.0 documentation
Logo
Bertini 4.0.0 documentation
  • 👋 Welcome to Bertini 2
  • 🔦 Tutorials
    • Formulating and solving systems
      • 🌐 Finding all the solutions
      • 🎯 Solving a system with ZeroDimSolver
      • 📍 A real point on every component (the Trott curve)
      • 🎯 Critical points by rank deficiency
      • 🧮 Eigenvalues by homotopy continuation
      • 🔁 Parameter homotopy
      • 🧱 Build a system in pieces with clone and concatenate
      • 🎲 Randomize an overdetermined system
      • 🪢 Author your own start system: a product of linears
      • 🧮 Canonical monomial ordering, and opting out
      • 🗃️ A dataframe of solutions: filtering and plotting with pandas
    • Manipulating solutions
      • ✂️ Intro to slices
      • 🛷 Move a slice, hold the system fixed
      • 📈 Plotting solutions and paths
    • Solver settings & multiprecision
      • Auxiliary coordinates
      • ⚙️ Carrying settings across related solves
      • 🪢 Crossed paths: when two paths (accidentally) become one
      • 🎚️ When double precision is not enough
      • 🔬 Precision models: double, multiple, adaptive
      • ⏱️ Stopping a solve, and using a wall-clock budget
    • Parallelism in Python
      • 🚀 Solving at scale: many cores, many machines
      • ⚗️ Parameter homotopy in parallel
    • Observing, metadata, and more
      • 👀 Watching paths: observers and path data
      • 🎨 Seeing precision change
      • 🖼️ The classic continuation cartoon, from real data
    • Performance and benchmarking
      • ⏱️ Timing Bertini 1 vs Bertini 2
    • The record-keeping system
      • Automatic record keeping
      • Chained homotopies: provenance all the way back
    • Doing things manually
      • ♻️ Evaluation of cyclic-\(n\) polynomials
      • 🎮 Using an endgame to compute singular endpoints
      • 🛤 Tracking to nonsingular endpoints
      • 〰️ Tracking an analytic homotopy
  • 🌟 Everyday API
  • ✨ Showpieces
    • 🧬 The Monodromy Loom
    • ✈️ The Flight Recorder
  • ⚙️ Configuration reference
  • Multiprecision numbers and NumPy
  • 🔎 Full API reference
    • bertini.algorithms
      • bertini.algorithms.zerodim
    • bertini.config
    • bertini.endgame
    • bertini.logging
    • bertini.multiprec
    • bertini.nag_algorithm
    • bertini.operators
    • bertini.parallel
    • bertini.parse
    • bertini.random
    • bertini.records
    • bertini.symbolics
    • bertini.sympy_bridge
    • bertini.system
      • bertini.system.start_system
    • bertini.tracking
      • bertini.tracking.observers
        • bertini.tracking.observers.amp
        • bertini.tracking.observers.double
        • bertini.tracking.observers.multiple
  • 📚 Bibliography
Back to top
View this page

⚙️ Carrying settings across related solves¶

A real computation is rarely one solve. A numerical irreducible decomposition, a parameter sweep, a multi-stage pipeline – each runs many related solves, and you usually want the same tracking settings on every one of them. Re-typing tolerances and step controls for each solver is both tedious and a place for them to silently drift apart.

Bertini 2 gives every config owner – every tracker and every solver – the same small interface for this. (Owning configs is a capability, not a class: a tracker is not a solver, but both carry configs, so both get these methods.)

This page is about moving settings around. For which settings exist and what they default to, see ⚙️ Configuration reference.

Set fields by name¶

Every numeric config field accepts a string – the exact, noise-free way to write a number (0.05 the Python float is not \(1/20\); "0.05" is). And you can set fields right on the solver without naming which config struct they live in: update() routes each field to whichever config owns it.

x, y = bertini.Variable('x'), bertini.Variable('y')
system = bertini.System()
system.add_function(x*x + y*y - 1)
system.add_function(x + y)
system.add_variable_group(bertini.VariableGroup([x, y]))

solver = bertini.ZeroDimSolver(system)
solver.update(final_tolerance="1e-11",                 # -> EndgameConfig
              max_num_crossed_path_resolve_attempts=3) # -> ZeroDimConfig

from bertini.endgame import EndgameConfig
from bertini.nag_algorithm import ZeroDimConfig
assert solver.get_config(EndgameConfig).final_tolerance == 1e-11
assert solver.get_config(ZeroDimConfig).max_num_crossed_path_resolve_attempts == 3

The solver keeps some of its settings in its tracker and its endgame, and one call reaches those too. max_step_size belongs to the tracker and num_sample_points to the endgame; neither needs naming the object it lives on:

solver.update(max_step_size="0.05", num_sample_points=6)

from bertini.tracking import SteppingConfig
assert solver.get_config(SteppingConfig).max_step_size == bertini.multiprec.real_mp("0.05")
assert solver.get_config(EndgameConfig).num_sample_points == 6

A misspelled field raises immediately rather than silently doing nothing:

try:
    solver.update(max_stepsize="0.05")      # no such field anywhere on this solver
    raise AssertionError("should have raised")
except AttributeError:
    pass

Each field name belongs to exactly one config, so the flat form and the named form reach the same field. final_tolerance is the endgame’s:

solver.update(final_tolerance="1e-12")                      # flat
assert solver.get_config(EndgameConfig).final_tolerance == 1e-12
solver.configure(endgame={'final_tolerance': "1e-8"})       # named
assert solver.get_config(EndgameConfig).final_tolerance == 1e-8

Carry a whole group of settings¶

get_settings() hands you the owner’s entire configuration as a plain dict {name: config} – independent, picklable copies. set_settings() applies one back. So you configure once and stamp every subsequent solver with the same settings.

reference = bertini.ZeroDimSolver(system)
reference.update(final_tolerance="1e-11", newton_before_endgame="1e-6")
settings = reference.get_settings()
assert set(settings) == set(reference.config_names())     # one entry per config

# ... later, for each related solve ...
next_solver = bertini.ZeroDimSolver(system)
next_solver.set_settings(settings)
assert next_solver.get_config(EndgameConfig).final_tolerance == 1e-11

The bundle is an ordinary picklable value, so it can be stored or sent to another process – the same settings then drive a worker’s solves as drive the manager’s.

import pickle
carried = pickle.loads(pickle.dumps(settings))
worker_solver = bertini.ZeroDimSolver(system)
worker_solver.set_settings(carried)
assert worker_solver.get_config(EndgameConfig).final_tolerance == 1e-11

The settings are precision-agnostic¶

A bundle built from one precision model applies unchanged to another. This is what lets a multi-stage workflow mix precision models – a quick double-precision pass, then an adaptive re-solve – while carrying one set of tracking tolerances through all of them.

tuned = bertini.ZeroDimSolver(system, mptype='double')
tuned.update(final_tolerance="1e-10")
bundle = tuned.get_settings()

for mptype in ('multiple', 'adaptive'):
    solver = bertini.ZeroDimSolver(system, mptype=mptype)
    solver.set_settings(bundle)                       # drops on cleanly, any precision model
    assert solver.get_config(EndgameConfig).final_tolerance == 1e-10

By default set_settings() applies only the configs the target actually has and skips the rest, so a bundle also moves between different kinds of owner (a tracker has no solver-level tolerances). Pass strict=True to instead require every config in the bundle to be applicable.

from bertini import AMPTracker
tracker = AMPTracker(system)
tracker.set_settings(settings)             # silently skips the solver-only configs
try:
    tracker.set_settings(settings, strict=True)
    raise AssertionError("should have raised")
except KeyError:
    pass

Complete example¶

carrying_settings.py¶
"""Carrying settings across related solves -- Bertini 2 tutorial.

Tune one config owner, get_settings(), and set_settings() on each of the rest.
Run:  python carrying_settings.py
"""

import pickle

import numpy as np
import bertini
from bertini import AMPTracker
from bertini.nag_algorithm import ZeroDimConfig
from bertini.tracking import SteppingConfig
from bertini.endgame import EndgameConfig


def build_system():
    """The little system every section solves."""
    x, y = bertini.Variable('x'), bertini.Variable('y')
    system = bertini.System()
    system.add_function(x*x + y*y - 1)
    system.add_function(x + y)
    system.add_variable_group(bertini.VariableGroup([x, y]))
    return system


def set_fields_by_name(system):
    """Route fields by name onto whichever config owns them."""
    solver = bertini.ZeroDimSolver(system)
    solver.update(final_tolerance="1e-11",                 # -> EndgameConfig
                  max_num_crossed_path_resolve_attempts=3) # -> ZeroDimConfig

    assert solver.get_config(EndgameConfig).final_tolerance == 1e-11
    assert solver.get_config(ZeroDimConfig).max_num_crossed_path_resolve_attempts == 3

    # Settings the solver keeps in its tracker and its endgame are reached by the same call.
    solver.update(max_step_size="0.05", num_sample_points=6)
    assert solver.get_config(SteppingConfig).max_step_size == bertini.multiprec.real_mp("0.05")
    assert solver.get_config(EndgameConfig).num_sample_points == 6

    # A misspelled field raises immediately rather than doing nothing.
    try:
        solver.update(max_stepsize="0.05")      # no such field anywhere on this solver
        raise AssertionError("should have raised")
    except AttributeError:
        pass

    return solver


def carry_a_whole_bundle(system):
    """get_settings() -> plain picklable dict; set_settings() stamps another solver."""
    reference = bertini.ZeroDimSolver(system)
    reference.update(final_tolerance="1e-11", newton_before_endgame="1e-6")
    settings = reference.get_settings()
    assert set(settings) == set(reference.config_names())     # one entry per config

    # ... later, for each related solve ...
    next_solver = bertini.ZeroDimSolver(system)
    next_solver.set_settings(settings)
    assert next_solver.get_config(EndgameConfig).final_tolerance == 1e-11

    # The bundle is an ordinary picklable value -- store it or ship it to a worker.
    carried = pickle.loads(pickle.dumps(settings))
    worker_solver = bertini.ZeroDimSolver(system)
    worker_solver.set_settings(carried)
    assert worker_solver.get_config(EndgameConfig).final_tolerance == 1e-11

    return settings


def settings_are_precision_agnostic(system, settings):
    """One bundle drops cleanly onto any precision model, and across owner kinds."""
    tuned = bertini.ZeroDimSolver(system, mptype='double')
    tuned.update(final_tolerance="1e-10")
    bundle = tuned.get_settings()

    for mptype in ('multiple', 'adaptive'):
        solver = bertini.ZeroDimSolver(system, mptype=mptype)
        solver.set_settings(bundle)                       # drops on cleanly, any precision model
        assert solver.get_config(EndgameConfig).final_tolerance == 1e-10

    # By default set_settings() applies only the configs the target has and skips the rest.
    tracker = AMPTracker(system)
    tracker.set_settings(settings)             # silently skips the solver-only configs
    try:
        tracker.set_settings(settings, strict=True)
        raise AssertionError("should have raised")
    except KeyError:
        pass


def main():
    system = build_system()
    set_fields_by_name(system)
    settings = carry_a_whole_bundle(system)
    settings_are_precision_agnostic(system, settings)


if __name__ == '__main__':
    main()
Next
🪢 Crossed paths: when two paths (accidentally) become one
Previous
Auxiliary coordinates
Copyright © 2015-2026, Bertini Team
Made with Sphinx and @pradyunsg's Furo
Built from d882924 on 2026-10-06 20:10 UTC.
On this page
  • ⚙️ Carrying settings across related solves
    • Set fields by name
    • Carry a whole group of settings
    • The settings are precision-agnostic
    • Complete example