Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions core/include/bertini2/nag_algorithms/common/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ struct ZeroDimConfig
/// Under MPI the per-rank thread count comes from OMP_NUM_THREADS, not this field.
unsigned num_threads = 0;

/// Whether an identical ask already present in the records directory may be RECALLED instead of
/// re-tracked (default true). Set false to force a fresh track even when the paths are recorded --
/// e.g. to run path observers, benchmark the solve, or re-verify reproducibility. Like num_threads
/// this is transient (it changes only WHETHER the work runs, not WHAT is computed), so it is
/// deliberately excluded from the configuration's identity/digest. No effect when nothing is recorded.
bool recall = true;

mpq_rational start_time{1}; ///< Homotopy start time (t=1).
mpq_rational endgame_boundary{1, 10}; ///< Time at which tracking hands off to the endgame (t=1/10).
mpq_rational target_time{0}; ///< Homotopy target time (t=0).
Expand Down
6 changes: 5 additions & 1 deletion core/include/bertini2/nag_algorithms/zero_dim_solve.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1130,7 +1130,11 @@ run the endgame, classify the endpoints, report. See the forward-declare doc ab
if (records_)
{
EnsureRunRecorded();
indices_to_run = RecallRecordedPaths(all_indices);
// recall==false forces a fresh track of every path even when identical results are
// already recorded (e.g. to run path observers or benchmark); the fresh run is still
// recorded. See ZeroDimConfig::recall.
if (this->template Get<ZeroDimConf>().recall)
indices_to_run = RecallRecordedPaths(all_indices);
}

// num_threads: 0 = auto (hardware_concurrency), 1 = serial, N = N threads;
Expand Down
7 changes: 4 additions & 3 deletions core/include/bertini2/records/config_encoding.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ System encoding (ADR-0042):
encoding changes without the bump.

Deliberately excluded from encoding: `algorithm::RandomConfig` (the seed is its own slot
in the ask identity, beside the config digest -- never inside it) and
`ZeroDimConfig::num_threads` (thread count must not change what was computed).
in the ask identity, beside the config digest -- never inside it) and the transient
`ZeroDimConfig::num_threads` / `ZeroDimConfig::recall` (thread count and recall-vs-retrack
change only whether/how the work runs, not what is computed).
*/

#pragma once
Expand Down Expand Up @@ -104,7 +105,7 @@ std::string CanonicalEncoding(algorithm::RegenerationConfig const& c);
/// \brief Canonical encoding of post-processing (classification) settings.
std::string CanonicalEncoding(algorithm::PostProcessingConfig const& c);
/// \brief Canonical encoding of top-level zero-dim solve settings. Excludes
/// num_threads (transient: thread count must not change identity).
/// num_threads and recall (transient: they must not change identity).
std::string CanonicalEncoding(algorithm::ZeroDimConfig const& c);
/// \brief Canonical encoding of the algorithm (track-type) selection.
std::string CanonicalEncoding(algorithm::MetaConfig const& c);
Expand Down
3 changes: 2 additions & 1 deletion core/src/records/config_encoding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@ std::string CanonicalEncoding(algorithm::PostProcessingConfig const& c)

std::string CanonicalEncoding(algorithm::ZeroDimConfig const& c)
{
// num_threads deliberately excluded: transient (must not change what was computed).
// num_threads and recall deliberately excluded: transient (they change only whether/how the work
// runs, not what is computed), so they must not affect the config's identity.
std::ostringstream out;
out << "(cfg ZeroDim"
<< " initial_ambient_precision=" << c.initial_ambient_precision
Expand Down
38 changes: 38 additions & 0 deletions core/test/nag_algorithms/zero_dim_records.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,44 @@ BOOST_AUTO_TEST_CASE(recording_solve_then_full_recall)
BOOST_CHECK_SMALL((first[ii] - again[ii]).norm(), 1e-14);
}

// ZeroDimConfig::recall == false forces a fresh re-track of an identical ask that would otherwise be
// recalled -- the escape hatch for path observers / benchmarking / re-verification.
BOOST_AUTO_TEST_CASE(recall_false_forces_a_fresh_retrack)
{
auto const dir = FreshDir("norecall");

// first solve records all four paths (nothing to recall yet)
SetGlobalSeed(42);
auto sys_a = TwoQuadrics();
ZD a(sys_a);
a.DefaultSetup();
a.RecordTo(std::make_shared<records::OutputDirectory>(dir));
a.Solve();
BOOST_CHECK_EQUAL(a.NumPathsRecalled(), 0u);

// identical ask, recall = true (the default): recalls all four, tracks nothing
SetGlobalSeed(42);
auto sys_b = TwoQuadrics();
ZD b(sys_b);
b.DefaultSetup();
b.RecordTo(std::make_shared<records::OutputDirectory>(dir));
b.Solve();
BOOST_CHECK_EQUAL(b.NumPathsRecalled(), 4u);

// identical ask, recall = false: re-tracks everything (recalls nothing) and still finds the roots
SetGlobalSeed(42);
auto sys_c = TwoQuadrics();
ZD c(sys_c);
c.DefaultSetup();
auto cfg = c.Get<algorithm::ZeroDimConfig>();
cfg.recall = false;
c.Set(cfg);
c.RecordTo(std::make_shared<records::OutputDirectory>(dir));
c.Solve();
BOOST_CHECK_EQUAL(c.NumPathsRecalled(), 0u);
BOOST_CHECK_EQUAL(c.Report().num_finite_solutions, 4u);
}

BOOST_AUTO_TEST_CASE(partial_directory_resumes_computing_only_the_missing)
{
auto const full_dir = FreshDir("resume_full");
Expand Down
46 changes: 46 additions & 0 deletions python/test/zero_dim/recall_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""ZeroDimConfig.recall (default True): an identical ask already in the records directory is recalled
rather than re-tracked. recall=False forces a fresh track even when recorded -- the escape hatch for
path observers / benchmarking / re-verification. (Bug context: a SolutionPathCollector silently
collected nothing on a re-solve, because the paths were recalled, not tracked.)
"""

import bertini as pb
from bertini import ZeroDimSolver
from bertini.nag_algorithm import ZeroDimConfig, observers as nobs


def _two_quadrics():
x, y = pb.variables(['x', 'y'])
sys = pb.System()
sys.add_variable_group(x, y)
sys.add_functions([x * x - 1, y * y - 1]) # 4 well-separated roots -> 4 total-degree paths
return sys


def _paths_collected(rec_dir, recall):
pb.random.set_random_seed(42) # SAME ask every call (system + settings + seed)
solver = ZeroDimSolver(_two_quadrics())
solver.record_to(str(rec_dir)) # isolate the records dir (no CWD pollution)
cfg = solver.get_config(ZeroDimConfig)
cfg.recall = recall
solver.set_config(cfg)
collector = nobs.SolutionPathCollector()
solver.add_observer(collector)
solver.solve()
return len(collector.series)


def test_recall_default_true_then_false(tmp_path):
rec = tmp_path / "records"
assert _paths_collected(rec, recall=True) == 4 # 1st: fresh, tracks + records 4 paths
assert _paths_collected(rec, recall=True) == 0 # 2nd: identical ask -> RECALLED, observer empty
assert _paths_collected(rec, recall=False) == 4 # 3rd: recall=False -> forced fresh re-track


def test_recall_config_roundtrips():
solver = ZeroDimSolver(_two_quadrics())
assert solver.get_config(ZeroDimConfig).recall is True # default
cfg = solver.get_config(ZeroDimConfig)
cfg.recall = False
solver.set_config(cfg)
assert solver.get_config(ZeroDimConfig).recall is False
6 changes: 6 additions & 0 deletions python_bindings/src/zero_dim_configs_export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ namespace bertini{
"(all available cores), 1 = serial (no thread pool), N = N threads. The "
"OMP_NUM_THREADS environment variable overrides this. Threading needs no MPI and "
"no free-threaded Python: the heavy tracking runs in C++ with the GIL released.")
.def_readwrite("recall", &ZeroDimConfig::recall,
"Whether an identical ask already in the records directory may be RECALLED instead of "
"re-tracked (default True). Set False to force a fresh track even when the paths are "
"recorded -- e.g. to run path observers, benchmark the solve, or re-verify a run; the "
"fresh track is still recorded. No effect when nothing is recorded. Transient: it does "
"not affect the run's identity/digest.")
;

// metadata types
Expand Down
Loading