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.
-
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.
-
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
- 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.
- 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
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.
final_tolerancehas two owners, and the loser is silently discarded. It exists onTolerancesConfig(solver level) and onEndgameConfig.solve()pushes the solver'sTolerancesConfigdown into the endgame, clobbering anything set directly on the endgame. Setting it viaget_endgame().set_endgame_settings(...)therefore has no effect and raises nothing.config_names()returns strings thatget_config()refuses.get_configwants a type;config_names()advertises names it will not accept.Reproducer — defect 1
versus the flat route, which works:
What makes this a trap rather than merely surprising is that the other endgame fields behave the opposite way. Measured on the same solver:
solve()?solve()field?AttributeError)AttributeError)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_tolerancevalues 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
config_names()andget_config()are adjacent on the same object and do not compose. Eitherget_configshould accept the names, orconfig_names()should not read like an index into it.Suggested fixes
final_toleranceone owner. If the solver owns it,EndgameConfigshould not expose a field the solver will overwrite — orset_endgame_settingsshould reject/warn on fields the solver will clobber. Silent reversion is the worst of the options.get_configaccept whatconfig_names()returns (or drop one of the two).A design question worth asking
TolerancesConfigcurrently holds:"Tolerances for what?" is not answerable from the name, and the grab-bag is arguably what creates defect 1:
final_toleranceends 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
VERSION3.5.0.dev0, branchintegration/391-on-387(develop + System.eval accepts any point precision; mixed time/space precisions throw (closes #377) #387 + MakeMovingHomotopy: 'identical moving row' guard compares 6-digit renderings, so distinct rows are falsely refused #391) @b8895912bertini22.0.2