Bertini 2 d882924 (2026-10-06 19:56 UTC)
Numerical algebraic geometry library (C++ core)
Loading...
Searching...
No Matches
zero_dim_solve.hpp
Go to the documentation of this file.
1//This file is part of Bertini 2.
2//
3//bertini2/nag_algorithms/zero_dim_solve.hpp is free software: you can redistribute it and/or modify
4//it under the terms of the GNU General Public License as published by
5//the Free Software Foundation, either version 3 of the License, or
6//(at your option) any later version.
7//
8//bertini2/nag_algorithms/zero_dim_solve.hpp is distributed in the hope that it will be useful,
9//but WITHOUT ANY WARRANTY; without even the implied warranty of
10//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11//GNU General Public License for more details.
12//
13//You should have received a copy of the GNU General Public License
14//along with bertini2/nag_algorithms/zero_dim_solve.hpp. If not, see <http://www.gnu.org/licenses/>.
15//
16// Copyright(C) Bertini2 Development Team
17//
18// See <http://www.gnu.org/licenses/> for a copy of the license,
19// as well as COPYING. Bertini2 is provided with permitted
20// additional terms in the b2/licenses/ directory.
21
27
28#pragma once
29
31#include "bertini2/eigen_extensions.hpp" // bertini::IsDistinct (issues #302, #304)
32
34#include "bertini2/tracking.hpp"
37
40
44#include "bertini2/system/start_base.hpp" // start_system::StartSystem + StartSystemFactory / MakeStartFactory
45#include "bertini2/parallel.hpp"
52#include <algorithm>
53#include <chrono>
54
56#include <cstdlib>
57#include <limits>
58#include <mutex>
59#include <iostream>
60#include <map>
61
62
63namespace bertini {
64
65 // forward-declare the interim default start system so ZeroDimSolver's default factory argument
66 // (MakeStartFactory<TotalDegreeBinomial>) can name it; the concrete type rides in via start_systems.hpp
67 // at every call site that actually constructs a ZeroDimSolver.
68 namespace start_system { class TotalDegreeBinomial; }
69
70 namespace algorithm {
71
72
82template<typename TrackerType, typename EndgameType, typename SystemType>
83struct HomotopySolver;
84
89template<typename TrackerType, typename EndgameType, typename SystemType>
90struct ZeroDimSolver;
91
92
93
99template<typename TrackerType, typename EndgameType, typename SystemType>
115
116
118struct AnyZeroDim : public virtual AnyAlgorithm
119{
121 virtual void WriteMainData(std::ostream& out) const = 0;
123 virtual void WriteRawData(std::ostream& out) const = 0;
124 // Bertini 1.7-compatible machine-readable solution files (count-led coordinate blocks).
126 virtual void WriteFiniteSolutions(std::ostream& out) const = 0;
128 virtual void WriteRealFiniteSolutions(std::ostream& out) const = 0;
130 virtual void WriteNonsingularSolutions(std::ostream& out) const = 0;
132 virtual void WriteSingularSolutions(std::ostream& out) const = 0;
134 virtual void WriteRawSolutions(std::ostream& out) const = 0;
136 virtual void ApplyParsedConfigs(std::string const& config_str) = 0;
139 virtual void RecordToPath(std::string const& /*path*/) {}
141 virtual std::string RecordsRunIdentity() const { return {}; }
144 virtual void AppendRecordJson(std::string const& /*record_json*/) {}
148 virtual std::string PutRecordsDefinition(std::string const& /*content*/,
149 std::string const& /*kind*/,
150 std::string const& /*label*/ = {}) { return {}; }
151 virtual ~AnyZeroDim() = default;
152};
153
154
155
156
159
160
162
165{
166
170
171 std::chrono::system_clock::time_point start_time;
172 std::chrono::microseconds elapsed_time;
173};
174
175
177template<typename ComplexT>
179{
181
182 // only vaguely metadata. artifacts of randomness or ordering
185
187 bool precision_changed = false;
190 double path_time_seconds = 0.0;
191 unsigned num_successful_steps = 0;
192 unsigned num_failed_steps = 0;
201
204
205
216 // Two accuracy estimates, and each says in its NAME which coordinates it is in (ADR-0069).
217 // Both are the distance between the endgame's last two approximations of the root. The
218 // internal one is what the endgame's convergence test compares against final_tolerance;
219 // it reads as a number of digits. The user one is the absolute error in the units of
220 // the caller's own variables. There is deliberately no field named plain
221 // `accuracy_estimate`: a reader takes that to be in their own coordinates, and it was not.
224 unsigned cycle_num;
225 // Honest precision/accuracy of the computed solution, as DIGIT COUNTS. precision_digits is the
226 // working precision the endgame actually finished in (DoublePrecision() ~16 for a path that stayed in
227 // the adaptive-numeric-type endgame's hardware-double fast lane; the mpfr precision for one that
228 // escalated). accuracy_digits is how many of those digits are trustworthy, from the convergence
229 // agreement: floor(-log10(accuracy_estimate_internal_coords)), clamped to [0, precision_digits]. Read together:
230 // "computed in precision_digits digits, good to accuracy_digits of them."
231 unsigned precision_digits = 0;
232 unsigned accuracy_digits = 0;
234
235
238
239 int multiplicity = 1;
241 bool is_real = false;
242 bool is_finite = false;
243 bool is_singular = false;
249 // nonsolution flag: a finite, successful endpoint that is NOT a solution of the actual target
250 // system -- a nonsolution. ZeroDimSolver sets this when it squares up an over-determined system:
251 // the randomized square system has extraneous roots that satisfy the random combinations but not
252 // the original equations. Orthogonal to is_finite (a nonsolution is finite); the finite / real /
253 // singular accessors exclude nonsolutions, and they are exposed on their own (Nonsolutions()).
254 // Load-bearing for the regeneration cascade, which must identify and discard nonsolutions.
255 bool is_nonsolution = false;
256
259 bool result =
260 this->path_index == other.path_index
261 && this->solution_index == other.solution_index
262 && this->precision_changed == other.precision_changed
263 && this->time_of_first_prec_increase == other.time_of_first_prec_increase
264 && this->max_precision_used == other.max_precision_used
265 && this->pre_endgame_success_code == other.pre_endgame_success_code
266 && this->condition_number == other.condition_number
267 && this->newton_residual == other.newton_residual
268 && this->final_time_used == other.final_time_used
269 && this->accuracy_estimate_internal_coords == other.accuracy_estimate_internal_coords
270 && this->accuracy_estimate_user_coords == other.accuracy_estimate_user_coords
271 && this->cycle_num == other.cycle_num
272 && this->precision_digits == other.precision_digits
273 && this->accuracy_digits == other.accuracy_digits
274 && this->endgame_success_code == other.endgame_success_code
275 && this->function_residual == other.function_residual
276 && this->multiplicity == other.multiplicity
277 && this->multiplicity_representative == other.multiplicity_representative
278 && this->is_real == other.is_real
279 && this->is_finite == other.is_finite
280 && this->is_singular == other.is_singular
281 && this->is_nonsolution == other.is_nonsolution
282 && this->crossing_unresolved == other.crossing_unresolved
283 && this->num_successful_steps == other.num_successful_steps
284 && this->num_failed_steps == other.num_failed_steps
285 && this->wall_clock_limit_seconds == other.wall_clock_limit_seconds
286 && this->singular_values.size() == other.singular_values.size()
287 && (this->singular_values.size() == 0
288 || (this->singular_values.array() == other.singular_values.array()).all())
289 && this->latest_path_point.size() == other.latest_path_point.size()
290 && (this->latest_path_point.size() == 0
291 || (this->latest_path_point.array() == other.latest_path_point.array()).all())
292 ;
293
294 return result; }
295};
296
298// this is for interoperability with vectors of these in the Python bindings, for better or for worse.
299template<typename NumT>
300std::ostream& operator<<(std::ostream & out, const SolutionMetaData<NumT> & meta){
301 out << "path_index = " << meta.path_index << std::endl;
302 out << "solution_index = " << meta.solution_index << std::endl;
303
304 out << "precision_changed = " << meta.precision_changed << std::endl;
305 out << "time_of_first_prec_increase = " << meta.time_of_first_prec_increase << std::endl;
306 out << "max_precision_used = " << meta.max_precision_used << std::endl;
307 out << "path_time_seconds = " << meta.path_time_seconds << std::endl;
308 out << "num_successful_steps = " << meta.num_successful_steps << std::endl;
309 out << "num_failed_steps = " << meta.num_failed_steps << std::endl;
310 out << "wall_clock_limit_seconds = " << meta.wall_clock_limit_seconds << std::endl;
311 out << "latest_path_point = " << meta.latest_path_point.transpose() << std::endl;
312
313 out << "pre_endgame_success_code = " << meta.pre_endgame_success_code << std::endl;
314
315 out << "condition_number = " << meta.condition_number << std::endl;
316 out << "singular_values = " << meta.singular_values.transpose() << std::endl;
317 out << "newton_residual = " << meta.newton_residual << std::endl;
318 out << "final_time_used = " << meta.final_time_used << std::endl;
319 out << "accuracy_estimate_internal_coords = " << meta.accuracy_estimate_internal_coords << std::endl;
320 out << "accuracy_estimate_user_coords = " << meta.accuracy_estimate_user_coords << std::endl;
321 out << "precision_digits = " << meta.precision_digits << std::endl;
322 out << "accuracy_digits = " << meta.accuracy_digits << std::endl;
323 out << "cycle_num = " << meta.cycle_num << std::endl;
324 out << "endgame_success_code = " << meta.endgame_success_code << std::endl;
325
326 out << "function_residual = " << meta.function_residual << std::endl;
327
328 out << "multiplicity = " << meta.multiplicity << std::endl;
329 out << "multiplicity_representative = " << meta.multiplicity_representative << std::endl;
330 out << "is_real = " << meta.is_real << std::endl;
331 out << "is_finite = " << meta.is_finite << std::endl;
332 out << "is_singular = " << meta.is_singular << std::endl;
333 out << "is_nonsolution = " << meta.is_nonsolution << std::endl;
334 out << "crossing_unresolved = " << meta.crossing_unresolved << std::endl;
335
336 return out;
337}
338
339
341template<typename ComplexT>
343{
345
349 // The precision the tracker was actually using when it reached the endgame boundary.
350 // Carried explicitly (rather than inferred from Precision(path_point)) because the
351 // path_point is always stored as the tracker's BaseComplexT (multiprecision for AMP);
352 // a path that tracked in double gets widened on output, so its mantissa precision no
353 // longer reflects the precision that was in use. The endgame should resume at this
354 // precision. See zero_dim_solve TrackSinglePathDuringEG.
356
358 EGBoundaryMetaData() = default;
359 EGBoundaryMetaData(EGBoundaryMetaData const&) = default;
360 EGBoundaryMetaData& operator=(EGBoundaryMetaData const&) = default;
363 EGBoundaryMetaData(Vec<ComplexT> const& pt, SuccessCode const& code, RealT const& ss, unsigned prec) :
365 {}
366
369 bool result =
370 this->path_point == other.path_point
371 && this->success_code == other.success_code
372 && this->last_used_stepsize == other.last_used_stepsize
373 && this->precision == other.precision
374 ;
375
376 return result;
377 }
378};
379
381// this is for interoperability with vectors of these in the Python bindings, for better or for worse.
382template<typename NumT>
383std::ostream& operator<<(std::ostream & out, const EGBoundaryMetaData<NumT> & meta){
384 out << "path_point = " << meta.path_point << std::endl;
385 out << "success_code = " << meta.success_code << std::endl;
386 out << "last_used_stepsize = " << meta.last_used_stepsize << std::endl;
387 out << "precision = " << meta.precision << std::endl;
388 return out;
389}
390
404{
405 bool passed = true;
407 unsigned num_resolve_attempts = 0;
408 std::vector<unsigned long long> crossed_path_indices;
409};
410
412inline
413std::ostream& operator<<(std::ostream & out, const MidpathCheckReport & r){
414 out << "passed = " << std::boolalpha << r.passed << std::endl;
415 out << "num_crossings_detected = " << r.num_crossings_detected << std::endl;
416 out << "num_resolve_attempts = " << r.num_resolve_attempts << std::endl;
417 out << "crossed_path_indices = [";
418 for (size_t i = 0; i < r.crossed_path_indices.size(); ++i)
419 out << (i ? ", " : "") << r.crossed_path_indices[i];
420 out << "]" << std::endl;
421 return out;
422}
423
436{
437 unsigned long long num_paths_tracked = 0;
438 unsigned long long num_finite_solutions = 0;
439 unsigned long long num_finite_endpoints = 0;
440 unsigned long long num_diverged = 0;
441 unsigned long long num_failed = 0;
442 unsigned long long num_singular = 0;
443 unsigned long long num_real = 0;
444 unsigned long long num_nonsolutions = 0;
445 std::map<SuccessCode, unsigned long long> failures_by_reason;
447 unsigned max_precision_used = 0;
449 bool all_paths_resolved = false;
450};
451
456template<typename ComplexT>
458 MidpathCheckReport const& midpath)
459{
460 SolveReport r;
461 r.num_paths_tracked = metadata.size();
462 r.midpath = midpath;
463 double finite_distinct = 0;
464 for (auto const& m : metadata)
465 {
466 if (m.max_precision_used > r.max_precision_used)
467 r.max_precision_used = m.max_precision_used;
468
469 // A path the endgame's security check truncated near infinity (SecurityMaxNormReached) is
470 // diverging, not failing -- it is the security/max-norm heuristic catching a path on its way
471 // to infinity, exactly like a clean GoingToInfinity. (The endgame tests classify the two
472 // together too.) With infinite-path truncation on by default, these are expected, so they
473 // must not inflate num_failed / clear all_paths_resolved.
474 bool diverged = (m.endgame_success_code == SuccessCode::GoingToInfinity
475 || m.endgame_success_code == SuccessCode::SecurityMaxNormReached);
476 if (m.endgame_success_code == SuccessCode::Success)
477 {
478 if (m.is_nonsolution)
479 ++r.num_nonsolutions; // a nonsolution: finite, but not a solution of the target
480 else if (m.is_finite)
481 {
483 finite_distinct += 1.0 / m.multiplicity;
484 if (m.is_singular) ++r.num_singular;
485 if (m.is_real) ++r.num_real;
486 if (static_cast<double>(m.condition_number) > r.max_condition_number)
487 r.max_condition_number = static_cast<double>(m.condition_number);
488 }
489 else
490 diverged = true;
491 }
492
493 if (diverged)
494 ++r.num_diverged;
495 else if (m.endgame_success_code != SuccessCode::Success) // neither a solution nor a clean divergence
496 {
497 ++r.num_failed;
498 ++r.failures_by_reason[m.endgame_success_code];
499 }
500 }
501 r.num_finite_solutions = static_cast<unsigned long long>(finite_distinct + 0.5);
502 r.all_paths_resolved = (r.num_failed == 0) && midpath.passed;
503 return r;
504}
505
507inline
508std::ostream& operator<<(std::ostream & out, const SolveReport & r)
509{
510 out << "zero-dim solve -- " << r.num_paths_tracked << " paths tracked\n";
511 out << " finite solutions " << r.num_finite_solutions << " (distinct)\n";
512 out << " diverged " << r.num_diverged << "\n";
513 out << " FAILED " << r.num_failed;
514 if (r.num_failed)
515 {
516 out << " ";
517 bool first = true;
518 for (auto const& kv : r.failures_by_reason)
519 {
520 out << (first ? "" : ", ") << kv.second << " x " << kv.first;
521 first = false;
522 }
523 }
524 out << "\n ----\n";
525 out << " singular solutions " << r.num_singular << "\n";
526 out << " real solutions " << r.num_real << "\n";
527 if (r.num_nonsolutions)
528 out << " nonsolutions " << r.num_nonsolutions << " (finite, but not solutions of the target)\n";
529 out << " path crossings " << r.midpath.num_crossings_detected
530 << (r.midpath.passed ? " (resolved)" : " (UNRESOLVED)") << "\n";
531 out << " max condition num " << r.max_condition_number << "\n";
532 out << " max precision used " << r.max_precision_used << " digits\n";
533 out << " all paths resolved? " << (r.all_paths_resolved ? "yes" : "NO") << "\n";
534 return out;
535}
536
541 template<typename TrackerType, typename EndgameType, typename SystemType>
543 public virtual AnyZeroDim,
544 public detail::Configured<
545 typename AlgoTraits< HomotopySolver<TrackerType, EndgameType, SystemType>>::NeededConfigs>
546 {
547 // these usings are for getters in python
548 using TrackerT = TrackerType;
549 using EndgameT = EndgameType;
550 using SystemT = SystemType;
551 // the engine sees the start system only through the polymorphic base
554
555 // This engine emits its lifecycle events on the AnyZeroDim base, so a
556 // single observer type can watch any templated solver. Accept observers
557 // declared for AnyZeroDim (in addition to the exact concrete type).
558 bool ObservableIsA(std::type_index t) const override
559 {
560 return Observable::ObservableIsA(t) || t == std::type_index(typeid(AnyZeroDim));
561 }
562
563
564
565
569
571
573
574
579 using Config::Get;
580
581
586
587
590
591
592// a few more using statements
593
595
596 // --- system storage ---------------------------------------------------------------
597 // The engine holds the homotopy, start system, and target by REFERENCE; the caller
598 // (a user, or ZeroDimSolver) owns them and guarantees they outlive the engine. This
599 // references, not owned: the engine never forms its systems (ZeroDimSolver / the user does).
600 protected:
601 std::reference_wrapper<const SystemType> target_system_;
602 std::reference_wrapper<const StartSystemBaseT> start_system_;
603 std::reference_wrapper<const SystemType> homotopy_;
604
605 // Re-seat the system references. ZeroDimSolver calls this after an MPI broadcast
606 // re-materializes rank 0's authoritative systems into its owned storage.
608 void ResetSystems(SystemType const& hom, StartSystemBaseT const& start, SystemType const& target)
609 {
610 homotopy_ = std::cref(hom);
611 start_system_ = std::cref(start);
612 target_system_ = std::cref(target);
613 }
614
615#ifdef BERTINI2_HAVE_MPI
616 // Hook: install rank 0's authoritative systems on every rank before a distributed solve.
617 // A user-supplied homotopy (plain HomotopySolver) is the user's responsibility across
618 // ranks, so this is a no-op here; ZeroDimSolver, which owns its systems, overrides it.
619 virtual void DistributeSystems(MPI_Comm /*comm*/) {}
620#endif
621
622 public:
624 const SystemType& TargetSystem() const { return target_system_.get(); }
626 const StartSystemBaseT& StartSystem() const { return start_system_.get(); }
628 const SystemType& Homotopy() const { return homotopy_.get(); }
629
630
631
633
644 HomotopySolver(SystemType const& target, StartSystemBaseT const& start, SystemType const& homotopy)
645 : target_system_(std::cref(target)), start_system_(std::cref(start)), homotopy_(std::cref(homotopy)),
646 tracker_(homotopy), endgame_(tracker_)
647 {
648 DefaultSetup();
649 }
650
651
688
692 void Run() override
693 {
694 // TODO(Phase 2 Python): AnyZeroDim lacks GetSolutions() -- solutions are only
695 // accessible via the concrete type's member arrays or WriteMainData().
696 // Resolve when designing Python bindings; the right approach depends on whether
697 // Python holds AnyZeroDim or the concrete ZeroDim<...> type directly.
698#ifdef BERTINI2_HAVE_MPI
699 if (parallel::Size() > 1)
700 {
701 RunParallel(parallel::WorldComm());
702 return;
703 }
704#endif
705 Solve();
706 }
707
708#ifdef BERTINI2_HAVE_MPI
721 void RunParallel(MPI_Comm comm)
722 {
725
726 ScopedSessionState const session_state_guard{}; // a solve leaves the session as it found it
727
728 mpfr_free_cache(); // reproducibility: clear this rank's mpfr constant cache (see Solve())
729
731
732 // Each rank built its homotopy independently in the constructor -- with its OWN
733 // random patch, start-system coefficients, and gamma -- so a given path index would
734 // mean a different path on each worker, and the manager would gather a scrambled,
735 // mostly-wrong solution set. Make rank 0 the single authoritative source: broadcast
736 // its actual target system, start system, and homotopy to every rank. These are all
737 // exact (rational coefficients + integer/rational patch), so serialization carries
738 // them bit-for-bit -- there is no per-rank re-derivation that could drift, and the
739 // distributed homotopy is identical to rank 0's serial one. We also broadcast rank
740 // 0's RNG seed so any randomness drawn later (e.g. in the endgame) is consistent; the
741 // per-path tracking RNG is already deterministic in the path index (ReseedThisThread).
742 {
743 unsigned long seed = parallel::IsManager() ? GetGlobalSeed() : 0ul;
744 MPI_Bcast(&seed, 1, MPI_UNSIGNED_LONG, 0, comm);
745 SetGlobalSeed(seed);
746
747 // Install rank 0's authoritative systems on every rank. The plain engine over a
748 // user-supplied homotopy does nothing here (the user owns cross-rank consistency);
749 // ZeroDimSolver, which owns its systems, overrides DistributeSystems to broadcast
750 // rank 0's target / start / homotopy and re-seat the engine's references.
751 DistributeSystems(comm);
752
754 GetTracker().SetSystem(Homotopy());
755 }
756
757 PreSolveChecks();
758 PreSolveSetup();
759
760 // threads per rank: the same rule as the standalone solve (BERTINI_NUM_THREADS,
761 // then num_threads, then every CPU this rank may run on)
762 const int n_threads = static_cast<int>(
763 parallel::EffectiveThreadCount(this->template Get<ZeroDimConf>().num_threads));
764
765 // One round = dispatch a set of path indices, each executed as a WHOLE path
766 // (pre-endgame + endgame) by a worker. Round 0 is every path; later rounds re-run
767 // only the crossed paths with escalated settings -- the re-track is path-independent,
768 // so it rides the same demand-driven worker pool (no idle-manager bottleneck).
770 {
771 // Rank 0 computes every start point once, authoritatively, and ships each to the
772 // worker that tracks it. Cached so re-track rounds reuse the identical points.
773 std::vector<Vec<BaseComplexT>> start_points(num_start_points_);
774 for (decltype(num_start_points_) ii{0}; ii < num_start_points_; ++ii)
775 start_points[ii] = ComputeStartPoint(static_cast<SolnIndT>(ii));
776
777 auto run_round = [&](std::queue<Task>& q)
778 {
779 parallel::RunManagerLoop<Task, Result>(comm, q,
780 [this](Result const& r){ StoreFullPathResult(r); });
781 };
782
783 // the records seam (ADR-0046): the manager is the sole writer; recall what
784 // is already recorded and dispatch only the rest
786 std::vector<SolnIndT> indices_to_run;
787 for (decltype(num_start_points_) ii{0}; ii < num_start_points_; ++ii)
788 indices_to_run.push_back(static_cast<SolnIndT>(ii));
789 if (records_)
790 {
792 indices_to_run = RecallRecordedPaths(indices_to_run);
793 }
794
795 std::queue<Task> queue;
796 for (auto idx : indices_to_run)
797 queue.push(Task{ idx, start_points[idx] });
798 run_round(queue);
799
800 // Midpath/crossing check on the collected boundary points, then bounded parallel
801 // resolve rounds. A continue/stop flag is broadcast to the workers each round so
802 // they know whether to run again (and escalate their settings first).
804 midpath_report_ = MidpathCheckReport{};
805 midpath_report_.passed = passed;
806 for (auto const& v : midpath_.GetCrossedPaths())
807 midpath_report_.crossed_path_indices.push_back(v.index());
808 midpath_report_.num_crossings_detected =
809 static_cast<unsigned>(midpath_report_.crossed_path_indices.size());
810
811 const auto max_attempts = this->template Get<ZeroDimConf>().max_num_crossed_path_resolve_attempts;
812 unsigned num_resolve_attempts = 0;
813 while (true)
814 {
815 int keep_going = (!passed && num_resolve_attempts < max_attempts) ? 1 : 0;
816 MPI_Bcast(&keep_going, 1, MPI_INT, 0, comm);
817 if (!keep_going)
818 break;
819
820 std::queue<Task> redo;
821 for (auto const& v : midpath_.GetCrossedPaths())
822 if (v.rerun())
823 {
824 auto idx = static_cast<SolnIndT>(v.index());
825 redo.push(Task{ idx, start_points[idx] });
826 }
827 run_round(redo);
828
830 ++num_resolve_attempts;
831 }
832 midpath_report_.num_resolve_attempts = num_resolve_attempts;
833 midpath_report_.passed = passed;
834 FlagUnresolvedCrossings(passed);
835 RecordMidpathOutcome();
836 if (!passed)
837 std::cerr << "warning: " << midpath_report_.num_crossings_detected
838 << " path crossing(s) detected at the endgame boundary remained unresolved "
839 << "after " << num_resolve_attempts << " re-track attempt(s); "
840 << "the affected solutions may be wrong. Consider a higher-order predictor "
841 << "or a tighter tracking tolerance. See EndgameBoundaryMetadata()." << std::endl;
842
843 PostEGAction();
844
846 }
847 else // worker
848 {
849 auto run_worker_round = [&]()
850 {
851 if (n_threads <= 1)
852 {
853 // Single-threaded worker: execute whole paths on the rank's member
854 // tracker/endgame directly, from rank 0's authoritative start point.
855 parallel::RunWorkerLoop<Task, Result>(comm,
856 [this](Task const& task){ ExecuteOnePath(MemberDuringEGContext(), static_cast<SolnIndT>(task.path_index), task.start_point); },
857 [this](Task const& task) -> Result { return PackFullPathResult(static_cast<SolnIndT>(task.path_index)); });
858 }
859 else
860 {
861 // Threaded worker: each thread owns a self-contained PathThreadState clone
862 // so concurrent whole-path execution shares no mutable state. Cloned per
863 // round so it inherits any escalation (predictor bump) applied to the rank's
864 // member tracker between rounds.
865 auto state_factory = [this]() -> std::unique_ptr<PathThreadState>
866 {
867 // Clone(), not copy: System copies are SHALLOW (shared expression-tree
868 // nodes whose value caches mutate on Eval); Clone() deep-copies the tree.
869 // Trackers/Endgames have no default ctor, so aggregate-init via new.
870 std::unique_ptr<PathThreadState> s(
871 new PathThreadState{ Clone(GetTracker().GetSystem()), Clone(TargetSystem()),
872 GetTracker(), GetEndgame(), {} });
873 s->tracker.SetSystem(s->sys);
874 s->endgame.SetTracker(s->tracker);
875 SetThreadPrecision(this->template Get<ZeroDimConf>().initial_ambient_precision);
876 return s;
877 };
878
879 auto track_fn = [this](std::unique_ptr<PathThreadState>& state, Task const& task) -> Result
880 {
881 auto idx = static_cast<SolnIndT>(task.path_index);
882 ExecuteOnePath(state->Context(), idx, task.start_point);
883 return PackFullPathResult(idx);
884 };
885
886 parallel::RunWorkerLoopThreaded<Task, Result>(
887 comm, state_factory, track_fn, n_threads);
888 }
889 }; // run_worker_round
890
891 while (true)
892 {
893 run_worker_round();
894
895 int keep_going = 0;
896 MPI_Bcast(&keep_going, 1, MPI_INT, 0, comm);
897 if (!keep_going)
898 break;
899
900 // Match the escalation the serial RunMidpathResolution applies between
901 // re-track passes: tighten midpath_retrack_tolerance_ (read by ExecuteOnePath)
902 // and bump a low-order predictor on this rank's member tracker, so the next
903 // round's thread clones inherit it.
904 EscalateRetrackSettings();
905 }
906 }
907 }
908#endif // BERTINI2_HAVE_MPI
909
910 // Definitions are out-of-line at the bottom of this file, after
911 // output.hpp is included (avoiding a circular-include chicken-and-egg).
912 void WriteMainData(std::ostream& out) const override;
913 void WriteRawData(std::ostream& out) const override;
914 void WriteFiniteSolutions(std::ostream& out) const override;
915 void WriteRealFiniteSolutions(std::ostream& out) const override;
916 void WriteNonsingularSolutions(std::ostream& out) const override;
917 void WriteSingularSolutions(std::ostream& out) const override;
918 void WriteRawSolutions(std::ostream& out) const override;
919 void ApplyParsedConfigs(std::string const& config_str) override;
920
921 virtual ~HomotopySolver() = default;
923 // NOTE: feasibility checking (ConsistencyCheck) and start-system construction live in
924 // ZeroDimSolver, which owns the systems; the engine is handed a ready homotopy.
925
926
934
935
936
937
938
939
942 {
943 // The systems are already built and referenced (the engine does not form them);
944 // just read off the number of start points the start system will produce.
945 num_start_points_ = StartSystem().NumStartPoints(); // populate the internal variable
946 }
947
948
949
954 {
955 // this code can be made generic using Boost.Hana.
956 // see https://stackoverflow.com/questions/28764085/how-to-create-an-element-for-each-type-in-a-typelist-and-add-it-to-a-vector-in-c,
957 // for example
958 //
959 // all that would need to be done is to extract a hana::tuple_t from the
960 // typelist contained in this::Config, and then hana::for_each() over it.
961
962 this->template Set<Tolerances>(Tolerances());
963 this->template Set<PostProcessing>(PostProcessing());
964 // ZeroDimConfig is not templated on the complex type, so it cannot pick its own
965 // per-type ambient-precision default. Set it here, where the tracking type is known.
966 ZeroDimConf zdc;
968 this->template Set<ZeroDimConf>(zdc);
969 this->template Set<AutoRetrack>(AutoRetrack());
970 }
971
974 {
976 }
977
979 const auto& MidpathRetrackTol() const
980 {
982 }
983
995
997 unsigned StartPointPrecision() const
998 {
1000 }
1001
1002
1010
1011
1020 {
1021 this->template Set<MidPathConfig>(mp);
1022 midpath_.Set(mp);
1023 }
1024
1031 {
1032 tracker_.SetSystem(Homotopy());
1033 // the solver sets what it owns -- the phase tracking tolerance -- and leaves the
1034 // tracker's own settings alone, so a predictor or truncation threshold set on the
1035 // tracker survives DefaultSetup
1036 tracker_.Setup(tracker_.GetPredictor(),
1037 this->template Get<Tolerances>().newton_before_endgame,
1039
1040 tracker_.PrecisionSetup(PrecisionConfig(Homotopy()));
1041 }
1042
1043
1053 void SetTracker(TrackerType const& new_tracker)
1054 {
1055 tracker_ = new_tracker;
1056 endgame_.SetTracker(tracker_);
1057 }
1058
1064 const TrackerType & GetTracker() const
1065 {
1066 return tracker_;
1067 }
1068
1074 TrackerType & GetTracker()
1075 {
1076 return tracker_;
1077 }
1078
1079
1081
1088 void SetEndgame(EndgameType const& new_endgame)
1089 {
1090 endgame_ = new_endgame;
1091 }
1092
1098 const EndgameType & GetEndgame() const
1099 {
1100 return endgame_;
1101 }
1102
1108 EndgameType & GetEndgame()
1109 {
1110 return endgame_;
1111 }
1112
1113
1115 template<typename T>
1116 const T & GetFromEndgame() const
1117 {
1118 return endgame_.template Get<T>();
1119 }
1120
1122 template<typename T>
1123 void SetToEndgame(T const& t)
1124 {
1125 endgame_.Set(t);
1126 }
1127
1129
1130
1131
1145 void Solve()
1146 {
1147 // Reproducibility: clear this thread's mpfr constant cache (pi, etc.) at the solve
1148 // boundary. mpfr caches transcendental constants at the precision last requested; the
1149 // Cauchy endgame needs pi (roots of unity), so a high-precision solve leaves pi cached
1150 // at high precision. A subsequent lower-precision solve would otherwise reuse that
1151 // cached value rounded down -- differing by ~1 ULP from a freshly-computed
1152 // low-precision pi -- making a solve's behaviour depend on what ran before it in the
1153 // process (non-reproducible). Clearing here makes each solve independent of history.
1154 // Cheap (one constant recompute per solve); thread-local (each worker thread clears its
1155 // own when it starts a solve).
1156 mpfr_free_cache();
1157
1158 ScopedSessionState const session_state_guard{}; // a solve leaves the session as it found it
1159
1161
1162 PreSolveChecks();
1163
1164 PreSolveSetup();
1165
1166 // the whole-solve wall-clock budget, counted from here; none unless asked for
1167 solve_deadline_.reset();
1168 if (auto const limit = this->template Get<ZeroDimConf>().max_solve_wall_clock_duration; limit > 0)
1169 solve_deadline_ = std::chrono::steady_clock::now()
1170 + std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(limit));
1171
1172 this->NotifyObservers(AlgorithmStarted<AnyZeroDim>(*this));
1173
1174 // Speculative-full-path model: carry every path all the way through (pre-endgame +
1175 // endgame) as one unit, then detect crossings from the collected boundary points and
1176 // re-run any crossed path in full. This is the same shape the distributed solve uses
1177 // (one worker per whole path), so serial and distributed share the per-path primitive
1178 // -- including computing the start point via the same ComputeStartPoint.
1179
1180 // Compute every start point once, up front (deterministic per index, as the MPI
1181 // manager does). The threaded dispatch ships each worker its start point, and the
1182 // serial path / crossed-path re-tracks reuse the identical points from here.
1183 std::vector<Vec<BaseComplexT>> start_points(num_start_points_);
1184 std::vector<SolnIndT> all_indices(num_start_points_);
1185 for (decltype(num_start_points_) ii{0}; ii < num_start_points_; ++ii)
1186 {
1187 auto idx = static_cast<SolnIndT>(ii);
1188 start_points[ii] = ComputeStartPoint(idx);
1189 all_indices[ii] = idx;
1190 }
1191
1192 // the records seam (ADR-0046): ensure the run is on record, recall any paths
1193 // already recorded (solve() is ensure-answered), and compute only the rest
1195 std::vector<SolnIndT> indices_to_run = all_indices;
1196 if (records_)
1197 {
1199 // which recorded outcomes are reused is RecallPolicy's business, applied inside
1200 indices_to_run = RecallRecordedPaths(all_indices);
1201 }
1202
1203 // num_threads: 0 = auto (every CPU this process may run on), 1 = serial, N = N
1204 // threads; BERTINI_NUM_THREADS overrides. n_threads <= 1 takes the pool-free
1205 // serial path.
1206 const unsigned n_threads =
1207 parallel::EffectiveThreadCount(this->template Get<ZeroDimConf>().num_threads);
1208
1209 if (n_threads <= 1)
1210 {
1211 for (auto idx : indices_to_run)
1212 {
1214 break; // ExecuteOnePath would no-op; this just stops asking
1215 ExecuteOnePath(MemberDuringEGContext(), idx, start_points[idx]);
1216 // the serial path installs results directly (no StoreFullPathResult),
1217 // so emit its record here
1219 }
1220 }
1221 else
1222 {
1223 RunPathsThreaded(indices_to_run, start_points, n_threads);
1224 }
1225
1226 TallyStoppedSolve(indices_to_run);
1227
1228 // Crossed-path re-tracks run on the main thread against the (escalated) member
1229 // tracker. They are typically rare (often none); running them in parallel too is a
1230 // deferred optimization.
1231 //
1232 // Skipped entirely when the solve was stopped: the midpath check compares paths
1233 // against each other at the endgame boundary, and a set where an arbitrary
1234 // number of paths never ran has nothing meaningful to compare. Re-tracking
1235 // under a standing stop request would also just abandon each path again.
1236 if (!stopped_early_)
1237 RunMidpathResolution([this, &start_points](SolnIndT idx){
1238 ExecuteOnePath(MemberDuringEGContext(), idx, start_points[idx]);
1239 RecordCompletedPath(PackFullPathResult(idx)); // a re-track appends a fresh record (last wins)
1240 });
1241
1242 PostEGAction();
1243
1245
1246 this->NotifyObservers(AlgorithmComplete<AnyZeroDim>(*this));
1247 }
1248
1249
1250
1251
1263 const auto& SolutionsUserCoords() const
1264 {
1266 {
1267 solutions_user_coords_.clear();
1269 for (const auto& s : solutions_post_endgame_)
1270 {
1271 // A path that failed before or during the endgame leaves its endpoint
1272 // slot default-constructed (zero coordinates); keep index alignment with
1273 // SolutionMetadata (output filters on it) by emitting an empty
1274 // placeholder rather than trying to dehomogenize an unset point.
1275 if (s.size() == 0)
1276 solutions_user_coords_.emplace_back();
1277 else
1278 solutions_user_coords_.push_back(this->TargetSystem().DehomogenizePoint(s));
1279 }
1281 }
1283 }
1284
1296 const auto& SolutionsInternalCoords() const
1297 {
1299 }
1300
1308 template<typename Pred>
1309 SolnCont<Vec<BaseComplexT>> SolutionsWhere(Pred pred, bool user_coords = true,
1310 bool merge_multiplicities = false) const
1311 {
1312 auto const& sols = user_coords ? SolutionsUserCoords() : SolutionsInternalCoords();
1313 auto const& md = SolutionMetadata();
1315 for (size_t i = 0; i < md.size() && i < sols.size(); ++i)
1316 {
1317 // merge_multiplicities: keep only the chosen representative of each multiplicity
1318 // cluster, so a multiplicity-m solution appears once rather than m times. A no-op for
1319 // simple / at-infinity / failed endpoints (each is its own representative). Issue #299.
1320 if (merge_multiplicities && !md[i].multiplicity_representative)
1321 continue;
1322 if (pred(md[i]))
1323 out.push_back(sols[i]);
1324 }
1325 return out;
1326 }
1327
1334 SolnCont<Vec<BaseComplexT>> FiniteSolutions(bool user_coords = true, bool merge_multiplicities = false) const
1335 {
1336 return SolutionsWhere([](auto const& m){
1337 return m.endgame_success_code == SuccessCode::Success && m.is_finite && !m.is_nonsolution; }, user_coords, merge_multiplicities);
1338 }
1339
1341 SolnCont<Vec<BaseComplexT>> RealSolutions(bool user_coords = true, bool merge_multiplicities = false) const
1342 {
1343 return SolutionsWhere([](auto const& m){
1344 return m.endgame_success_code == SuccessCode::Success && m.is_finite && !m.is_nonsolution && m.is_real; }, user_coords, merge_multiplicities);
1345 }
1346
1348 SolnCont<Vec<BaseComplexT>> NonsingularSolutions(bool user_coords = true, bool merge_multiplicities = false) const
1349 {
1350 return SolutionsWhere([](auto const& m){
1351 return m.endgame_success_code == SuccessCode::Success && m.is_finite && !m.is_nonsolution && !m.is_singular; }, user_coords, merge_multiplicities);
1352 }
1353
1355 SolnCont<Vec<BaseComplexT>> SingularSolutions(bool user_coords = true, bool merge_multiplicities = false) const
1356 {
1357 return SolutionsWhere([](auto const& m){
1358 return m.endgame_success_code == SuccessCode::Success && m.is_finite && !m.is_nonsolution && m.is_singular; }, user_coords, merge_multiplicities);
1359 }
1360
1367 SolnCont<Vec<BaseComplexT>> Nonsolutions(bool user_coords = true) const
1368 {
1369 return SolutionsWhere([](auto const& m){
1370 return m.endgame_success_code == SuccessCode::Success && m.is_nonsolution; }, user_coords);
1371 }
1372
1384 SolnCont<Vec<BaseComplexT>> InfiniteSolutions(bool user_coords = true) const
1385 {
1386 return SolutionsWhere([](auto const& m){ return !m.is_finite; }, user_coords);
1387 }
1388
1392 const auto& SolutionMetadata() const
1393 {
1395 }
1396
1412 {
1413 return endgame_.FinalTolerance();
1414 }
1415
1426 {
1427 return endgame_.FinalTolerance() *
1428 this->template Get<PostProcessing>().same_point_tolerance_multiplier;
1429 }
1430
1452 bool user_coords = true, bool representatives_only = true) const
1453 {
1454 auto const& sols = user_coords ? SolutionsUserCoords() : SolutionsInternalCoords();
1455 auto const& md = SolutionMetadata();
1456
1457 std::vector<size_t> matches;
1458 for (size_t i = 0; i < md.size() && i < sols.size(); ++i)
1459 {
1460 if (representatives_only && !md[i].multiplicity_representative)
1461 continue;
1462 if (!bertini::IsDistinct(sols[i], point, tol))
1463 matches.push_back(i);
1464 }
1465
1466 if (matches.empty())
1467 throw std::runtime_error("metadata_for: no solution matches the given point within tol");
1468
1469 // More than one representative among the matches => tol grouped two distinct clusters.
1470 // With representatives_only (the default) and the default tol this cannot happen -- distinct
1471 // representatives are at least SamePointTolerance apart and the window is smaller -- so it
1472 // only fires for a deliberately coarse user-supplied tol, where it is the honest answer.
1473 std::vector<size_t> reps;
1474 for (auto i : matches)
1475 if (md[i].multiplicity_representative)
1476 reps.push_back(i);
1477 if (reps.size() > 1)
1478 throw std::runtime_error("metadata_for: the point matches more than one distinct solution cluster; reduce tol");
1479 if (reps.size() == 1)
1480 return md[reps.front()];
1481 // No representative matched (only reachable with representatives_only=false, when the point
1482 // is a non-representative copy whose representative fell outside tol); return the matched
1483 // copy's own record (it still carries the cluster's multiplicity).
1484 return md[matches.front()];
1485 }
1486
1503 std::vector<SolutionMetaDataT> CoincidentMetadataForPoint(Vec<BaseComplexT> const& point, NumErrorT tol, bool user_coords = true) const
1504 {
1505 auto const& sols = user_coords ? SolutionsUserCoords() : SolutionsInternalCoords();
1506 auto const& md = SolutionMetadata();
1507
1508 // Find the representative NEAREST the point (within tol). Nearest, not "all within tol", so
1509 // the cluster is identified unambiguously even for a coarse tol.
1510 size_t rep = md.size();
1511 NumErrorT best = std::numeric_limits<NumErrorT>::infinity();
1512 for (size_t i = 0; i < md.size() && i < sols.size(); ++i)
1513 {
1514 if (!md[i].multiplicity_representative)
1515 continue;
1516 NumErrorT d = static_cast<NumErrorT>((sols[i] - point).template lpNorm<Eigen::Infinity>());
1517 if (d <= tol && d < best) { best = d; rep = i; }
1518 }
1519 if (rep == md.size())
1520 throw std::runtime_error("metadata_for: no solution matches the given point within tol");
1521
1522 // Gather the whole cluster: every endpoint the solver deemed the same solution as the
1523 // representative (within the same-point clustering tolerance).
1524 const NumErrorT same_tol = SamePointTolerance();
1525 std::vector<SolutionMetaDataT> out;
1526 for (size_t i = 0; i < md.size() && i < sols.size(); ++i)
1527 if (!bertini::IsDistinct(sols[i], sols[rep], same_tol))
1528 out.push_back(md[i]);
1529 return out;
1530 }
1531
1535 const auto& EndgameBoundarySolutions() const
1536 {
1538 }
1539
1548 {
1549 return midpath_report_;
1550 }
1551
1562
1563 private:
1564
1568 void PreSolveChecks() const
1569 {
1571 throw std::runtime_error("start system has more solutions than container for results. I refuse to continue until this has been addressed.");
1572 }
1573
1574
1575 void PreSolveSetup()
1576 {
1577 // The midpath checker is a consumer of the solver's MidPathConfig, not a second
1578 // home for it, so whatever the caller last set on the solver takes effect here --
1579 // whether they named the config or reached the field through the flat surface.
1580 midpath_.Set(this->template Get<MidPathConfig>());
1581
1582 // Fixed-multiple precision: FixedPrecisionConfig.precision (on the tracker) is the
1583 // authoritative precision for the whole solve. Lift the tracker, the ambient/thread
1584 // precision, the start-point precision (via initial_ambient_precision), and the systems
1585 // all to that one value -- TrackerLoopInitialization requires them to agree. (Double is
1586 // fixed at 16 and adaptive manages its own precision, so neither enters here.)
1588 {
1589 auto const& fp = GetTracker().template Get<PrecisionConfig>();
1590 // Double validates (its PrecisionSetup throws on any precision other than 16);
1591 // fixed-multiple adopts the value into precision_.
1592 GetTracker().PrecisionSetup(fp);
1593 if constexpr (!std::is_same<BaseComplexT, complex_dbl>::value)
1594 if (fp.precision != 0)
1595 {
1596 ZeroDimConf zdc = this->template Get<ZeroDimConf>();
1597 zdc.initial_ambient_precision = fp.precision; // -> thread + start-point precision
1598 this->template Set<ZeroDimConf>(zdc);
1599 }
1600 }
1601
1602 auto num_as_size_t = static_cast<SolnIndT>(num_start_points_);
1603
1604 solution_final_metadata_.resize(num_as_size_t);
1605 solutions_at_endgame_boundary_.resize(num_as_size_t);
1606 solutions_post_endgame_.resize(num_as_size_t);
1607
1608 SetMidpathRetrackTol(this->template Get<Tolerances>().newton_before_endgame);
1609
1610 // Default the start-point precision to the initial ambient precision. A caller can
1611 // override it via SetStartPointPrecision (e.g. to carry a higher precision forward
1612 // from a previous solve whose output feeds this one).
1614 start_point_precision_ = this->template Get<ZeroDimConf>().initial_ambient_precision;
1615 }
1616
1623 struct BeforeEGContext
1624 {
1625 TrackerType& tracker;
1626 tracking::PathPrecisionRecorder<TrackerType>& path_prec;
1627 };
1628 struct DuringEGContext
1629 {
1630 // const ref: the residual/dehomogenize/precision calls are const-callable, and the
1631 // RefToGiven policy hands back a const target system. A mutable thread-owned clone
1632 // binds here too.
1633 SystemType const& target_sys;
1634 TrackerType& tracker;
1635 EndgameType& endgame;
1636 tracking::PathPrecisionRecorder<TrackerType>& path_prec;
1637 };
1638
1639 BeforeEGContext MemberBeforeEGContext()
1640 {
1641 return BeforeEGContext{ GetTracker(), path_prec_ };
1642 }
1643 DuringEGContext MemberDuringEGContext()
1644 {
1645 return DuringEGContext{ TargetSystem(), GetTracker(), GetEndgame(), path_prec_ };
1646 }
1647
1649 static void HarvestPathPrecision(SolutionMetaData<BaseComplexT>& smd,
1650 tracking::PathPrecisionRecorder<TrackerType> const& rec)
1651 {
1652 if (rec.DidPrecisionIncrease() && !smd.precision_changed)
1653 {
1654 smd.precision_changed = true;
1655 smd.time_of_first_prec_increase = rec.TimeOfIncrease();
1656 }
1657 using std::max;
1658 smd.max_precision_used = max(smd.max_precision_used, rec.MaxPrecision());
1659 }
1660
1671 Vec<BaseComplexT> ComputeStartPoint(SolnIndT soln_ind)
1672 {
1674 static std::mutex start_point_mutex;
1675 std::lock_guard<std::mutex> lock(start_point_mutex);
1676 return StartSystem().template StartPoint<BaseComplexT>(soln_ind);
1677 }
1678
1687 void ExecuteBeforeEG(BeforeEGContext ctx, SolnIndT soln_ind, Vec<BaseComplexT> const& start_point)
1688 {
1689 ReseedThisThread(static_cast<uint64_t>(soln_ind));
1690
1691 auto& smd = solution_final_metadata_[soln_ind];
1692 smd.path_index = soln_ind;
1693 smd.solution_index = soln_ind;
1694
1695 // The step tally spans the whole path -- this track and every sub-track the
1696 // endgame will issue -- so it starts here and is read after each stage.
1697 ctx.tracker.ResetCumulativeStepCounts();
1698
1699 // The wall-clock budget, likewise per path and spanning both stages: armed here as
1700 // a deadline (so the endgame's many sub-tracks share it), cleared when the path is
1701 // over. Recorded with the path whether or not it bites.
1702 auto const wall_clock_limit = this->template Get<ZeroDimConf>().max_path_wall_clock_duration;
1703 smd.wall_clock_limit_seconds = wall_clock_limit;
1704 ctx.tracker.ClearMaxWallClockTime();
1705 if (wall_clock_limit > 0)
1706 ctx.tracker.SetMaxWallClockDuration(std::chrono::duration<double>(wall_clock_limit));
1707 // the whole-solve deadline, when there is one and it comes first, is the one that binds
1708 if (solve_deadline_ && (!ctx.tracker.MaxWallClockTime() || *solve_deadline_ < *ctx.tracker.MaxWallClockTime()))
1709 ctx.tracker.SetMaxWallClockTime(*solve_deadline_);
1710
1711 auto initial_prec = this->template Get<ZeroDimConf>().initial_ambient_precision;
1712 // SetThreadPrecision: writes thread-local only, safe from concurrent threads.
1713 SetThreadPrecision(initial_prec);
1714
1715 // Draw the condition-number probe direction ONCE here, for the whole track of this
1716 // point (pre-endgame AND endgame). We have just reseeded this thread's RNG to a value
1717 // determined solely by the path index, so the probe is deterministic per path and
1718 // identical whether the path is tracked in a serial loop or on a distributed worker --
1719 // regardless of how the paths were split across ranks. It is NOT refreshed for the
1720 // endgame (which would churn the RNG across its sample-circle sub-tracks), so the same
1721 // direction persists start to finish.
1722 ctx.tracker.RefreshConditionDirection();
1723
1724 // The times are stored precision-free (mpq_rational); materialize them as the tracking
1725 // complex type at the current working precision.
1726 BaseComplexT t_start ( BaseRealT(this->template Get<ZeroDimConf>().start_time) );
1727 BaseComplexT t_endgame_boundary( BaseRealT(this->template Get<ZeroDimConf>().endgame_boundary) );
1728
1729 // The start point is supplied by the caller (computed once, authoritatively, via
1730 // ComputeStartPoint) rather than regenerated here. In a distributed solve rank 0
1731 // computes it and sends it with the task, so a worker never derives its own -- that,
1732 // plus authoritative pi (issue #156), is what makes the start point identical across
1733 // serial and distributed runs.
1734
1735 // Reset to the configured initial step size for this fresh path. In the
1736 // speculative-full-path model a path's endgame runs immediately before the next
1737 // path's pre-endgame tracking on the same tracker, and the endgame leaves
1738 // ReinitializeInitialStepSize(false) with a tiny step; without restoring it here the
1739 // next path would crawl from the start time with that tiny step (effectively a hang).
1740 ctx.tracker.ReinitializeInitialStepSize(true);
1741
1742 // Begin every AMP path in hardware double precision; the AMP step criteria escalate to
1743 // multiprecision only where a path actually needs it (and the endgame, which clears
1744 // this override below, manages its own precision). Total-degree start points are born
1745 // at LowestMultiplePrecision (the generator's arithmetic widens past the requested
1746 // digits, see issue #308), and initial_ambient_precision is itself a multiprecision
1747 // value, so without forcing double here the tracker starts -- and, in practice, stays
1748 // -- in multiprecision on every path, even trivially well-conditioned ones.
1749 if constexpr (tracking::TrackerTraits<TrackerType>::IsAdaptivePrec)
1750 ctx.tracker.SetStartPrecision(DoublePrecision());
1751
1752 Vec<BaseComplexT> result;
1753 auto tracking_success = ctx.tracker.TrackPath(result, t_start, t_endgame_boundary, start_point);
1754
1756 EGBoundaryMetaDataT({ result, tracking_success, ctx.tracker.CurrentStepsize(), ctx.tracker.CurrentPrecision() });
1757
1758 // Clear the start-precision override so it does not leak into the endgame, which
1759 // shares this tracker instance for its sample circles.
1760 if constexpr (tracking::TrackerTraits<TrackerType>::IsAdaptivePrec)
1761 ctx.tracker.SetStartPrecision(std::nullopt);
1762
1763 smd.pre_endgame_success_code = tracking_success;
1764
1765 if (tracking::TrackerTraits<TrackerType>::IsAdaptivePrec)
1766 HarvestPathPrecision(smd, ctx.path_prec);
1767
1768 StampWhereThePathGotTo(smd, ctx.tracker, tracking_success, ctx.tracker.CurrentTime());
1769 }
1770
1787 void StampWhereThePathGotTo(SolutionMetaData<BaseComplexT>& smd, TrackerType const& tracker,
1788 SuccessCode code, BaseComplexT const& time_reached) const
1789 {
1790 using std::max;
1791 smd.final_time_used = time_reached;
1792 smd.num_successful_steps = tracker.CumulativeSuccessfulSteps();
1793 smd.num_failed_steps = tracker.CumulativeFailedSteps();
1794 smd.max_precision_used = max(smd.max_precision_used,
1795 static_cast<decltype(smd.max_precision_used)>(tracker.CurrentPrecision()));
1796 if (code == SuccessCode::Success)
1797 smd.latest_path_point.resize(0);
1798 else
1799 smd.latest_path_point = tracker.CurrentPoint();
1800 }
1801
1810 struct PathThreadState
1811 {
1812 System sys; // homotopy, tracked by `tracker`
1813 System target_sys; // for function residuals / dehomogenization
1814 TrackerType tracker;
1815 EndgameType endgame;
1816 tracking::PathPrecisionRecorder<TrackerType> path_prec;
1817
1818 DuringEGContext Context()
1819 {
1820 return DuringEGContext{ target_sys, tracker, endgame, path_prec };
1821 }
1822 };
1823
1835 auto MakeThreadStateFactory()
1836 {
1837 return [this]() -> std::unique_ptr<PathThreadState>
1838 {
1839 // Trackers/Endgames have no default ctor, so aggregate-init via new; {} defaults
1840 // the path's precision recorder.
1841 std::unique_ptr<PathThreadState> s(
1842 new PathThreadState{ Clone(GetTracker().GetSystem()), Clone(TargetSystem()),
1843 GetTracker(), GetEndgame(), {} });
1844 s->tracker.SetSystem(s->sys); // re-point the copy at its own System
1845 s->endgame.SetTracker(s->tracker); // ... and the endgame at that tracker
1846 SetThreadPrecision(this->template Get<ZeroDimConf>().initial_ambient_precision);
1847 return s;
1848 };
1849 }
1850
1859 auto MakeThreadTrackFn()
1860 {
1861 return [this](std::unique_ptr<PathThreadState>& state,
1862 parallel::StartPointTask<BaseComplexT> const& task)
1863 -> parallel::FullPathResult<BaseComplexT>
1864 {
1865 auto idx = static_cast<SolnIndT>(task.path_index);
1866 ExecuteOnePath(state->Context(), idx, task.start_point);
1867 return PackFullPathResult(idx);
1868 };
1869 }
1870
1879 void RunPathsThreaded(std::vector<SolnIndT> const& indices,
1880 std::vector<Vec<BaseComplexT>> const& start_points,
1881 unsigned n_threads)
1882 {
1883 using Task = parallel::StartPointTask<BaseComplexT>;
1884 using Result = parallel::FullPathResult<BaseComplexT>;
1885
1886 auto state_factory = MakeThreadStateFactory();
1887 auto track_fn = MakeThreadTrackFn();
1888
1889 parallel::WorkerThreadPool<Task, Result, decltype(state_factory), decltype(track_fn)>
1890 pool(static_cast<int>(n_threads), state_factory, track_fn);
1891
1892 for (auto idx : indices)
1893 pool.submit(Task{ static_cast<std::size_t>(idx), start_points[idx] });
1894
1895 for (std::size_t k = 0; k < indices.size(); ++k)
1896 StoreFullPathResult(pool.collect());
1897
1898 pool.shutdown();
1899 }
1900
1917 void EscalateRetrackSettings()
1918 {
1919 midpath_retrack_tolerance_ *= this->template Get<AutoRetrack>().midpath_decrease_tolerance_factor;
1920 GetTracker().SetTrackingTolerance(midpath_retrack_tolerance_);
1921
1922 const auto default_predictor = tracking::predict::DefaultPredictor();
1923 if (tracking::predict::Order(GetTracker().GetPredictor())
1924 < tracking::predict::Order(default_predictor))
1925 GetTracker().SetPredictor(default_predictor);
1926 }
1927
1928
1929
1940 void ExecuteOnePath(DuringEGContext ctx, SolnIndT soln_ind, Vec<BaseComplexT> const& start_point)
1941 {
1942 // Carry the executing tracker on the path events: &ctx.tracker is the member tracker
1943 // in a serial solve and the thread-local clone in a threaded one, so a meta-observer
1944 // attaches its per-path sub-observer to the tracker that actually runs this path.
1945 Observable const* exec_tracker = &ctx.tracker;
1946
1947 // Somebody asked us to stop before this path began, so do not begin it. Its
1948 // metadata keeps the default NeverStarted, which is the honest description and
1949 // is already distinguishable from a path we started and abandoned. Checked
1950 // here rather than only in the dispatch loops so the threaded pool, which has
1951 // every task submitted up front, drains without doing work.
1953 return;
1954
1955 // wall-clock the whole path (pre-endgame + endgame). steady_clock: monotonic, safe on
1956 // the worker thread; written to this path's own metadata slot, so no cross-path race.
1957 auto const path_start_clock = std::chrono::steady_clock::now();
1958 auto stamp_path_time = [&]{
1959 solution_final_metadata_[soln_ind].path_time_seconds =
1960 std::chrono::duration<double>(std::chrono::steady_clock::now() - path_start_clock).count();
1961 };
1962
1963 this->NotifyObservers(PathStarted<AnyZeroDim>(*this, static_cast<std::size_t>(soln_ind), exec_tracker));
1964
1965 // One precision record for the whole path, across the track to the boundary and
1966 // every endgame sub-track: started here, read after each stage, detached at the end.
1967 constexpr bool adaptive = tracking::TrackerTraits<TrackerType>::IsAdaptivePrec;
1968 if (adaptive)
1969 {
1970 ctx.path_prec.Reset();
1971 ctx.tracker.AddObserver(ctx.path_prec);
1972 }
1973
1974 ctx.tracker.SetTrackingTolerance(midpath_retrack_tolerance_);
1975 ExecuteBeforeEG(BeforeEGContext{ ctx.tracker, ctx.path_prec }, soln_ind, start_point);
1976
1977 ReadSolveTimeoutAsAnInterrupt(solution_final_metadata_[soln_ind].pre_endgame_success_code, path_start_clock);
1978 if (solution_final_metadata_[soln_ind].pre_endgame_success_code != SuccessCode::Success)
1979 {
1980 if (adaptive)
1981 ctx.tracker.RemoveObserver(ctx.path_prec);
1982 ctx.tracker.ClearMaxWallClockTime();
1983 stamp_path_time();
1984 this->NotifyObservers(PathComplete<AnyZeroDim>(*this, static_cast<std::size_t>(soln_ind), exec_tracker,
1985 solution_final_metadata_[soln_ind].pre_endgame_success_code));
1986 return;
1987 }
1988
1989 ctx.tracker.SetTrackingTolerance(this->template Get<Tolerances>().newton_during_endgame);
1990 ExecuteDuringEG(ctx, soln_ind);
1991 if (adaptive)
1992 ctx.tracker.RemoveObserver(ctx.path_prec);
1993
1994 ReadSolveTimeoutAsAnInterrupt(solution_final_metadata_[soln_ind].endgame_success_code, path_start_clock);
1995 ctx.tracker.ClearMaxWallClockTime(); // the budget was this path's; do not carry it to the next
1996 stamp_path_time();
1997 this->NotifyObservers(PathComplete<AnyZeroDim>(*this, static_cast<std::size_t>(soln_ind), exec_tracker,
1998 solution_final_metadata_[soln_ind].endgame_success_code));
1999 }
2000
2001
2011 template<typename RedoOne>
2012 void RunMidpathResolution(RedoOne&& redo_one)
2013 {
2014 auto passed = midpath_.Check(solutions_at_endgame_boundary_, StartSystem());
2015
2016 midpath_report_ = MidpathCheckReport{};
2017 midpath_report_.passed = passed;
2018 for (auto const& v : midpath_.GetCrossedPaths())
2019 midpath_report_.crossed_path_indices.push_back(v.index());
2020 midpath_report_.num_crossings_detected =
2021 static_cast<unsigned>(midpath_report_.crossed_path_indices.size());
2022
2023 const auto max_attempts = this->template Get<ZeroDimConf>().max_num_crossed_path_resolve_attempts;
2024 unsigned num_resolve_attempts = 0;
2025 while (!passed && num_resolve_attempts < max_attempts)
2026 {
2027 EscalateRetrackSettings();
2028 for (auto const& v : midpath_.GetCrossedPaths())
2029 if (v.rerun())
2030 redo_one(static_cast<SolnIndT>(v.index()));
2032 ++num_resolve_attempts;
2033 }
2034
2035 midpath_report_.num_resolve_attempts = num_resolve_attempts;
2036 midpath_report_.passed = passed;
2037 FlagUnresolvedCrossings(passed);
2038 RecordMidpathOutcome();
2039
2040 if (!passed)
2041 std::cerr << "warning: " << midpath_report_.num_crossings_detected
2042 << " path crossing(s) detected at the endgame boundary remained unresolved "
2043 << "after " << num_resolve_attempts << " re-track attempt(s); "
2044 << "the affected solutions may be wrong. Consider a higher-order predictor "
2045 << "or a tighter tracking tolerance. See EndgameBoundaryMetadata()." << std::endl;
2046 }
2047
2048
2065 void FlagUnresolvedCrossings(bool passed)
2066 {
2067 for (auto& smd : solution_final_metadata_)
2068 smd.crossing_unresolved = false;
2069 if (passed)
2070 return;
2071 for (auto const& v : midpath_.GetCrossedPaths())
2072 solution_final_metadata_[static_cast<SolnIndT>(v.index())].crossing_unresolved = true;
2073 }
2074
2094 void RecordMidpathOutcome()
2095 {
2096 if (!records_ || recalling_ || records_run_id_.empty())
2097 return;
2098
2099 boost::json::array crossed;
2100 for (auto const i : midpath_report_.crossed_path_indices)
2101 crossed.push_back(boost::json::value(static_cast<std::int64_t>(i)));
2102 records_->AppendResult(records_run_id_,
2103 {{"kind", "midpath"},
2104 {"run", records_run_id_},
2105 {"passed", midpath_report_.passed},
2106 {"num_crossings_detected",
2107 static_cast<std::int64_t>(midpath_report_.num_crossings_detected)},
2108 {"num_resolve_attempts",
2109 static_cast<std::int64_t>(midpath_report_.num_resolve_attempts)},
2110 {"crossed_path_indices", std::move(crossed)}});
2111
2112 for (SolnIndT idx = 0; idx < solution_final_metadata_.size(); ++idx)
2113 if (solution_final_metadata_[idx].crossing_unresolved)
2115 }
2116
2125 void ExecuteDuringEG(DuringEGContext ctx, SolnIndT soln_ind)
2126 {
2127 ReseedThisThread(static_cast<uint64_t>(soln_ind) + static_cast<uint64_t>(num_start_points_));
2128
2129 auto& smd = solution_final_metadata_[soln_ind];
2130
2131 const auto& bdry_point = solutions_at_endgame_boundary_[soln_ind].path_point;
2132
2133 ctx.tracker.SetStepSize(solutions_at_endgame_boundary_[soln_ind].last_used_stepsize);
2134 ctx.tracker.ReinitializeInitialStepSize(false);
2135
2136 // Resume the endgame at the precision the path was actually using at the boundary,
2137 // rather than inferring it from the (always-multiprecision) point's mantissa, which
2138 // is unreliable for paths that tracked in double.
2139 auto start_prec = solutions_at_endgame_boundary_[soln_ind].precision;
2140 SetThreadPrecision(start_prec);
2141
2142 ctx.endgame.SetBoundaryTime(BaseComplexT(BaseRealT(this->template Get<ZeroDimConf>().endgame_boundary)));
2143 ctx.endgame.SetTargetTime (BaseComplexT(BaseRealT(this->template Get<ZeroDimConf>().target_time)));
2144
2145 auto eg_success = ctx.endgame.Run(bdry_point);
2146
2147 // The endgame writes its approximation only when it converges, so on a failure
2148 // the accessor still holds whatever the PREVIOUS path on this endgame left there.
2149 // A failed path's solution slot is therefore emptied, never copied: its stamp
2150 // (latest_path_point, final_time_used) says where it got to.
2151 if (eg_success == SuccessCode::Success)
2152 solutions_post_endgame_[soln_ind] = ctx.endgame.template FinalApproximation<BaseComplexT>();
2153 else
2154 solutions_post_endgame_[soln_ind].resize(0);
2155
2156 smd.endgame_success_code = eg_success;
2157 StampWhereThePathGotTo(smd, ctx.tracker, eg_success,
2158 eg_success == SuccessCode::Success ? ctx.endgame.LatestTime()
2159 : ctx.tracker.CurrentTime());
2160
2161 // Harvest the path's precision record REGARDLESS of the endgame outcome: the
2162 // precision a path used is a fact about the tracking that happened, and it is
2163 // exactly the FAILED paths -- e.g. slow divergers escalating in the mp lane --
2164 // whose precision honesty matters most for diagnostics.
2165 if (tracking::TrackerTraits<TrackerType>::IsAdaptivePrec)
2166 HarvestPathPrecision(smd, ctx.path_prec);
2167
2168 // an unsuccessful endgame has no final approximation, so the final-point-dependent
2169 // metadata cannot be computed.
2170 if (eg_success != SuccessCode::Success)
2171 return;
2172 if (tracking::TrackerTraits<TrackerType>::IsAdaptivePrec)
2173 {
2174 assert(Precision(solutions_post_endgame_[soln_ind])==Precision(ctx.endgame.template FinalApproximation<BaseComplexT>()));
2176 // the system no longer needs aligning by hand -- the Eval below does it
2177 // from the point it is handed (#377)
2178 }
2179 smd.function_residual = static_cast<NumErrorT>(ctx.target_sys.Eval(solutions_post_endgame_[soln_ind]).template lpNorm<Eigen::Infinity>());
2180 smd.singular_values = EndpointSingularValues(ctx.target_sys, solutions_post_endgame_[soln_ind]);
2181 smd.condition_number = SpectralConditionNumberOf(smd.singular_values);
2182 smd.newton_residual = ctx.tracker.LatestNormOfStep();
2183
2184 smd.accuracy_estimate_internal_coords = ctx.endgame.ApproximateError();
2185 smd.accuracy_estimate_user_coords =
2186 static_cast<NumErrorT>( (ctx.target_sys.DehomogenizePoint(solutions_post_endgame_[soln_ind]) -
2187 ctx.target_sys.DehomogenizePoint(ctx.endgame.template PreviousApproximation<BaseComplexT>())).template lpNorm<Eigen::Infinity>() );
2188 smd.cycle_num = ctx.endgame.CycleNumber();
2189
2190 // Honest precision/accuracy of this solution, as digit counts. precision_digits is the
2191 // precision the endgame actually finished in -- which is exactly the precision of the
2192 // stored solution point (DoublePrecision() for a fast-lane path, the mpfr precision for an
2193 // escalated one). accuracy_digits is how many of those digits the convergence agreement
2194 // supports: floor(-log10(accuracy_estimate_internal_coords)), clamped to [0, precision_digits].
2195 smd.precision_digits = static_cast<unsigned>(Precision(solutions_post_endgame_[soln_ind]));
2196 {
2197 using std::log10; using std::floor; using std::min; using std::max;
2198 double err = static_cast<double>(smd.accuracy_estimate_internal_coords);
2199 unsigned acc = (err > 0.0)
2200 ? static_cast<unsigned>(max(0.0, floor(-log10(err))))
2201 : smd.precision_digits;
2202 smd.accuracy_digits = min(acc, smd.precision_digits);
2203 }
2204 }
2205
2206
2207 protected:
2208 // virtual so ZeroDimSolver can append its extraneous-solution filter after the engine's
2209 // classification (it overrides this to call the base, then filter against the original
2210 // over-determined system). Everything from here down -- the classification helpers, the
2211 // pack/store helpers, and the data members -- is protected so the derived algorithm can
2212 // read the per-endpoint metadata and endpoints it needs for that filter.
2214 virtual void PostEGAction()
2215 {
2217 }
2218
2219
2220
2232
2245 {
2246 const auto& post = this->template Get<PostProcessing>();
2247
2248 for (decltype(num_start_points_) ii{0}; ii < num_start_points_; ++ii)
2249 {
2250 auto& smd = solution_final_metadata_[ii];
2251
2252 if (smd.endgame_success_code==SuccessCode::GoingToInfinity ||
2253 smd.endgame_success_code==SuccessCode::SecurityMaxNormReached)
2254 {
2255 smd.is_finite = false; // the endgame already decided this path diverges
2256 continue;
2257 }
2258 if (smd.endgame_success_code!=SuccessCode::Success)
2259 continue; // failed otherwise: leave defaults (not finite/real/singular)
2260
2261 // the system renders both verdicts: it is what knows which of its coordinates
2262 // the questions are about (b2#403)
2263 smd.is_finite = this->TargetSystem().IsFinite(solutions_post_endgame_[ii],
2264 post.endpoint_finite_threshold);
2265 if (smd.is_finite)
2266 smd.is_real = this->TargetSystem().IsReal(solutions_post_endgame_[ii],
2267 post.real_threshold);
2268 }
2269 }
2270
2283 {
2284 const NumErrorT same_tol = this->SamePointTolerance();
2285
2286 std::vector<Vec<BaseComplexT>> user_pts(num_start_points_);
2287 std::vector<char> eligible(num_start_points_, 0);
2288 for (decltype(num_start_points_) ii{0}; ii < num_start_points_; ++ii)
2289 {
2290 auto const& smd = solution_final_metadata_[ii];
2291 if (smd.endgame_success_code==SuccessCode::Success && smd.is_finite)
2292 {
2293 user_pts[ii] = this->TargetSystem().DehomogenizePoint(solutions_post_endgame_[ii]);
2294 eligible[ii] = 1;
2295 }
2296 }
2297
2298 for (decltype(num_start_points_) ii{0}; ii < num_start_points_; ++ii)
2299 {
2300 if (!eligible[ii]) continue;
2301 for (decltype(num_start_points_) jj{ii+1}; jj < num_start_points_; ++jj)
2302 {
2303 if (!eligible[jj]) continue;
2304 if ( static_cast<NumErrorT>((user_pts[ii] - user_pts[jj]).template lpNorm<Eigen::Infinity>()) < same_tol )
2305 {
2306 ++solution_final_metadata_[ii].multiplicity;
2307 ++solution_final_metadata_[jj].multiplicity;
2308 // jj coincides with the earlier ii, so it is a duplicate, not the
2309 // representative; the lowest-index member of a cluster is never marked
2310 // here, so it remains the single representative.
2311 solution_final_metadata_[jj].multiplicity_representative = false;
2312 }
2313 }
2314 }
2315 }
2316
2327 {
2328 const NumErrorT cond_threshold = this->template Get<PostProcessing>().condition_number_threshold;
2329 for (decltype(num_start_points_) ii{0}; ii < num_start_points_; ++ii)
2330 {
2331 auto& smd = solution_final_metadata_[ii];
2332 if (smd.endgame_success_code!=SuccessCode::Success)
2333 continue;
2334 smd.is_singular = (smd.multiplicity > 1) || (smd.condition_number > cond_threshold);
2335 }
2336 }
2337
2362 static NumErrorT EndpointSpectralConditionNumber(SystemType const& sys, Vec<BaseComplexT> const& endpoint)
2363 {
2365 }
2366
2378 static Vec<NumErrorT> EndpointSingularValues(SystemType const& sys, Vec<BaseComplexT> const& endpoint)
2379 {
2380 const auto J = sys.Jacobian(endpoint);
2381 const auto sv = Eigen::JacobiSVD<Mat<BaseComplexT>>(J).singularValues();
2382 Vec<NumErrorT> out(sv.size());
2383 for (Eigen::Index i = 0; i < sv.size(); ++i)
2384 out(i) = static_cast<NumErrorT>(sv(i));
2385 return out;
2386 }
2387
2398 {
2399 if (singular_values.size() == 0)
2400 return std::numeric_limits<NumErrorT>::infinity();
2401 const NumErrorT smallest = singular_values(singular_values.size()-1);
2402 if (smallest <= 0)
2403 return std::numeric_limits<NumErrorT>::infinity();
2404 return singular_values(0) / smallest;
2405 }
2406
2407
2408
2410 // MPI pack/store helpers (only compiled when BERTINI2_HAVE_MPI is defined)
2412
2413 // Pack everything computed for one whole path into a single result value. Used as the
2414 // thread-safe handoff in BOTH the MPI protocol (serialized over the wire) and the
2415 // standalone threaded solve (a worker thread packs, the main thread installs via
2416 // StoreFullPathResult) -- the compute/install split is what keeps shared result arrays
2417 // race-free when several threads run distinct paths.
2422 {
2424 r.path_index = idx;
2427 r.boundary_stepsize = solutions_at_endgame_boundary_[idx].last_used_stepsize;
2429
2430 auto const& smd = solution_final_metadata_[idx];
2431 r.endgame_success_code = smd.endgame_success_code;
2433 r.function_residual = smd.function_residual;
2434 r.condition_number = smd.condition_number;
2435 r.singular_values = smd.singular_values;
2436 r.newton_residual = smd.newton_residual;
2437 r.final_time_used = smd.final_time_used;
2438 r.accuracy_estimate_internal_coords = smd.accuracy_estimate_internal_coords;
2439 r.accuracy_estimate_user_coords = smd.accuracy_estimate_user_coords;
2440 r.cycle_num = smd.cycle_num;
2441 r.precision_digits = smd.precision_digits;
2442 r.accuracy_digits = smd.accuracy_digits;
2443 r.precision_changed = smd.precision_changed;
2444 r.time_of_first_prec_increase = smd.time_of_first_prec_increase;
2445 r.max_precision_used = smd.max_precision_used;
2446 r.path_time_seconds = smd.path_time_seconds;
2447 r.num_successful_steps = smd.num_successful_steps;
2448 r.num_failed_steps = smd.num_failed_steps;
2449 r.latest_path_point = smd.latest_path_point;
2450 r.wall_clock_limit_seconds = smd.wall_clock_limit_seconds;
2451 // Always false while paths are being tracked -- FlagUnresolvedCrossings runs after
2452 // the last resolve round -- so a re-track never carries an earlier round's verdict
2453 // back out. It is set here for the amending record that reports a flagged path
2454 // once the check is finally over.
2455 r.crossing_unresolved = smd.crossing_unresolved;
2456 return r;
2457 }
2458
2459 // Install a whole-path result on the manager. Overwrites cleanly, so re-dispatching a
2460 // crossed path in a later resolve round simply replaces its earlier (crossed) result.
2464 {
2465 auto idx = static_cast<SolnIndT>(r.path_index);
2469
2470 auto& smd = solution_final_metadata_[idx];
2471 smd.path_index = idx;
2472 smd.solution_index = idx;
2473 smd.pre_endgame_success_code = r.pre_endgame_success_code;
2474 smd.endgame_success_code = r.endgame_success_code;
2475 smd.function_residual = r.function_residual;
2476 smd.condition_number = r.condition_number;
2477 smd.singular_values = r.singular_values;
2478 smd.newton_residual = r.newton_residual;
2479 smd.final_time_used = r.final_time_used;
2480 smd.accuracy_estimate_internal_coords = r.accuracy_estimate_internal_coords;
2481 smd.accuracy_estimate_user_coords = r.accuracy_estimate_user_coords;
2482 smd.cycle_num = r.cycle_num;
2483 smd.precision_digits = r.precision_digits;
2484 smd.accuracy_digits = r.accuracy_digits;
2485 smd.precision_changed = r.precision_changed;
2486 smd.time_of_first_prec_increase = r.time_of_first_prec_increase;
2487 smd.max_precision_used = r.max_precision_used;
2488 smd.path_time_seconds = r.path_time_seconds;
2489 smd.num_successful_steps = r.num_successful_steps;
2490 smd.num_failed_steps = r.num_failed_steps;
2491 smd.latest_path_point = r.latest_path_point;
2492 smd.wall_clock_limit_seconds = r.wall_clock_limit_seconds;
2493 // a recalled path brings its archived crossing verdict back with it; a freshly
2494 // tracked one carries false, which is what a path mid-solve is
2495 smd.crossing_unresolved = r.crossing_unresolved;
2496
2497 // the records seam (ADR-0046): every topology installs completed paths here on
2498 // the main/manager thread, so emission is single-writer by construction
2500 }
2501
2502
2504 //
2505 // the structured output directory seam (records; ADR-0046)
2506 //
2508
2509 public:
2510
2515 void RecordTo(std::shared_ptr<records::OutputDirectory> directory)
2516 {
2517 records_ = std::move(directory);
2518 }
2519
2523 void RecordToPath(std::string const& path) override
2524 {
2526 }
2527
2529 std::string RecordsRunIdentity() const override { return records_run_id_; }
2530
2541 virtual SystemType const& RecordsTargetSystem() const { return TargetSystem(); }
2542
2544 void AppendRecordJson(std::string const& record_json) override
2545 {
2546 if (records_)
2547 records_->Append(boost::json::parse(record_json).as_object());
2548 }
2549
2552 std::string PutRecordsDefinition(std::string const& content,
2553 std::string const& kind,
2554 std::string const& label = {}) override
2555 {
2556 return records_ ? records_->PutDefinition(content, kind, std::nullopt, label)
2557 : std::string();
2558 }
2559
2566 {
2567 if (!records_ || records_run_id_.empty())
2568 return;
2569 boost::json::array points;
2570 for (auto const& smd : solution_final_metadata_)
2571 if (smd.is_finite)
2572 points.push_back(boost::json::object{
2573 {"run", records_run_id_},
2574 {"index", static_cast<std::int64_t>(smd.path_index)}});
2575 boost::json::object result;
2576 result["kind"] = "result";
2577 result["name"] = "finite solutions [run " + records_run_id_ + "]";
2578 result["when"] = records::TimeStampNow();
2579 result["description"] = "auto-declared by the solver after post-processing";
2580 result["points"] = points;
2581 records_->Append(result);
2582 records_->RefreshIndex();
2583 }
2584
2586 std::shared_ptr<records::OutputDirectory> const& Records() const { return records_; }
2587
2592 unsigned long long NumPaths() const { return num_start_points_; }
2593
2595 unsigned long long NumPathsRecalled() const { return num_recalled_; }
2596
2607 bool WasStoppedEarly() const { return stopped_early_; }
2608
2610 unsigned long long NumPathsNeverStarted() const { return num_never_started_; }
2611
2613 std::string const& RecordsRunId() const { return records_run_id_; }
2614
2627 {
2628 if (records_ || !parallel::IsManager())
2629 return;
2630 if (auto const dir = records::AmbientRecordsPath())
2632 }
2633
2652 void SetRecordedStartProvenance(std::vector<boost::json::object> refs,
2653 std::string start_identity)
2654 {
2655 records_start_refs_ = std::move(refs);
2656 records_start_identity_ = std::move(start_identity);
2657 }
2658
2679 std::string CanonicalSettingsText() const
2680 {
2681 std::ostringstream text;
2682 text << records::ConfigEncodingVersion << "\n"
2683 << records::CanonicalEncoding(this->template Get<ZeroDimConf>()) << "\n"
2684 << records::CanonicalEncoding(this->template Get<Tolerances>()) << "\n"
2685 << records::CanonicalEncoding(this->template Get<AutoRetrack>()) << "\n"
2686 << records::CanonicalEncoding(this->template Get<PostProcessing>()) << "\n"
2689 << records::CanonicalEncoding(GetTracker().GetPredictor()) << "\n"
2693 text << records::CanonicalEncoding(this->template Get<MidPathConfig>()) << "\n";
2694 return text.str();
2695 }
2696
2707 boost::json::object RecordsAsk() const
2708 {
2710 boost::json::object ask;
2711 ask["op"] = "zerodim";
2714 ask["target"] = RecordsTargetSystem().ContentDigest().Hex();
2715 // the homotopy actually tracked: for seed-derived homotopies this is
2716 // redundant with (target, seed) but harmless; for USER homotopies (the
2717 // engine driven directly) it is the only thing telling asks apart
2718 ask["homotopy"] = Homotopy().ContentDigest().Hex();
2719 if (!records_start_identity_.empty())
2720 ask["start"] = records_start_identity_; // external start data is identity
2721 ask["config"] = settings.Hex();
2722 ask["seed"] = static_cast<std::int64_t>(GetGlobalSeed());
2723 return ask;
2724 }
2725
2733 template <typename... EGConfTs>
2734 void AppendEndgameConfigEncodings(std::ostringstream& text,
2736 {
2738 GetEndgame().template Get<EGConfTs>()) << "\n"), ...);
2739 }
2740
2746 {
2747 auto const ask = RecordsAsk();
2748 records_run_id_ = detail::Sha256(boost::json::serialize(ask)).Hex().substr(0, 16);
2749
2750 // the run's results file (the payload store) is created BEFORE anything
2751 // refers to it: line 1 is its self-description, so a wandering results
2752 // file says what run and ask it answers. Idempotent on resume.
2753 records_->EnsureResultsFile(records_run_id_,
2754 {{"kind", "results_header"},
2755 {"schema", records::RecordSchemaVersion},
2756 {"run", records_run_id_},
2757 {"ask", ask}});
2758
2759 records_run_resumed_ = false;
2760 for (auto const& rec : records_->Scan())
2761 if (auto const* k = rec.if_contains("kind");
2762 k && k->is_string() && k->get_string() == "run"
2763 && rec.if_contains("run") && rec.at("run").is_string()
2764 && rec.at("run").as_string() == records_run_id_)
2765 {
2766 // header already on record: this ask was asked before. The
2767 // recall event narrating THIS session lands after recall runs
2768 // (RecallRecordedPaths knows the counts).
2769 records_run_resumed_ = true;
2770 return;
2771 }
2772
2773 // the archived system is a JSON document (like the configs): a
2774 // structured "system" parts view (variable groups, functions, ...) and
2775 // the CANONICAL ENCODING (b2sysenc) -- exact and complete: every block
2776 // (slices, randomization, blends), patch, and gamma survives. Classic
2777 // syntax appears nowhere (an input/compat format, not an output one).
2778 // The encoding is the digest PREIMAGE, so the definition id EQUALS
2779 // target_digest and a copied-out file verifies itself (hash .encoding).
2780 auto const target_digest_hex = RecordsTargetSystem().ContentDigest().Hex();
2781 records_->PutDefinition(
2782 records::SystemEncodingAsJson(RecordsTargetSystem().CanonicalEncodingText(),
2783 target_digest_hex,
2785 "systems", target_digest_hex);
2786 // the homotopy ACTUALLY TRACKED is archived too: its exact coefficients
2787 // (gamma, start-system constants, blend structure) are what the paths
2788 // followed, and for user homotopies (chained solves, the engine driven
2789 // directly) the archived encoding is the ONLY complete record -- no
2790 // seed can rebuild a homotopy constructed before this solve began.
2791 auto const homotopy_digest_hex = Homotopy().ContentDigest().Hex();
2792 records_->PutDefinition(
2793 records::SystemEncodingAsJson(Homotopy().CanonicalEncodingText(),
2794 homotopy_digest_hex,
2796 "systems", homotopy_digest_hex);
2797 // the settings, reconstructible: the SAME canonical text the digest is over
2798 std::string const config_text = CanonicalSettingsText();
2799 records_->PutDefinition(
2800 records::ConfigTextAsJson(config_text, std::string(ask.at("config").as_string())),
2801 "configs",
2802 std::string(ask.at("config").as_string()));
2803 boost::json::object header;
2804 header["kind"] = "run";
2805 header["schema"] = records::RecordSchemaVersion;
2806 header["when"] = records::TimeStampNow();
2807 header["run"] = records_run_id_;
2808 header["op"] = "zerodim";
2809 // which software wrote this -- descriptive only, NEVER part of the ask
2810 // identity (a newer build answering the same ask must still recall)
2811 header["producer"] = records::ProducerInfo();
2812 header["ask"] = ask;
2813 // the field holds a DIGEST -- dereferencing it to the definitions/
2814 // file is the reader's job (its id equals this digest)
2815 header["target_digest"] = target_digest_hex;
2816 // the INTERNAL variable ordering, so views can label endpoint coordinates
2817 // truthfully (homogenized points have more coordinates than the user's
2818 // rendering declares)
2819 {
2820 boost::json::array variable_names, user_variable_names;
2821 auto const& homogenizers = TargetSystem().HomogenizingVariables();
2822 for (auto const& v : TargetSystem().Variables())
2823 {
2824 variable_names.push_back(boost::json::value(v->name()));
2825 bool const is_homogenizer = std::find(homogenizers.begin(),
2826 homogenizers.end(), v) != homogenizers.end();
2827 if (!is_homogenizer)
2828 user_variable_names.push_back(boost::json::value(v->name()));
2829 }
2830 header["variables"] = variable_names;
2831 // the USER's variables, in dehomogenized-point order: labels for
2832 // the endpoint_user coordinates on track records
2833 header["variables_user"] = user_variable_names;
2834 }
2835 // (the settings digest lives in ask.config; deref is the reader's job)
2836 header["num_paths"] = static_cast<std::int64_t>(num_start_points_);
2837 // where this run's computed paths live: history refers, results/ holds
2838 header["results_file"] = "results/" + records_run_id_.substr(0, 2)
2839 + "/" + records_run_id_ + ".jsonl";
2840 records_->Append(header);
2841 }
2842
2856 static bool AbandonmentIsReusable(parallel::FullPathResult<BaseComplexT> const& r, double current_limit)
2857 {
2858 auto const is = [&](SuccessCode c){
2859 return r.pre_endgame_success_code == c || r.endgame_success_code == c; };
2861 return false;
2863 return current_limit > 0 && r.wall_clock_limit_seconds > 0
2864 && current_limit <= r.wall_clock_limit_seconds;
2865 return true;
2866 }
2867
2876 std::vector<SolnIndT> RecallRecordedPaths(std::vector<SolnIndT> const& all_indices)
2877 {
2878 std::map<std::size_t, boost::json::object> recorded; // last record per index wins
2879 for (auto const& rec : records_->ResultsOf(records_run_id_))
2880 {
2881 auto const* k = rec.if_contains("kind");
2882 if (!k || !k->is_string() || k->get_string() != "path")
2883 continue;
2884 if (auto const* idx = rec.if_contains("index"); idx && idx->is_int64())
2885 recorded[static_cast<std::size_t>(idx->as_int64())] = rec;
2886 }
2887
2888 auto const policy = this->template Get<RecordsConfig>().recall;
2889 auto const current_limit = this->template Get<ZeroDimConf>().max_path_wall_clock_duration;
2890
2891 num_recalled_ = 0;
2892 recalling_ = true;
2893 std::vector<SolnIndT> missing;
2894 for (auto const idx : all_indices)
2895 {
2896 auto const found = recorded.find(static_cast<std::size_t>(idx));
2897 if (found == recorded.end() || policy == RecallPolicy::Nothing)
2898 {
2899 missing.push_back(idx);
2900 continue;
2901 }
2902
2903 auto decoded = records::DecodeFullPathResult<BaseComplexT>(found->second, idx);
2904
2905 // The rule for reusing a record: a path record is reusable when the run
2906 // that would reuse it asks no more of that path than the run that produced
2907 // it. A completed path -- success, divergence, a failure under settings
2908 // that are part of the ask -- always qualifies, since nothing more is being
2909 // asked. An ABANDONED path was ended for a reason deliberately kept out of
2910 // the ask, so the record says "we gave up here", which is true and is not
2911 // an answer; whether to reuse it is the user's call (RecallPolicy). Under
2912 // the default: a path somebody stopped is always re-tracked, since an
2913 // interrupt has no budget to compare; one a wall-clock limit cut off is
2914 // reused only while the current budget asks no more patience than the one
2915 // that abandoned it, and re-tracked once the budget goes up -- which is
2916 // exactly when paying again is the point. Either way its record stays, so
2917 // the store still says what happened; it is simply not mistaken for a
2918 // conclusion.
2919 if (policy == RecallPolicy::Completed && !AbandonmentIsReusable(decoded, current_limit))
2920 {
2921 missing.push_back(idx);
2922 continue;
2923 }
2924
2925 StoreFullPathResult(std::move(decoded));
2926 ++num_recalled_;
2927 }
2928 recalling_ = false;
2929
2930 // the narrative stays complete: a recalled ask WAS asked. One small
2931 // history line per re-ask -- when, how much came from the store, how
2932 // much this session still computed (the kill-and-rerun story, in the
2933 // record). Fresh runs (no prior header) narrate via their run header.
2935 records_->Append({{"kind", "recall"},
2936 {"run", records_run_id_},
2937 {"when", records::TimeStampNow()},
2938 {"num_recalled", static_cast<std::int64_t>(num_recalled_)},
2939 {"num_computed", static_cast<std::int64_t>(missing.size())},
2940 {"producer", records::ProducerInfo()}});
2941 return missing;
2942 }
2943
2953 void TallyStoppedSolve(std::vector<SolnIndT> const& dispatched)
2954 {
2956 bool abandoned_any = false;
2957
2958 for (auto idx : dispatched)
2959 {
2960 auto const code = solution_final_metadata_[idx].pre_endgame_success_code;
2961 if (code == SuccessCode::NeverStarted)
2963 else if (code == SuccessCode::ExternallyTerminated)
2964 abandoned_any = true;
2965
2966 if (solution_final_metadata_[idx].endgame_success_code
2968 abandoned_any = true;
2969 }
2970
2971 stopped_early_ = abandoned_any || num_never_started_ > 0;
2972 }
2973
2983 {
2984 if (!records_ || recalling_ || records_run_id_.empty())
2985 return;
2986
2987 // A path we never began has nothing to report. This happens when a solve is
2988 // stopped partway: every path after the stop is left NeverStarted. Their
2989 // absence is explained once, at the run level, by the record that says the
2990 // solve was cut short -- which is better than thousands of identical rows
2991 // saying we did not get to it. A path we DID begin and then abandoned is a
2992 // different thing entirely and is recorded, with how far it got.
2994 return;
2995 auto record = records::EncodeFullPathResult(r);
2996 record["kind"] = "path";
2997 record["run"] = records_run_id_;
2998 record["index"] = static_cast<std::int64_t>(r.path_index);
2999 // success / diverged / failed -- a truncation near infinity is a verdict,
3000 // not a failure (the SummarizeSolve bucketing, PR #46, now in the records)
3001 record["status"] = records::CoarsePathStatus(r.endgame_success_code);
3002 // the endpoint in USER coordinates too: audits read results in the
3003 // variables the user wrote, not the internal homogenized ones
3004 if (r.solution.size() != 0)
3005 record["endpoint_user"] = records::EncodePoint(
3006 this->TargetSystem().DehomogenizePoint(r.solution));
3007 // a chained/given start carries its true provenance; otherwise the
3008 // canonical start-system label (provenance bottoms out here)
3009 record["start"] = (r.path_index < records_start_refs_.size())
3011 : boost::json::object{{"kind", "start_label"},
3012 {"index", static_cast<std::int64_t>(r.path_index)}};
3013 records_->AppendResult(records_run_id_, record);
3014 }
3015
3016
3017 protected:
3018
3019 protected:
3020
3022 // private data members
3024
3025 std::shared_ptr<records::OutputDirectory> records_;
3026 std::string records_run_id_;
3027 std::vector<boost::json::object> records_start_refs_;
3029 bool recalling_ = false;
3031 unsigned long long num_recalled_ = 0;
3032
3033 bool stopped_early_ = false;
3034 std::optional<std::chrono::steady_clock::time_point> solve_deadline_;
3035
3038 {
3039 return solve_deadline_ && std::chrono::steady_clock::now() >= *solve_deadline_;
3040 }
3041
3056 void ReadSolveTimeoutAsAnInterrupt(SuccessCode& code, std::chrono::steady_clock::time_point path_start) const
3057 {
3059 return;
3060 auto const path_limit = this->template Get<ZeroDimConf>().max_path_wall_clock_duration;
3061 if (path_limit > 0)
3062 {
3063 auto const path_deadline = path_start
3064 + std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(path_limit));
3065 if (path_deadline <= *solve_deadline_)
3066 return; // the path's own budget ran out first: that is what stopped it
3067 }
3069 }
3070 unsigned long long num_never_started_ = 0;
3071
3072 unsigned long long num_start_points_;
3077
3078
3080 // i feel like these should be factored out into some policy class which prescribes how they are used, so that the actions taken are customizable.
3082
3083
3085 TrackerType tracker_;
3086 EndgameType endgame_;
3088
3089
3090
3095 mutable bool solutions_user_coords_fresh_ = false;
3097
3098
3099 }; // struct HomotopySolver
3100
3101
3102 // impl namespace deliberately NOT named `detail`: bertini::detail (TypeList, Configured)
3103 // is referenced unqualified throughout this file, and a bertini::algorithm::detail would
3104 // shadow it for any code defined after this point (e.g. ApplyParsedConfigs).
3106
3116 template<typename SystemType>
3118 {
3121
3123 OwnedHomotopy(SystemType const& target, FactoryT factory, std::string const& path_variable_name)
3124 : user_target_(Clone(target)), owned_target_(Clone(target)), owned_factory_(std::move(factory))
3125 {
3126 ConsistencyCheck(); // feasibility (no path var; not under-constrained; polynomial)
3127 SquareUp(); // randomize an over-determined system down to square
3128 RankCheck(); // square, but can isolated solutions even exist?
3129 PrepareTarget(owned_target_); // homogenize + auto-patch the (now square) target
3130 owned_start_ = owned_factory_(owned_target_); // start system over the prepared target
3131 // Treat the configured name as a BASE: mangle it only if it would collide with a
3132 // user variable, so the injected path variable can never overlap a user symbol.
3134 UniquePathVariableName(owned_target_, path_variable_name));
3135 }
3136
3138 SystemType const& BuiltTarget() const { return owned_target_; }
3143 SystemType const& UserTarget() const { return user_target_; }
3145 StartSystemBaseT const& BuiltStart() const { return *owned_start_; }
3147 SystemType const& BuiltHomotopy() const { return owned_homotopy_; }
3148
3149 protected:
3150 SystemType user_target_;
3151 SystemType owned_target_;
3152 std::shared_ptr<StartSystemBaseT> owned_start_;
3153 SystemType owned_homotopy_;
3155
3156 // When the user's system was over-determined, SquareUp randomizes it down to square for
3157 // tracking and keeps the ORIGINAL (natural, un-homogenized) system here so ZeroDimSolver
3158 // can discard the extraneous solutions the squaring introduces. was_randomized_ gates
3159 // that filter; randomization_matrix_ records the (exact) coefficient matrix used.
3160 bool was_randomized_ = false;
3163
3167 void ConsistencyCheck() const
3168 {
3169 if (owned_target_.HavePathVariable())
3170 throw std::runtime_error("unable to perform zero dim solve on target system -- has path variable, use a HomotopySolver instead.");
3171
3172 // A square zero-dim system needs one equation per dimension. Each projective
3173 // (homogeneous) variable group of size k spans P^{k-1}: its k coordinates carry only
3174 // k-1 dimensions because scale is free, so it needs one fewer equation than it has
3175 // variables. Subtract that free scale per projective group before comparing. An
3176 // UNDER-determined system has a positive-dimensional solution set, so no isolated
3177 // solutions to compute -- raise a helpful error rather than tracking garbage.
3178 if (owned_target_.NumVariables() - owned_target_.NumHomVariableGroups() > owned_target_.NumTotalFunctions())
3179 throw std::runtime_error("unable to perform zero dim solve on target system -- it is under-determined (fewer equations than variables), so its solution set is positive-dimensional, not zero-dimensional. ZeroDimSolver computes isolated solutions only; add equations, or use a positive-dimensional method.");
3180
3181 if (!owned_target_.IsPolynomial())
3182 throw std::runtime_error("unable to perform zero dim solve on target system -- system is non-polynomial, use a HomotopySolver instead.");
3183 }
3184
3192 {
3193 auto const dimension = owned_target_.NumVariables() - owned_target_.NumHomVariableGroups();
3194 if (owned_target_.NumTotalFunctions() <= dimension)
3195 return; // already square (or under-determined, which ConsistencyCheck already rejected)
3196
3197 original_natural_target_ = Clone(owned_target_); // keep the original N-function system
3198 owned_target_ = owned_target_.Randomize();
3199 randomization_matrix_ = owned_target_.RandomizationMatrix();
3200 was_randomized_ = true;
3201 }
3202
3212 void RankCheck() const
3213 {
3214 if (owned_target_.HavePathVariable())
3215 return;
3216 auto const n = static_cast<Eigen::Index>(owned_target_.NumVariables());
3217 if (static_cast<Eigen::Index>(owned_target_.NumTotalFunctions()) != n || n == 0)
3218 return; // not a square map: ConsistencyCheck governs feasibility here
3219
3220 // a generic complex sample point: a zero-dimensional variety misses it almost surely,
3221 // where the Jacobian attains its generic (full) rank.
3223 Mat<complex_dbl> J = owned_target_.template Jacobian<complex_dbl>(pt);
3224 Eigen::FullPivLU<Mat<complex_dbl>> lu(J);
3225 lu.setThreshold(1e-10);
3226 if (lu.rank() < n)
3227 throw std::runtime_error("unable to perform zero dim solve on target system -- the Jacobian is rank-deficient at a generic point, so the solution set is positive-dimensional, not zero-dimensional. ZeroDimSolver computes isolated solutions only.");
3228 }
3229
3232 static void PrepareTarget(SystemType& target)
3233 {
3234 target.Homogenize(); // work over projective coordinates
3235 target.AutoPatch(); // then patch if needed
3236 }
3237 };
3238
3239 } // ns zero_dim_detail
3240
3241
3251 template<typename TrackerType, typename EndgameType, typename SystemType>
3253 private zero_dim_detail::OwnedHomotopy<SystemType>,
3254 public HomotopySolver<TrackerType, EndgameType, SystemType>
3255 {
3258 using FactoryT = typename OwnedT::FactoryT;
3259
3266 ZeroDimSolver(SystemType const& target,
3268 : OwnedT(target, std::move(factory), ZeroDimConfig{}.path_variable_name),
3270 {}
3271
3274 SystemType const& RecordsTargetSystem() const override { return OwnedT::UserTarget(); }
3275
3277 bool WasRandomized() const { return this->was_randomized_; }
3280
3281 protected:
3294 void PostEGAction() override
3295 {
3296 EngineT::PostEGAction(); // the engine's finite/real/multiplicity/singular classification
3297
3298 if (!this->was_randomized_)
3299 return;
3300
3301 // genuine roots satisfy the original system to ~solve accuracy; spurious ones miss it
3302 // by O(1). A generous multiple of the endgame tolerance separates the two cleanly.
3303 const double threshold =
3304 1e3 * static_cast<double>(this->template Get<TolerancesConfig>().newton_during_endgame);
3305
3306 for (decltype(this->num_start_points_) ii{0}; ii < this->num_start_points_; ++ii)
3307 {
3308 auto& smd = this->solution_final_metadata_[ii];
3309 if (smd.endgame_success_code != SuccessCode::Success || !smd.is_finite)
3310 continue;
3311
3312 auto user_pt = this->TargetSystem().DehomogenizePoint(this->solutions_post_endgame_[ii]);
3313 // Evaluate the original system in DOUBLE precision: the filter only needs to tell a
3314 // genuine root (tiny residual) from an extraneous one (O(1)), and a double residual
3315 // does that robustly without a precision mismatch between the (possibly AMP/high
3316 // precision) solution point and the original system's working precision.
3317 Vec<complex_dbl> pt_d(user_pt.size());
3318 for (Eigen::Index k = 0; k < user_pt.size(); ++k)
3319 pt_d(k) = complex_dbl(user_pt(k));
3320 auto residual = static_cast<NumErrorT>(
3321 this->original_natural_target_.template Eval<complex_dbl>(pt_d).template lpNorm<Eigen::Infinity>());
3322 smd.function_residual = residual; // report residual against the ORIGINAL system
3323 if (static_cast<double>(residual) > threshold)
3324 smd.is_nonsolution = true; // extraneous: finite, but not a solution of the original system
3325 }
3326 }
3327
3328#ifdef BERTINI2_HAVE_MPI
3329 // Broadcast rank 0's authoritative owned systems to every rank, then re-seat the engine's
3330 // references and let the caller (RunParallel) re-point the tracker at the homotopy.
3331 void DistributeSystems(MPI_Comm comm) override
3332 {
3333 parallel::mpi_broadcast_serialized(comm, this->owned_target_, 0);
3334 // the OWNING shared_ptr<StartSystem> carries the concrete derived type polymorphically
3335 // (BOOST_CLASS_EXPORT); a base reference would serialize only the System slice.
3336 parallel::mpi_broadcast_serialized(comm, this->owned_start_, 0);
3337 parallel::mpi_broadcast_serialized(comm, this->owned_homotopy_, 0);
3338 this->ResetSystems(this->owned_homotopy_, *this->owned_start_, this->owned_target_);
3339 }
3340#endif
3341 };
3342
3343 } // ns algo
3344
3345} // ns bertini
3346
3347// Include output formatters after ZeroDim is fully defined.
3348// output.hpp includes zero_dim_solve.hpp, so #pragma once prevents re-inclusion
3349// and the circular dependency is resolved. Any TU that gets zero_dim_solve.hpp
3350// therefore also gets output.hpp, making WriteMainData/WriteRawData instantiable.
3352
3353namespace bertini {
3354namespace algorithm {
3355
3356template<typename TrackerType, typename EndgameType, typename SystemType>
3357inline void
3362
3363template<typename TrackerType, typename EndgameType, typename SystemType>
3364inline void
3369
3370template<typename TrackerType, typename EndgameType, typename SystemType>
3371inline void
3376
3377template<typename TrackerType, typename EndgameType, typename SystemType>
3378inline void
3383
3384template<typename TrackerType, typename EndgameType, typename SystemType>
3385inline void
3390
3391template<typename TrackerType, typename EndgameType, typename SystemType>
3392inline void
3397
3398template<typename TrackerType, typename EndgameType, typename SystemType>
3399inline void
3404
3405} // ns algorithm
3406} // ns bertini
3407
3408// Include config parsers after ZeroDim is fully defined, then provide the
3409// out-of-line definition of ApplyParsedConfigs. Parsing headers do not include
3410// zero_dim_solve.hpp, so there is no circular dependency here.
3412
3413namespace bertini {
3414namespace algorithm {
3415
3421// Injects every element of a std::tuple<Ts...> into `target` via Set<T>.
3422// Works for any Configured<>-derived target whose typelist contains all Ts.
3423template<typename Target, typename... Ts>
3424void InjectParsedTuple(Target& target, std::tuple<Ts...> const& t) {
3425 (target.template Set<Ts>(std::get<Ts>(t)), ...);
3426}
3427
3428template<typename TrackerType, typename EndgameType, typename SystemType>
3429void
3430HomotopySolver<TrackerType,EndgameType,SystemType>
3431 ::ApplyParsedConfigs(std::string const& config_str)
3432{
3433 using namespace parsing::classic;
3434
3435 // 1. solver-owned configs (Tolerances, PostProcessing, ZeroDimConf, AutoRetrack)
3436 using ZDConfs = typename Config::UsedConfigs;
3437 auto zd = ConfigParser<ZDConfs>::Parse(config_str);
3438 InjectParsedTuple(*this, zd);
3439 // The path variable name comes from ZeroDimConf, so the homotopy must be
3440 // re-formed if the parsed name differs from the one used at construction.
3441 // Only re-run setup in that case: re-running unconditionally re-randomizes
3442 // gamma and the start system for no reason, and (before Homogenize() was made
3443 // idempotent) re-corrupted the prepared target system.
3444 // Compare against the collision-free name actually used at construction (the configured
3445 // name is a base that UniquePathVariableName may have mangled), so an unchanged config
3446 // does not spuriously look like a rename and force a rebuild.
3447 auto const effective_path_variable_name =
3448 UniquePathVariableName(this->TargetSystem(), this->template Get<ZeroDimConf>().path_variable_name);
3449 if (!Homotopy().HavePathVariable()
3450 || Homotopy().GetPathVariable()->name() != effective_path_variable_name)
3451 {
3453 }
3454
3455 // 2. Tracker — the classic file's own tracker settings, then the phase tolerance the solver
3456 // drives. Setup() still takes stepping and Newton positionally.
3457 using TkConfs = detail::TypeList<
3461 auto tk = ConfigParser<TkConfs>::Parse(config_str);
3462 tracker_.template Set<tracking::TrackerConfig>(std::get<tracking::TrackerConfig>(tk));
3463 tracker_.Setup(
3464 std::get<tracking::TrackerConfig>(tk).predictor,
3465 this->template Get<Tolerances>().newton_before_endgame,
3466 std::get<tracking::SteppingConfig>(tk),
3467 std::get<tracking::NewtonConfig>(tk));
3468 tracker_.PrecisionSetup(PrecisionConfig(Homotopy()));
3469 endgame_.SetTracker(tracker_); // keep endgame's tracker ref consistent after reconfigure
3470
3471 // 3. Endgame-owned configs — endgame_ is the concrete EndgameType deriving from
3472 // Configured<its_configs>, so Set<T> is available directly.
3473 // AlgoTraits<EndgameType>::NeededConfigs is public (unlike EndgameType::Configs).
3474 using EGConfs = typename endgame::AlgoTraits<EndgameType>::NeededConfigs;
3475 auto eg = ConfigParser<EGConfs>::Parse(config_str);
3477
3478 // 4. Midpath config
3479 SetMidpath(ConfigParser<MidPathConfig>::Parse(config_str));
3480}
3481
3482} // ns algorithm
3483} // ns bertini
3484
3485
3486// Explicit instantiation declarations — suppress re-instantiation of the production solver types in
3487// every including TU. Six ZeroDimSolver combos (Tracker x Endgame) cover EVERY clone-owned start
3488// system (the start system is held polymorphically), plus six HomotopySolver combos for user
3489// homotopies. Definitions in core/src/eti/zero_dim_eti.cpp + zero_dim_blackbox_eti.cpp; see ADR-0014.
3490#include "bertini2/endgames.hpp"
3492
3493namespace bertini{ namespace algorithm{
3494
3495extern template struct ZeroDimSolver<tracking::DoublePrecisionTracker, typename endgame::EndgameSelector<tracking::DoublePrecisionTracker>::PSEG, System>;
3496extern template struct ZeroDimSolver<tracking::DoublePrecisionTracker, typename endgame::EndgameSelector<tracking::DoublePrecisionTracker>::Cauchy, System>;
3497extern template struct ZeroDimSolver<tracking::MultiplePrecisionTracker, typename endgame::EndgameSelector<tracking::MultiplePrecisionTracker>::PSEG, System>;
3498extern template struct ZeroDimSolver<tracking::MultiplePrecisionTracker, typename endgame::EndgameSelector<tracking::MultiplePrecisionTracker>::Cauchy, System>;
3499extern template struct ZeroDimSolver<tracking::AMPTracker, typename endgame::EndgameSelector<tracking::AMPTracker>::PSEG, System>;
3500extern template struct ZeroDimSolver<tracking::AMPTracker, typename endgame::EndgameSelector<tracking::AMPTracker>::Cauchy, System>;
3501
3502// user homotopies: the engine over caller-owned systems. definitions in zero_dim_blackbox_eti.cpp
3503extern template struct HomotopySolver<tracking::DoublePrecisionTracker, typename endgame::EndgameSelector<tracking::DoublePrecisionTracker>::PSEG, System>;
3504extern template struct HomotopySolver<tracking::DoublePrecisionTracker, typename endgame::EndgameSelector<tracking::DoublePrecisionTracker>::Cauchy, System>;
3505extern template struct HomotopySolver<tracking::MultiplePrecisionTracker, typename endgame::EndgameSelector<tracking::MultiplePrecisionTracker>::PSEG, System>;
3506extern template struct HomotopySolver<tracking::MultiplePrecisionTracker, typename endgame::EndgameSelector<tracking::MultiplePrecisionTracker>::Cauchy, System>;
3507extern template struct HomotopySolver<tracking::AMPTracker, typename endgame::EndgameSelector<tracking::AMPTracker>::PSEG, System>;
3508extern template struct HomotopySolver<tracking::AMPTracker, typename endgame::EndgameSelector<tracking::AMPTracker>::Cauchy, System>;
3509
3510}} // namespaces
provides a common base type for algorithms to inherit from, for the abstraction layer above for calli...
virtual bool ObservableIsA(std::type_index t) const
Whether this observable may be observed as the given type.
Definition observable.hpp:166
void NotifyObservers(AnyEvent const &e) const
Sends an event to observers that subscribed to its exact dynamic type, then to all catch-all (untyped...
Definition observable.hpp:185
A deterministic, fully-specified random stream (SHA-256 counter mode).
Definition derive.hpp:78
static std::shared_ptr< OutputDirectory > Shared(std::filesystem::path const &root)
The process-shared OutputDirectory at root: one instance – hence one session history file – per direc...
Abstract base class for other start systems.
Definition start_base.hpp:83
virtual unsigned long long NumStartPoints() const =0
Get the number of start points (an upper bound on the number of target solutions).
Roots-of-unity start system for 1-homogeneous polynomial systems.
Definition total_degree_binomial.hpp:50
Observer that records the precision a whole path used, across every call to TrackPath that tracks it:...
Definition observers.hpp:244
Emit a System (and, with a config, a whole solve) as a Bertini 1 classic input file.
Canonical exact encoding + persistent digests for configuration structs (ADR-0043; the structured-out...
Contains the detail/configured helper type, for templated getting/setting/storage of config structs.
Bertini extensions of the Eigen linear algebra library.
Collects the various header files which define the Bertini2 endgames.
Provides the generators for bertini2.
Render a System's structure as JSON: named fields for its parts.
Provides methods for checking for path crossings.
Events emitted by the higher-level algorithms (e.g.
Definition zero_dim_solve.hpp:3105
Definition algorithm_base.hpp:34
typename SolnCont< complex_dbl >::size_type SolnIndT
Index type for solutions/paths (over a double-precision solution container).
Definition zero_dim_solve.hpp:158
std::ostream & operator<<(std::ostream &out, const SolutionMetaData< NumT > &meta)
Stream insertion: write the metadata fields one per line (used by the Python bindings).
Definition zero_dim_solve.hpp:300
SolveReport SummarizeSolve(std::vector< SolutionMetaData< ComplexT > > const &metadata, MidpathCheckReport const &midpath)
Build a SolveReport from the per-endpoint final metadata and the midpath-crossing report.
Definition zero_dim_solve.hpp:457
void InjectParsedTuple(Target &target, std::tuple< Ts... > const &t)
Inject every element of a tuple of config structs into a Configured-derived target via Set<T>.
Definition zero_dim_solve.hpp:3424
std::vector< T > SolnCont
Definition config.hpp:32
@ Completed
The default. Reuse completed paths; re-track abandoned ones – except one cut off by a wall-clock limi...
Definition config.hpp:184
@ Nothing
Track every path fresh, even when recorded (observers, benchmarking, re-verification)....
Definition config.hpp:183
unsigned DefaultInitialAmbientPrecision()
The default ambient precision for a ZeroDimConfig, by complex type.
Definition config.hpp:162
Digest256 Sha256(std::string_view data)
SHA-256 (FIPS 180-4) of a byte string.
boost::json::object SystemPartsJson(System const &sys)
A System's structure as a JSON object with fields for its parts.
Definition json_writer.hpp:65
unsigned EffectiveThreadCount(unsigned configured=0)
Resolve a configured thread count to the number of worker threads to actually spawn.
Definition thread_pool.hpp:122
int Size()
Get the number of ranks (1 in serial builds).
bool IsManager()
Query whether this rank is the manager (rank 0).
Definition classic_utilities.hpp:41
Definition visitable.hpp:36
Definition config_encoding.hpp:66
boost::json::object EncodeFullPathResult(parallel::FullPathResult< ComplexT > const &r)
Serialize one whole-path result as a b2rec/1 track record body.
Definition solver_recording.hpp:192
boost::json::object ProducerInfo()
The producer object recorded in every run header.
std::string TimeStampNow()
The record timestamp, now: YYYY-MM-DD HH:MM local time – the one clock format every record kind uses ...
std::string CanonicalEncoding(tracking::SteppingConfig const &c)
Canonical encoding of stepping settings.
std::string ConfigTextAsJson(std::string const &canonical_text, std::string const &digest_hex)
Render one or more canonical config encodings as pretty JSON, for archiving in a structured output di...
DrawStream & ThreadDrawStream()
This thread's pinned draw stream — the source of every identity-relevant random draw in bertini2.
std::string CoarsePathStatus(SuccessCode code)
The coarse outcome of a path, from its endgame verdict: "success", "diverged" (the path went to infin...
Definition solver_recording.hpp:175
std::string SystemEncodingAsJson(std::string const &encoding_text, std::string const &digest_hex, boost::json::object const &parts)
The archived form of a system definition: a machine-parseable JSON document {"schema",...
parallel::FullPathResult< ComplexT > DecodeFullPathResult(boost::json::object const &rec, std::size_t path_index)
Reconstruct a whole-path result from a track record (the recall half).
Definition solver_recording.hpp:242
boost::json::array EncodePoint(Vec< ComplexT > const &point)
Encode a point (vector of complex coordinates) as an array of scalar triples.
Definition solver_recording.hpp:109
constexpr char ConfigEncodingVersion[]
The version tag baked into every config digest; bump on any encoding change.
Definition config_encoding.hpp:69
std::optional< std::string > AmbientRecordsPath()
Resolve the ambient records directory from the environment – the ONE place the BERTINI_RECORDS_DIR se...
constexpr char RecordSchemaVersion[]
The record-format version tag written on run headers; see docs/records/b2rec-1.md.
Definition output_directory.hpp:62
Definition configIni_parse.hpp:78
Definition zero_dim_solve.hpp:68
StartSystemFactory< SystemType > MakeStartFactory()
Build a StartSystemFactory that mints a concrete start system from the target.
Definition start_base.hpp:68
std::function< std::shared_ptr< StartSystem >(SystemType const &)> StartSystemFactory
A factory that builds a start system from a (prepared) target system.
Definition start_base.hpp:54
Predictor DefaultPredictor()
Get the Bertini2 default predictor.
Definition explicit_predictors.hpp:70
unsigned Order(Predictor predictor_choice)
The order of the predictor.
Definition explicit_predictors.hpp:85
Definition algorithm_builder.hpp:36
System Clone(System const &sys)
Do a deep clone of the system.
constexpr unsigned DoublePrecision()
The number of digits considered "double precision" (16).
Definition num_traits.hpp:152
std::complex< double > complex_dbl
The double-precision complex number type.
Definition double_extensions.hpp:47
Eigen::Matrix< NumT, Eigen::Dynamic, 1 > Vec
A dynamically-sized column vector of NumT.
Definition eigen_extensions.hpp:296
void SetGlobalSeed(unsigned long seed)
Set the global RNG seed.
std::mt19937 & ThreadEngine()
Returns this thread's canonical mt19937 engine.
unsigned long GetGlobalSeed()
Returns the effective global seed.
void ReseedThisThread(uint64_t stream_key)
Reseed this thread's engine deterministically from the global seed mixed with stream_key.
void SetThreadPrecision(unsigned prec)
Set the precision (digits) for the current thread only (does not touch the global default).
Definition mpfr_complex.hpp:176
Eigen::Matrix< NumT, Eigen::Dynamic, Eigen::Dynamic > Mat
A dynamically-sized matrix of NumT.
Definition eigen_extensions.hpp:298
double NumErrorT
The numeric type used to hold error/tolerance magnitudes throughout Bertini2 – tracking tolerances,...
Definition num_traits.hpp:48
auto DefaultPrecision()
Get the global default precision (digits) for multiprecision numbers.
Definition mpfr_complex.hpp:135
SuccessCode
Definition config.hpp:42
@ NeverStarted
No tracking was attempted.
Definition config.hpp:50
@ ExternallyTerminated
Definition config.hpp:63
@ Success
Definition config.hpp:51
@ GoingToInfinity
Definition config.hpp:54
@ WallClockLimitReached
The tracker's wall-clock deadline passed between steps; the path was abandoned where it was....
Definition config.hpp:68
@ SecurityMaxNormReached
Definition config.hpp:65
bool IsDistinct(Eigen::MatrixBase< DerivedA > const &a, Eigen::MatrixBase< DerivedB > const &b, NumErrorT tol)
True if two points differ by more than tol in the infinity norm (the negation of :func:IsSamePoint).
Definition eigen_extensions.hpp:665
unsigned Precision(Eigen::MatrixBase< Derived > const &v)
Get the precision of an Eigen object.
Definition eigen_extensions.hpp:339
T max(T const &a, T const &b, T const &c)
Three-argument form of max.
Definition mpfr_extensions.hpp:96
System MakeHomotopy(System const &target, System const &start, std::string const &path_variable_name="", std::shared_ptr< node::Node > const &gamma=nullptr)
Form the gamma-trick straight-line homotopy H = (1-t)*target + gamma*t*start.
bool StopRequested()
Has somebody asked this process to stop?
std::string UniquePathVariableName(System const &target, std::string base="t")
Produce a path-variable name guaranteed not to collide with any variable in target.
Provides an Eigen-like NumTraits struct for querying traits of a number type.
Contains the observable base types.
Provides some outputting classes for printing results of running algorithms.
The structured output directory: durable, self-documenting records of computations (b2rec/1; ADR-0045...
Collective include file for parallelism in Bertini2.
Which software produced a record: name, version, and source commit, for the producer field of run hea...
Provides the parsers for settings in bertini2.
FullPathResult <-> track-record serialization (b2rec/1; the arc's rung 4).
Defines generic start system type.
Aggregate include for the various start systems in Bertini2.
Asking a long computation to stop, from outside it.
Numeric traits for a bertini number type: digit counts, conversions, and companion types.
Definition num_traits.hpp:57
detail::TypeList< TolerancesConfig, PostProcessingConfig, ZeroDimConfig, AutoRetrackConfig, RecordsConfig, MidPathConfig > NeededConfigs
The config types this algorithm reads (drives the reusable Python config interface).
Definition zero_dim_solve.hpp:106
typename tracking::TrackerTraits< TrackerType >::BaseComplexT BaseComplexT
The complex number type of the tracker.
Definition zero_dim_solve.hpp:103
typename tracking::TrackerTraits< TrackerType >::BaseRealT BaseRealT
The real number type of the tracker.
Definition zero_dim_solve.hpp:102
Definition config.hpp:271
metadata structs
Definition zero_dim_solve.hpp:165
SolnIndT number_path_successes
Number of paths that succeeded.
Definition zero_dim_solve.hpp:168
std::chrono::microseconds elapsed_time
Total wall-clock time the solve took.
Definition zero_dim_solve.hpp:172
SolnIndT number_paths_tracked
Total number of paths tracked.
Definition zero_dim_solve.hpp:169
std::chrono::system_clock::time_point start_time
Wall-clock time the solve started.
Definition zero_dim_solve.hpp:171
SolnIndT number_path_failures
Number of paths that failed.
Definition zero_dim_solve.hpp:167
Type-erased base for all nag algorithms, exposing a single Run() entry point.
Definition algorithm_base.hpp:46
Type-erased base for a zero-dimensional solve, exposing output and config entry points.
Definition zero_dim_solve.hpp:119
virtual void WriteRawSolutions(std::ostream &out) const =0
Write the raw solutions (Bertini 1.7 format) to out.
virtual void WriteSingularSolutions(std::ostream &out) const =0
Write the singular solutions (Bertini 1.7 format) to out.
virtual void WriteNonsingularSolutions(std::ostream &out) const =0
Write the nonsingular solutions (Bertini 1.7 format) to out.
virtual void ApplyParsedConfigs(std::string const &config_str)=0
Apply configuration settings parsed from a classic-input config string.
virtual void WriteRawData(std::ostream &out) const =0
Write the raw-data report to out.
virtual void WriteRealFiniteSolutions(std::ostream &out) const =0
Write the real finite solutions (Bertini 1.7 format) to out.
virtual void RecordToPath(std::string const &)
Attach a structured output directory at path (records + resume; ADR-0046).
Definition zero_dim_solve.hpp:139
virtual std::string RecordsRunIdentity() const
This solve's run id in the records (empty when not recording).
Definition zero_dim_solve.hpp:141
virtual void AppendRecordJson(std::string const &)
Append one raw record (a JSON object as text) to the attached directory; no-op when not recording.
Definition zero_dim_solve.hpp:144
virtual std::string PutRecordsDefinition(std::string const &, std::string const &, std::string const &={})
Store content as a definition of the given kind ("systems"/"configs"/ "givens"), optionally labeled b...
Definition zero_dim_solve.hpp:148
virtual ~AnyZeroDim()=default
virtual void WriteFiniteSolutions(std::ostream &out) const =0
Write the finite solutions (Bertini 1.7 format) to out.
virtual void WriteMainData(std::ostream &out) const =0
Write the human-readable main-data report to out.
Configuration for auto-retracking paths flagged as crossed.
Definition config.hpp:90
State captured at the endgame boundary, handed off to start the endgame.
Definition zero_dim_solve.hpp:343
typename NumTraits< ComplexT >::Real RealT
The real number type.
Definition zero_dim_solve.hpp:344
unsigned precision
Definition zero_dim_solve.hpp:355
bool operator==(const EGBoundaryMetaData< ComplexT > &other)
Equality comparison (compares all fields).
Definition zero_dim_solve.hpp:368
EGBoundaryMetaData(Vec< ComplexT > const &pt, SuccessCode const &code, RealT const &ss, unsigned prec)
Construct from the boundary point, its success code, the last step size, and the precision in use.
Definition zero_dim_solve.hpp:363
Vec< BaseComplexT > path_point
Definition zero_dim_solve.hpp:346
RealT last_used_stepsize
Definition zero_dim_solve.hpp:348
SuccessCode success_code
Definition zero_dim_solve.hpp:347
unsigned precision_
The ambient default precision at solve entry.
Definition zero_dim_solve.hpp:668
~ScopedSessionState()
Restore it, whatever the solve did in between.
Definition zero_dim_solve.hpp:679
ScopedSessionState(ScopedSessionState const &)=delete
Non-copyable.
ScopedSessionState & operator=(ScopedSessionState const &)=delete
Non-assignable.
ScopedSessionState()
Capture the ambient session state.
Definition zero_dim_solve.hpp:673
records::DrawStream stream_
The calling thread's pinned draw stream at entry.
Definition zero_dim_solve.hpp:670
std::mt19937 engine_
The calling thread's legacy engine state at entry.
Definition zero_dim_solve.hpp:669
The continuation primitive: given a homotopy + a source of start points, track each path through the ...
Definition zero_dim_solve.hpp:546
SolnCont< Vec< BaseComplexT > > RealSolutions(bool user_coords=true, bool merge_multiplicities=false) const
The real finite solutions (is_real applies the configured tolerance).
Definition zero_dim_solve.hpp:1341
const auto & EndgameBoundarySolutions() const
Get the solutions as computed at the endgame boundary.
Definition zero_dim_solve.hpp:1535
C SystemT
Definition zero_dim_solve.hpp:550
NumErrorT DefaultPointMatchTolerance() const
The default point-match tolerance for MetadataForPoint / CoincidentMetadataForPoint.
Definition zero_dim_solve.hpp:1411
void TallyStoppedSolve(std::vector< SolnIndT > const &dispatched)
After dispatching paths, work out whether this solve was cut short, and say so.
Definition zero_dim_solve.hpp:2953
const auto & MidpathRetrackTol() const
Definition zero_dim_solve.hpp:979
void ClassifyFiniteAndReal()
Classify each successful endpoint as finite/infinite, and (when finite) real.
Definition zero_dim_solve.hpp:2244
detail::Configured< typename AlgoTraits< HomotopySolver< A, B, C > >::NeededConfigs > Config
Definition zero_dim_solve.hpp:576
SolutionMetaData< BaseComplexT > SolutionMetaDataT
Definition zero_dim_solve.hpp:589
std::vector< boost::json::object > records_start_refs_
Definition zero_dim_solve.hpp:3027
void ComputePostTrackMetadata()
Populates the result_ member, with the post-processed results of the zero dim solve.
Definition zero_dim_solve.hpp:2226
const auto & SolutionsInternalCoords() const
Get the computed solutions in the solver's internal coordinates: homogenized, and lying on the target...
Definition zero_dim_solve.hpp:1296
bool stopped_early_
Definition zero_dim_solve.hpp:3033
void SetMidpath(MidPathConfig const &mp)
Apply a configuration to the midpath (path-crossing) checker.
Definition zero_dim_solve.hpp:1019
SolnCont< Vec< BaseComplexT > > SolutionsWhere(Pred pred, bool user_coords=true, bool merge_multiplicities=false) const
Select the solution points whose parallel metadata satisfies a predicate.
Definition zero_dim_solve.hpp:1309
std::string PutRecordsDefinition(std::string const &content, std::string const &kind, std::string const &label={}) override
Store a definition of the given kind (optional role label in the filename) in the attached directory ...
Definition zero_dim_solve.hpp:2552
void MaybeAttachAmbientRecords()
Attach the ambient output directory (manager rank only; workers never touch the records).
Definition zero_dim_solve.hpp:2626
void SetToEndgame(T const &t)
Set a configuration struct of type T on the endgame.
Definition zero_dim_solve.hpp:1123
bool records_run_resumed_
Definition zero_dim_solve.hpp:3030
SolnCont< EGBoundaryMetaDataT > solutions_at_endgame_boundary_
computed data
Definition zero_dim_solve.hpp:3092
PostProcessingConfig PostProcessing
Definition zero_dim_solve.hpp:583
const T & Get() const
Retrieve a stored configuration struct by type (inherited from Configured).
Definition configured.hpp:94
static Vec< NumErrorT > EndpointSingularValues(SystemType const &sys, Vec< BaseComplexT > const &endpoint)
The singular values of the system's Jacobian at an endpoint, largest first.
Definition zero_dim_solve.hpp:2378
unsigned long long NumPathsNeverStarted() const
How many paths the last Solve() never began, because it was stopped.
Definition zero_dim_solve.hpp:2610
NumErrorT midpath_retrack_tolerance_
Definition zero_dim_solve.hpp:3073
std::shared_ptr< records::OutputDirectory > records_
The attached output directory (null = not recording).
Definition zero_dim_solve.hpp:3025
void DeclareFiniteSolutionsResult()
Auto-declare this run's finite solutions as a result record and render the derived views – "what were...
Definition zero_dim_solve.hpp:2565
const TrackerType & GetTracker() const
Gets the tracker.
Definition zero_dim_solve.hpp:1064
B endgame_
Definition zero_dim_solve.hpp:3086
unsigned long long num_recalled_
Definition zero_dim_solve.hpp:3031
AutoRetrackConfig AutoRetrack
Definition zero_dim_solve.hpp:585
std::reference_wrapper< const C > homotopy_
Definition zero_dim_solve.hpp:603
std::string const & RecordsRunId() const
This solve's run id in the records (empty when not recording).
Definition zero_dim_solve.hpp:2613
void StoreFullPathResult(parallel::FullPathResult< BaseComplexT > const &r)
Install a whole-path result (from PackFullPathResult) into the shared result arrays.
Definition zero_dim_solve.hpp:2463
SolnCont< Vec< BaseComplexT > > NonsingularSolutions(bool user_coords=true, bool merge_multiplicities=false) const
The nonsingular finite solutions (simple, well-conditioned roots).
Definition zero_dim_solve.hpp:1348
const EndgameType & GetEndgame() const
Gets the endgame.
Definition zero_dim_solve.hpp:1098
static NumErrorT SpectralConditionNumberOf(Vec< NumErrorT > const &singular_values)
The spectral condition number from a spectrum: largest over smallest singular value.
Definition zero_dim_solve.hpp:2397
SolutionMetaDataT MetadataForPoint(Vec< BaseComplexT > const &point, NumErrorT tol, bool user_coords=true, bool representatives_only=true) const
The metadata of the solution matching point – the cluster REPRESENTATIVE (issue #302).
Definition zero_dim_solve.hpp:1451
EGBoundaryMetaData< BaseComplexT > EGBoundaryMetaDataT
Definition zero_dim_solve.hpp:588
SolnCont< SolutionMetaDataT > solution_final_metadata_
Definition zero_dim_solve.hpp:3096
static NumErrorT EndpointSpectralConditionNumber(SystemType const &sys, Vec< BaseComplexT > const &endpoint)
The spectral-norm condition number of the system's Jacobian at an endpoint.
Definition zero_dim_solve.hpp:2362
const SystemType & TargetSystem() const
Definition zero_dim_solve.hpp:624
SolnCont< Vec< BaseComplexT > > InfiniteSolutions(bool user_coords=true) const
The solutions at infinity: endpoints not classified finite (is_finite is false) – the complement of F...
Definition zero_dim_solve.hpp:1384
void DefaultSetup()
setup functions
Definition zero_dim_solve.hpp:927
std::reference_wrapper< const StartSystemBaseT > start_system_
Definition zero_dim_solve.hpp:602
void ReadSolveTimeoutAsAnInterrupt(SuccessCode &code, std::chrono::steady_clock::time_point path_start) const
A path the WHOLE-SOLVE budget cut off reads as interrupted, not as over its own budget.
Definition zero_dim_solve.hpp:3056
virtual SystemType const & RecordsTargetSystem() const
The system the records name as this solve's TARGET: what the user asked about.
Definition zero_dim_solve.hpp:2541
unsigned long long NumPathsRecalled() const
How many paths the last Solve() recalled from records instead of computing.
Definition zero_dim_solve.hpp:2595
std::vector< SolnIndT > RecallRecordedPaths(std::vector< SolnIndT > const &all_indices)
Recall recorded paths into the solver's state and return the indices still to compute.
Definition zero_dim_solve.hpp:2876
void ResetSystems(SystemType const &hom, StartSystemBaseT const &start, SystemType const &target)
Re-seat the system references (used after an MPI broadcast re-materializes the owned systems).
Definition zero_dim_solve.hpp:608
const T & GetFromEndgame() const
Get a configuration struct of type T from the endgame.
Definition zero_dim_solve.hpp:1116
void DefaultTrackerSetup()
Takes the default action to set up the zero dim algorithm with the default constructed tracker.
Definition zero_dim_solve.hpp:1030
std::shared_ptr< records::OutputDirectory > const & Records() const
The attached output directory (null when not recording).
Definition zero_dim_solve.hpp:2586
void ComputeMultiplicities()
Cluster identical endpoints to compute multiplicities.
Definition zero_dim_solve.hpp:2282
MidpathChecker< BaseRealT, BaseComplexT, EGBoundaryMetaData< BaseComplexT > > MidpathType
Definition zero_dim_solve.hpp:594
unsigned long long num_start_points_
Number of start points the start system produces.
Definition zero_dim_solve.hpp:3072
void ApplyParsedConfigs(std::string const &config_str) override
Apply configuration settings parsed from a classic-input config string.
Definition zero_dim_solve.hpp:3431
std::string records_start_identity_
Definition zero_dim_solve.hpp:3028
void RecordCompletedPath(parallel::FullPathResult< BaseComplexT > const &r)
Emit the path record for one completed path into the run's results file (called from StoreFullPathRes...
Definition zero_dim_solve.hpp:2982
void SetStartPointPrecision(unsigned p)
Set the precision at which start points are computed.
Definition zero_dim_solve.hpp:990
SolnCont< Vec< BaseComplexT > > solutions_user_coords_
Definition zero_dim_solve.hpp:3094
void AppendEndgameConfigEncodings(std::ostringstream &text, detail::TypeList< EGConfTs... >) const
Append the canonical encodings of the endgame's configs (its AlgoTraits::NeededConfigs,...
Definition zero_dim_solve.hpp:2734
TrackerType & GetTracker()
Gets the tracker.
Definition zero_dim_solve.hpp:1074
void EnsureRunRecorded()
Ensure this solve's run header is on record (appending it if absent) and set the run id.
Definition zero_dim_solve.hpp:2745
void DefaultSettingsSetup()
Fills the tolerances and retrack settings from default values.
Definition zero_dim_solve.hpp:953
bertini::start_system::StartSystem StartSystemBaseT
Definition zero_dim_solve.hpp:553
unsigned start_point_precision_
Definition zero_dim_solve.hpp:3075
void SetMidpathRetrackTol(NumErrorT const &rt)
Set the tolerance used when re-tracking paths flagged by the midpath check.
Definition zero_dim_solve.hpp:973
ZeroDimConfig ZeroDimConf
Definition zero_dim_solve.hpp:584
bool WasStoppedEarly() const
Was the last Solve() stopped before it finished?
Definition zero_dim_solve.hpp:2607
void AppendRecordJson(std::string const &record_json) override
Append a raw JSON record to the attached directory (no-op if none).
Definition zero_dim_solve.hpp:2544
NumErrorT SamePointTolerance() const
The same-point (clustering) tolerance: final_tolerance * same_point_tolerance_multiplier.
Definition zero_dim_solve.hpp:1425
void DefaultSystemSetup()
Read off the number of start points the (already-built) start system will produce.
Definition zero_dim_solve.hpp:941
MidpathCheckReport midpath_report_
populated by EGBoundaryAction; exposed via EndgameBoundaryMetadata()
Definition zero_dim_solve.hpp:3074
void SetRecordedStartProvenance(std::vector< boost::json::object > refs, std::string start_identity)
Declare where this solve's start points came from, for the records (chains and givens).
Definition zero_dim_solve.hpp:2652
void RecordToPath(std::string const &path) override
Attach an output directory by path (the AnyZeroDim polymorphic hook).
Definition zero_dim_solve.hpp:2523
SolveReport Report() const
A concise summary of the solve: how every path ended, by category, and whether any path was lost.
Definition zero_dim_solve.hpp:1558
typename tracking::TrackerTraits< A >::BaseComplexT BaseComplexT
Definition zero_dim_solve.hpp:567
const MidpathCheckReport & EndgameBoundaryMetadata() const
Get the report from the midpath (path-crossing) check performed at the endgame boundary: how many cro...
Definition zero_dim_solve.hpp:1547
bool recalling_
True while replaying recorded paths (suppresses re-emission).
Definition zero_dim_solve.hpp:3029
typename tracking::TrackerTraits< A >::BaseRealT BaseRealT
Definition zero_dim_solve.hpp:568
void WriteRawSolutions(std::ostream &out) const override
Write the raw solutions (Bertini 1.7 format) to out.
Definition zero_dim_solve.hpp:3400
TolerancesConfig Tolerances
Definition zero_dim_solve.hpp:582
const SystemType & Homotopy() const
Definition zero_dim_solve.hpp:628
bool SolveDeadlinePassed() const
Whether the whole-solve wall-clock budget has run out.
Definition zero_dim_solve.hpp:3037
bool ObservableIsA(std::type_index t) const override
Whether this observable may be observed as the given type.
Definition zero_dim_solve.hpp:558
void SetEndgame(EndgameType const &new_endgame)
endgame specific stuff
Definition zero_dim_solve.hpp:1088
const StartSystemBaseT & StartSystem() const
Definition zero_dim_solve.hpp:626
tracking::PathPrecisionRecorder< TrackerType > path_prec_
observers used during tracking
Definition zero_dim_solve.hpp:3081
void WriteFiniteSolutions(std::ostream &out) const override
Write the finite solutions (Bertini 1.7 format) to out.
Definition zero_dim_solve.hpp:3372
void WriteSingularSolutions(std::ostream &out) const override
Write the singular solutions (Bertini 1.7 format) to out.
Definition zero_dim_solve.hpp:3393
B EndgameT
Definition zero_dim_solve.hpp:549
unsigned long long NumPaths() const
How many paths there are to answer: the start system's start point count.
Definition zero_dim_solve.hpp:2592
std::string RecordsRunIdentity() const override
This solve's run id (the AnyZeroDim polymorphic hook).
Definition zero_dim_solve.hpp:2529
unsigned StartPointPrecision() const
Definition zero_dim_solve.hpp:997
typename tracking::TrackerTraits< A >::PrecisionConfig PrecisionConfig
Definition zero_dim_solve.hpp:570
A TrackerT
Definition zero_dim_solve.hpp:548
void Solve()
the main functions
Definition zero_dim_solve.hpp:1145
void WriteNonsingularSolutions(std::ostream &out) const override
Write the nonsingular solutions (Bertini 1.7 format) to out.
Definition zero_dim_solve.hpp:3386
HomotopySolver(SystemType const &target, StartSystemBaseT const &start, SystemType const &homotopy)
constructors
Definition zero_dim_solve.hpp:644
void WriteRawData(std::ostream &out) const override
Write the raw-data report to out.
Definition zero_dim_solve.hpp:3365
const auto & SolutionsUserCoords() const
Get the computed solutions, in the coordinates of the user's original variables.
Definition zero_dim_solve.hpp:1263
A tracker_
Definition zero_dim_solve.hpp:3085
bool start_point_precision_set_by_user_
Definition zero_dim_solve.hpp:3076
void DefaultMidpathSetup()
call this after setting up the tolerances, etc.
Definition zero_dim_solve.hpp:1006
std::string records_run_id_
This solve's run id in the records (ask hash prefix).
Definition zero_dim_solve.hpp:3026
void RecordTo(std::shared_ptr< records::OutputDirectory > directory)
Attach a structured output directory: this solve records to (and memoizes/resumes from) it.
Definition zero_dim_solve.hpp:2515
bool solutions_user_coords_fresh_
Whether the user-coordinate cache is up to date.
Definition zero_dim_solve.hpp:3095
boost::json::object RecordsAsk() const
The ask identity of this solve: op + tracker/endgame kind (stable record names, never typeid) + targe...
Definition zero_dim_solve.hpp:2707
SolnCont< Vec< BaseComplexT > > FiniteSolutions(bool user_coords=true, bool merge_multiplicities=false) const
The finite solutions: successful, finite endpoints that ARE solutions of the target (is_finite applie...
Definition zero_dim_solve.hpp:1334
typename SolnCont< BaseComplexT >::size_type SolnIndT
Definition zero_dim_solve.hpp:572
EndgameType & GetEndgame()
Gets the endgame.
Definition zero_dim_solve.hpp:1108
void SetTracker(TrackerType const &new_tracker)
Sets the tracker to one you supply to this function.
Definition zero_dim_solve.hpp:1053
parallel::FullPathResult< BaseComplexT > PackFullPathResult(SolnIndT idx) const
Pack everything computed for one whole path into a single result value (thread/MPI handoff).
Definition zero_dim_solve.hpp:2421
void Run() override
Main Run() function provided for calling from the blackbox mode.
Definition zero_dim_solve.hpp:692
std::vector< SolutionMetaDataT > CoincidentMetadataForPoint(Vec< BaseComplexT > const &point, NumErrorT tol, bool user_coords=true) const
All metadata records for the paths coincident with point (issue #302).
Definition zero_dim_solve.hpp:1503
void WriteMainData(std::ostream &out) const override
Write the human-readable main-data report to out.
Definition zero_dim_solve.hpp:3358
static bool AbandonmentIsReusable(parallel::FullPathResult< BaseComplexT > const &r, double current_limit)
Under RecallPolicy::Completed, may this recorded path stand in for tracking it?
Definition zero_dim_solve.hpp:2856
std::string CanonicalSettingsText() const
The canonical text of this solver's FULL settings: the algorithm-level configs, then the tracker's ow...
Definition zero_dim_solve.hpp:2679
std::reference_wrapper< const C > target_system_
Definition zero_dim_solve.hpp:601
bertini::start_system::StartSystem StartSystemT
Definition zero_dim_solve.hpp:552
SolnCont< Vec< BaseComplexT > > Nonsolutions(bool user_coords=true) const
The NONSOLUTIONS: finite, successful endpoints that are NOT solutions of the target system – the extr...
Definition zero_dim_solve.hpp:1367
unsigned long long num_never_started_
Definition zero_dim_solve.hpp:3070
void ClassifySingular()
Classify each successful endpoint as singular or not.
Definition zero_dim_solve.hpp:2326
std::optional< std::chrono::steady_clock::time_point > solve_deadline_
Definition zero_dim_solve.hpp:3034
const auto & SolutionMetadata() const
Get the metadat associated with the final computed solutions.
Definition zero_dim_solve.hpp:1392
void WriteRealFiniteSolutions(std::ostream &out) const override
Write the real finite solutions (Bertini 1.7 format) to out.
Definition zero_dim_solve.hpp:3379
SolnCont< Vec< BaseComplexT > > SingularSolutions(bool user_coords=true, bool merge_multiplicities=false) const
The singular finite solutions (multiple or ill-conditioned roots).
Definition zero_dim_solve.hpp:1355
SolnCont< Vec< BaseComplexT > > solutions_post_endgame_
Definition zero_dim_solve.hpp:3093
MidpathType midpath_
The midpath (path-crossing) checker.
Definition zero_dim_solve.hpp:3087
virtual void PostEGAction()
Action taken after the endgame: compute post-track metadata (ZeroDimSolver overrides to also filter e...
Definition zero_dim_solve.hpp:2214
Configuration for the midpath (path-crossing) check.
Definition config.hpp:80
Summary of what the midpath (path-crossing) check found at the endgame boundary, and what the algorit...
Definition zero_dim_solve.hpp:404
unsigned num_resolve_attempts
How many re-track attempts were actually performed.
Definition zero_dim_solve.hpp:407
bool passed
Did the final midpath check pass (no crossings remained)?
Definition zero_dim_solve.hpp:405
std::vector< unsigned long long > crossed_path_indices
Indices of the paths flagged as crossed on the first check.
Definition zero_dim_solve.hpp:408
unsigned num_crossings_detected
Number of crossed paths found on the first check, before any re-tracking.
Definition zero_dim_solve.hpp:406
Detects whether any solution paths crossed during tracking, before the endgame.
Definition midpath_check.hpp:52
Configuration for post-processing solutions (reality/finiteness/singularity classification).
Definition config.hpp:140
How a recording algorithm uses the records it finds: not what to compute, but what already-recorded w...
Definition config.hpp:197
Per-solution metadata gathered during and after a path track.
Definition zero_dim_solve.hpp:179
unsigned cycle_num
Definition zero_dim_solve.hpp:224
int multiplicity
Definition zero_dim_solve.hpp:239
bool is_real
Definition zero_dim_solve.hpp:241
bool operator==(const SolutionMetaData< ComplexT > &other)
Equality comparison over the identity fields (path_time_seconds is excluded).
Definition zero_dim_solve.hpp:258
SolnIndT solution_index
Definition zero_dim_solve.hpp:184
bool is_finite
Definition zero_dim_solve.hpp:242
BaseComplexT time_of_first_prec_increase
Definition zero_dim_solve.hpp:188
bool is_nonsolution
Definition zero_dim_solve.hpp:255
BaseComplexT final_time_used
Definition zero_dim_solve.hpp:215
bool crossing_unresolved
Definition zero_dim_solve.hpp:248
NumErrorT newton_residual
Definition zero_dim_solve.hpp:214
double path_time_seconds
Definition zero_dim_solve.hpp:190
unsigned num_failed_steps
Definition zero_dim_solve.hpp:192
bool is_singular
Definition zero_dim_solve.hpp:243
SuccessCode endgame_success_code
Definition zero_dim_solve.hpp:233
SuccessCode pre_endgame_success_code
Definition zero_dim_solve.hpp:203
unsigned num_successful_steps
Definition zero_dim_solve.hpp:191
Vec< BaseComplexT > latest_path_point
Definition zero_dim_solve.hpp:200
NumErrorT function_residual
Definition zero_dim_solve.hpp:237
unsigned precision_digits
Definition zero_dim_solve.hpp:231
bool multiplicity_representative
Definition zero_dim_solve.hpp:240
unsigned accuracy_digits
Definition zero_dim_solve.hpp:232
decltype(DefaultPrecision()) max_precision_used
Definition zero_dim_solve.hpp:189
double wall_clock_limit_seconds
Definition zero_dim_solve.hpp:193
NumErrorT accuracy_estimate_user_coords
Definition zero_dim_solve.hpp:223
NumErrorT accuracy_estimate_internal_coords
Definition zero_dim_solve.hpp:222
NumErrorT condition_number
Definition zero_dim_solve.hpp:207
Vec< NumErrorT > singular_values
Definition zero_dim_solve.hpp:213
bool precision_changed
Definition zero_dim_solve.hpp:187
SolnIndT path_index
Definition zero_dim_solve.hpp:183
typename SolnCont< ComplexT >::size_type SolnIndT
Index type for solutions/paths.
Definition zero_dim_solve.hpp:180
A concise, human-readable summary of a zero-dimensional solve: how every path ended up,...
Definition zero_dim_solve.hpp:436
unsigned long long num_singular
finite solutions flagged singular (multiple / ill-conditioned)
Definition zero_dim_solve.hpp:442
unsigned max_precision_used
highest working precision any path needed (digits)
Definition zero_dim_solve.hpp:447
unsigned long long num_finite_solutions
DISTINCT finite solutions (round of sum 1/multiplicity).
Definition zero_dim_solve.hpp:438
MidpathCheckReport midpath
the path-crossing check outcome
Definition zero_dim_solve.hpp:448
unsigned long long num_diverged
paths ending at infinity (Success & !finite, GoingToInfinity, or SecurityMaxNormReached truncation)
Definition zero_dim_solve.hpp:440
unsigned long long num_real
finite solutions flagged real
Definition zero_dim_solve.hpp:443
bool all_paths_resolved
num_failed==0 AND no unresolved crossings: the solve is trustworthy
Definition zero_dim_solve.hpp:449
std::map< SuccessCode, unsigned long long > failures_by_reason
histogram of the failed paths' SuccessCodes
Definition zero_dim_solve.hpp:445
double max_condition_number
largest condition number among finite solutions
Definition zero_dim_solve.hpp:446
unsigned long long num_failed
paths the tracker could not resolve (no solution, no clean divergence)
Definition zero_dim_solve.hpp:441
unsigned long long num_finite_endpoints
raw count of finite, successful endpoints
Definition zero_dim_solve.hpp:439
unsigned long long num_nonsolutions
finite endpoints that are NOT solutions of the target (nonsolutions of an over-determined,...
Definition zero_dim_solve.hpp:444
unsigned long long num_paths_tracked
total paths tracked (the Bezout / start count)
Definition zero_dim_solve.hpp:437
Newton and path-tracking tolerances.
Definition config.hpp:69
Top-level configuration for a zero-dimensional solve (times, precision, threading).
Definition config.hpp:212
unsigned initial_ambient_precision
Initial ambient (working) precision for the solve.
Definition config.hpp:215
zero_dim_detail::OwnedHomotopy< SystemType > OwnedT
The system-owning base.
Definition zero_dim_solve.hpp:3256
void PostEGAction() override
After the engine classifies the endpoints, discard the extraneous solutions that squaring an over-det...
Definition zero_dim_solve.hpp:3294
HomotopySolver< TrackerType, EndgameType, SystemType > EngineT
The continuation-engine base.
Definition zero_dim_solve.hpp:3257
Mat< complex_mp > const & RandomizationMatrix() const
The randomization matrix used to square up (empty if the system was already square).
Definition zero_dim_solve.hpp:3279
typename OwnedT::FactoryT FactoryT
The start-system factory type.
Definition zero_dim_solve.hpp:3258
ZeroDimSolver(SystemType const &target, FactoryT factory=bertini::start_system::MakeStartFactory< bertini::start_system::TotalDegreeBinomial, SystemType >())
Build the start system + homotopy from target, then construct the engine over them.
Definition zero_dim_solve.hpp:3266
bool WasRandomized() const
Whether the supplied system was over-determined and squared-up by randomization.
Definition zero_dim_solve.hpp:3277
SystemType const & RecordsTargetSystem() const override
The records name the PRISTINE user-supplied system as this solve's target (see HomotopySolver::Record...
Definition zero_dim_solve.hpp:3274
Definition output.hpp:51
Owns and builds the target / start system / homotopy for a zero-dim solve.
Definition zero_dim_solve.hpp:3118
OwnedHomotopy(SystemType const &target, FactoryT factory, std::string const &path_variable_name)
Build and own the target, start system, and homotopy from a target system and factory.
Definition zero_dim_solve.hpp:3123
SystemType const & BuiltTarget() const
Definition zero_dim_solve.hpp:3138
FactoryT owned_factory_
The start-system factory used to build the start system.
Definition zero_dim_solve.hpp:3154
SystemType const & UserTarget() const
Definition zero_dim_solve.hpp:3143
SystemType owned_target_
The owned (cloned, prepared) target system.
Definition zero_dim_solve.hpp:3151
bertini::start_system::StartSystem StartSystemBaseT
The start-system base type.
Definition zero_dim_solve.hpp:3119
static void PrepareTarget(SystemType &target)
Homogenize and auto-patch the (already square) target system in place.
Definition zero_dim_solve.hpp:3232
Mat< complex_mp > randomization_matrix_
The exact coefficient matrix used to square up the system.
Definition zero_dim_solve.hpp:3162
void ConsistencyCheck() const
Reject targets that cannot have isolated solutions for structural reasons.
Definition zero_dim_solve.hpp:3167
std::shared_ptr< StartSystemBaseT > owned_start_
The owned start system.
Definition zero_dim_solve.hpp:3152
void RankCheck() const
Reject a square target whose solution set is still positive-dimensional.
Definition zero_dim_solve.hpp:3212
StartSystemBaseT const & BuiltStart() const
Definition zero_dim_solve.hpp:3145
void SquareUp()
If the target is over-determined (more equations than the affine dimension), replace it with n generi...
Definition zero_dim_solve.hpp:3191
SystemType original_natural_target_
The original (natural, un-homogenized) target, kept to discard extraneous solutions.
Definition zero_dim_solve.hpp:3161
bool was_randomized_
Whether an over-determined target was squared up by randomization.
Definition zero_dim_solve.hpp:3160
SystemType user_target_
The target exactly as supplied (pristine; the records' target identity).
Definition zero_dim_solve.hpp:3150
bertini::start_system::StartSystemFactory< SystemType > FactoryT
The start-system factory type.
Definition zero_dim_solve.hpp:3120
SystemType owned_homotopy_
The owned homotopy.
Definition zero_dim_solve.hpp:3153
SystemType const & BuiltHomotopy() const
Definition zero_dim_solve.hpp:3147
This templated struct allows one to provide a variable typelist of config structs (or anything else f...
Definition configured.hpp:83
const T & Get() const
Get the held configuration object of type T.
Definition configured.hpp:94
TypeList< Ts... > UsedConfigs
The list of configuration types this object holds.
Definition configured.hpp:106
std::string Hex() const
The digest as 64 lowercase hex characters (the cross-session interchange form).
A basic typelist, in the style of Modern C++ Design, but using variadic templates.
Definition typelist.hpp:42
Definition config.hpp:176
Result of executing one WHOLE path: start -> endgame boundary -> target.
Definition path_result.hpp:101
unsigned max_precision_used
The highest precision used over the whole path.
Definition path_result.hpp:130
unsigned num_successful_steps
Successful predictor-corrector steps over the whole path.
Definition path_result.hpp:136
bool precision_changed
Whether precision changed during the path.
Definition path_result.hpp:128
unsigned cycle_num
The estimated cycle number at the endpoint.
Definition path_result.hpp:123
SuccessCode pre_endgame_success_code
Outcome of pre-endgame tracking to the boundary.
Definition path_result.hpp:108
double accuracy_estimate_user_coords
Estimated accuracy in the user's coordinates.
Definition path_result.hpp:122
bool crossing_unresolved
Whether a crossing involving this path was detected at the endgame boundary and never resolved,...
Definition path_result.hpp:144
unsigned num_failed_steps
Failed predictor-corrector steps over the whole path.
Definition path_result.hpp:137
ComplexT final_time_used
The time value the endgame finished at.
Definition path_result.hpp:120
Vec< ComplexT > solution
The solution point (final endpoint of the path).
Definition path_result.hpp:115
SolnIndT path_index
The index of the path this result is for.
Definition path_result.hpp:105
double function_residual
Residual of the system at the final solution.
Definition path_result.hpp:116
double wall_clock_limit_seconds
The per-path wall-clock budget in force when the path ran, seconds; 0 = none.
Definition path_result.hpp:139
double newton_residual
Final Newton residual.
Definition path_result.hpp:119
unsigned precision_digits
Digits the endgame finished in (= solution point's precision).
Definition path_result.hpp:124
SuccessCode endgame_success_code
Outcome of the endgame.
Definition path_result.hpp:114
double condition_number
Condition-number estimate at the final solution.
Definition path_result.hpp:117
unsigned boundary_precision
The precision at the endgame boundary.
Definition path_result.hpp:111
ComplexT time_of_first_prec_increase
The time of the first precision increase (if any).
Definition path_result.hpp:129
Vec< ComplexT > latest_path_point
The last point the tracker reached on a path that did not succeed; empty otherwise.
Definition path_result.hpp:138
double path_time_seconds
Wall-clock seconds to execute the whole path.
Definition path_result.hpp:133
unsigned accuracy_digits
Trustworthy digit count, from the convergence agreement.
Definition path_result.hpp:125
RealT boundary_stepsize
The step size at the endgame boundary.
Definition path_result.hpp:110
double accuracy_estimate_internal_coords
Estimated accuracy in the solver's internal coordinates (homogenized, on the patch).
Definition path_result.hpp:121
Vec< ComplexT > boundary_point
The space point at the endgame boundary.
Definition path_result.hpp:109
Vec< double > singular_values
Singular values of the target Jacobian at the final solution, largest first (the spectrum behind cond...
Definition path_result.hpp:118
Work item for the speculative-full-path model: a path index plus its start point.
Definition path_result.hpp:62
Settings governing the Newton corrector's iteration bounds.
Definition config.hpp:119
Settings governing step-size adjustment during tracking.
Definition config.hpp:96
The thresholds a tracker judges a path against: close enough, and too big.
Definition config.hpp:142
Trait lookup mapping a tracker type to its numeric types, event-emitter type, precision config,...
Definition config.hpp:387
Includes all files for tracking in Bertini2.
Contains the visitable base types.