Skip to content

Solver config surface: final_tolerance has two owners (endgame setting silently reverted), and config_names() returns names get_config() refuses #392

Description

@ofloveandhate

Summary

Two related interface defects in the solver configuration surface. Both are silent — no exception, no warning — and together they make it easy to believe you have set a tolerance when you have not.

  1. final_tolerance has two owners, and the loser is silently discarded. It exists on TolerancesConfig (solver level) and on EndgameConfig. solve() pushes the solver's TolerancesConfig down into the endgame, clobbering anything set directly on the endgame. Setting it via get_endgame().set_endgame_settings(...) therefore has no effect and raises nothing.

  2. config_names() returns strings that get_config() refuses. get_config wants a type; config_names() advertises names it will not accept.

Reproducer — defect 1

import bertini, bertini.nag_algorithm as na
# ... build homotopy HOM, start points START, target TGT ...

s = na.HomotopySolver(HOM, START, TGT, mptype='adaptive', endgame='powerseries')
eg = s.get_endgame()
cfg = eg.get_endgame_settings()
cfg.update(final_tolerance='1e-8')
eg.set_endgame_settings(cfg)
print(eg.get_endgame_settings().final_tolerance)          # 1e-08   <- it took
s.solve()
print(s.get_endgame().get_endgame_settings().final_tolerance)  # 1e-11  <- SILENTLY REVERTED

versus the flat route, which works:

s.solve(final_tolerance='1e-8')
print(s.get_endgame().get_endgame_settings().final_tolerance)  # 1e-08

What makes this a trap rather than merely surprising is that the other endgame fields behave the opposite way. Measured on the same solver:

field set via endgame config survives solve()? accepted as a flat solve() field?
final_tolerance 1e-08 NO → reverted accepted
num_sample_points 7 yes rejected (AttributeError)
max_num_refinements 7 yes rejected (AttributeError)
min_track_time 1e-120 yes rejected (AttributeError)

So the same API surface has opposite semantics field by field, and the one that loses does so without a word.

This cost real debugging time: a whole sweep of final_tolerance values routed through the endgame config produced byte-identical results, which read as "this knob does nothing to this problem" when in fact the knob was never applied. The flat route showed a sharp, decisive dependence on the same values.

Reproducer — defect 2

zd = na.ZeroDimSolver(system)
zd.config_names()   # ['auto_retrack', 'post_processing', 'tolerances', 'zero_dim']
zd.get_config('tolerances')
# KeyError: this object has no configuration of the requested type; see config_types()
zd.get_config(TolerancesConfig)   # works

config_names() and get_config() are adjacent on the same object and do not compose. Either get_config should accept the names, or config_names() should not read like an index into it.

Suggested fixes

  1. Give final_tolerance one owner. If the solver owns it, EndgameConfig should not expose a field the solver will overwrite — or set_endgame_settings should reject/warn on fields the solver will clobber. Silent reversion is the worst of the options.
  2. Make get_config accept what config_names() returns (or drop one of the two).

A design question worth asking

TolerancesConfig currently holds:

final_tolerance            an ENDGAME concept
newton_before_endgame      a TRACKER concept
newton_during_endgame      a TRACKER concept
path_truncation_threshold  a SECURITY concept

"Tolerances for what?" is not answerable from the name, and the grab-bag is arguably what creates defect 1: final_tolerance ends up owned by a config whose scope is "numbers that happen to be tolerances" rather than by the component that consumes it. Ownership per concern would make the precedence obvious instead of requiring an experiment to discover.

Environment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions