Polyhedral Homotopy Continuation Method for solving sparse polynomial system, optimized by only tracing real zeros
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

641 lines
21 KiB

// This file is part of Bertini 2.
//
// base_tracker.hpp is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
// base_tracker.hpp is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with base_tracker.hpp. If not, see <http://www.gnu.org/licenses/>.
//
// Copyright(C) 2015 - 2021 by Bertini2 Development Team
//
// See <http://www.gnu.org/licenses/> for a copy of the license,
// as well as COPYING. Bertini2 is provided with permitted
// additional terms in the b2/licenses/ directory.
// individual authors of this file include:
// silviana amethyst, university of wisconsin eau claire
/**
\file base_tracker.hpp
\brief
\brief Contains the abstract base Tracker type, from which all other Trackers
inherit.
*/
#ifndef BERTINI_BASE_TRACKER_HPP
#define BERTINI_BASE_TRACKER_HPP
#include <algorithm>
// #include "tracking/step.hpp"
#include "detail/observable.hpp"
#include "logging.hpp"
#include "trackers/newton_corrector.hpp"
#include "trackers/ode_predictors.hpp"
// Must be at the end of the include list
#include "detail/configured.hpp"
#include "detail/is_template_parameter.hpp"
#include "trackers/events.hpp"
namespace bertini {
namespace tracking {
/**
\class Tracker
\brief Base tracker class for trackers offered in Bertini2.
\see AMPTracker
## Using a tracker
Trackers in Bertini2 are the engine for tracking a path from one space-time pair
to another. The path is implicitly described by the system being tracked.
1. Create a system.
2. Create a tracker, associating it to the system.
3. Set the config for the track.
4. Track from your start point and time, to the target time.
5. Profit.
Specific examples are given with the implemented tracker types. So far, these
types are avaiable:
* AMPTracker
Please see their detailed documentation for description of how to use them
correctly.
## Purpose
Since the Bertini trackers have common functionality, and we want to be able to
call arbitrary algorithms using and tracker type, we use inheritance. That is,
there is common functionality in all trackers, such as
* Setup
* TrackPath
* Refine
which we want all Trackers to be able to do. However, the internal behaviour of
a particular tracker varies -- which is why it is a different type. In
particular, the fixed precision trackers produce and work in a fixed precision,
whereas the AMPTracker varies precision to get around or near branch points
while tracking.
Hence, the use of trackers in Bertini2 is through pointers or references to
Trackers, enabling the use of any kind of tracking in any algorithm, and further
allowing the development of new tracker types as the theory and practice
advance.
## Creating a new tracker type
To create a new Tracker type, inherit from this, and override the following
functions:
\code
public:
SuccessCode Refine(Vec<mpfr> & new_space,
Vec<mpfr> const& start_point, mpfr
const& current_time) override
{}
private:
void TrackerLoopInitialization(mpfr const& start_time, Vec<mpfr> const&
start_point) override
{}
SuccessCode InitialRefinement() override
{}
SuccessCode PreIterationCheck() const override
{}
SuccessCode TrackerIteration() override
{}
void CopyFinalSolution(Vec<mpfr> & solution_at_endtime) const override
{}
\endcode
and optionally override the following functions
\code
void ResetCounters() override
{}
void PostTrackCleanup() override
{}
\endcode
where you probably want to call this base function, which is why it is
protected, not private.
*/
template <class D>
class Tracker
: public Observable,
public detail::Configured<typename TrackerTraits<D>::NeededConfigs> {
using NeededTypes = typename TrackerTraits<D>::NeededTypes;
using BaseComplexType = typename TrackerTraits<D>::BaseComplexType;
using BaseRealType = typename TrackerTraits<D>::BaseRealType;
using CT = BaseComplexType;
using RT = BaseRealType;
public:
using Config = detail::Configured<typename TrackerTraits<D>::NeededConfigs>;
using Stepping = SteppingConfig;
using Newton = NewtonConfig;
using PrecConf = typename TrackerTraits<D>::PrecisionConfig;
Tracker(System const& sys) : tracked_system_(std::ref(sys)) {
predictor_ = std::make_shared<predict::ExplicitRKPredictor>(
predict::DefaultPredictor(), tracked_system_);
corrector_ = std::make_shared<correct::NewtonCorrector>(tracked_system_);
SetPredictor(predict::DefaultPredictor());
}
/**
\brief Get the tracker set up for tracking.
Pass the tracker the configuration for tracking, to get it set up.
*/
void Setup(Predictor new_predictor_choice, double const& tracking_tolerance,
double const& path_truncation_threshold,
SteppingConfig const& stepping, NewtonConfig const& newton) {
SetPredictor(new_predictor_choice);
corrector_->Settings(newton);
SetTrackingTolerance(tracking_tolerance);
path_truncation_threshold_ = path_truncation_threshold;
this->template Set(stepping);
this->template Set(newton);
current_stepsize_ = BaseRealType(stepping.initial_step_size);
}
using Config::Get;
/**
\brief Set how tightly to track the path.
Smaller values tracks the path more tightly, at the expense of higher
computational time.
\param tracking_tolerance The new value. Newton iterations are performed
until the step length is less than this number, or the max number of
iterations has been reached, in which case the overall predict-correct step is
viewed as a failure, and the step is undone. This number must be positive.
*/
void SetTrackingTolerance(double const& tracking_tolerance) {
if (tracking_tolerance <= 0)
throw std::runtime_error("tracking tolerance must be strictly positive");
tracking_tolerance_ = tracking_tolerance;
digits_tracking_tolerance_ =
NumTraits<double>::TolToDigits(tracking_tolerance);
}
void SetInfiniteTruncationTolerance(double const& tol) {
if (tol <= 0)
throw std::runtime_error(
"truncation threshold must be strictly positive");
path_truncation_threshold_ = tol;
}
/**
\brief Track a start point through time, from a start time to a target time.
\param[out] solution_at_endtime The value of the solution at the end time.
\param start_time The time at which to start tracking.
\param endtime The time to track to.
\param start_point The intial space values for tracking.
\return A success code indicating whether tracking was successful. Will be
SuccessCode::Success if was successful, and something else otherwise.
The is the fundamental method for the tracker. First, you create and set up
the tracker, telling it what system you will solve, and the settings to use.
Then, you actually do the tracking.
*/
SuccessCode TrackPath(Vec<CT>& solution_at_endtime, CT const& start_time,
CT const& endtime, Vec<CT> const& start_point) const {
if (start_point.size() != GetSystem().NumVariables())
throw std::runtime_error(
"start point size must match the number of variables in the system "
"to be tracked");
SuccessCode initialization_code =
TrackerLoopInitialization(start_time, endtime, start_point);
if (initialization_code != SuccessCode::Success) {
PostTrackCleanup();
return initialization_code;
}
// as precondition to this while loop, the correct container, either dbl or
// mpfr, must have the correct data.
while (!IsSymmRelDiffSmall(current_time_, endtime_,
Eigen::NumTraits<CT>::epsilon())) {
SuccessCode pre_iteration_code = PreIterationCheck();
if (pre_iteration_code != SuccessCode::Success) {
PostTrackCleanup();
return pre_iteration_code;
}
using std::abs;
// compute the next delta_t
if (abs(endtime_ - current_time_) < abs(current_stepsize_))
delta_t_ = endtime_ - current_time_;
else
delta_t_ = current_stepsize_ * (endtime_ - current_time_) /
abs(endtime_ - current_time_);
step_success_code_ = TrackerIteration();
if (infinite_path_truncation_ &&
(CheckGoingToInfinity() == SuccessCode::GoingToInfinity)) {
OnInfiniteTruncation();
PostTrackCleanup();
return SuccessCode::GoingToInfinity;
} else if (step_success_code_ == SuccessCode::Success)
OnStepSuccess();
else
OnStepFail();
} // re: while
CopyFinalSolution(solution_at_endtime);
PostTrackCleanup();
return SuccessCode::Success;
}
/**
\brief Refine a point to tolerance implicitly internally set.
Runs Newton's method using the current settings for tracking, including the
min and max number of iterations allowed, the tracking tolerance, precision,
etc. YOU must ensure that the input point has the correct precision.
\return The SuccessCode indicating whether the refinement completed.
\param[out] new_space The result of refinement.
\param start_point The seed for Newton's method for refinement.
\param current_time The current time value for refinement.
*/
template <typename C>
SuccessCode Refine(Vec<C>& new_space, Vec<C> const& start_point,
C const& current_time) const {
static_assert(
detail::IsTemplateParameter<C, NeededTypes>::value,
"complex type for refinement must be a used type for the tracker");
return this->AsDerived().RefineImpl(new_space, start_point, current_time);
}
/**
\brief Refine a point to a given tolerance.
Runs Newton's method using the current settings for tracking, including the
min and max number of iterations allowed, precision, etc, EXCEPT for the
tracking tolerance and max number of iterations, which you feed in here. YOU
must ensure that the input point has the correct precision.
\return The SuccessCode indicating whether the refinement completed.
\param[out] new_space The result of refinement.
\param start_point The seed for Newton's method for refinement.
\param current_time The current time value for refinement.
\param tolerance The tolerance to which to refine.
\param max_iterations The maximum number of iterations to use to refine.
*/
template <typename C>
SuccessCode Refine(Vec<C>& new_space, Vec<C> const& start_point,
C const& current_time, double const& tolerance,
unsigned max_iterations) const {
static_assert(
detail::IsTemplateParameter<C, NeededTypes>::value,
"complex type for refinement must be a used type for the tracker");
return this->AsDerived().RefineImpl(new_space, start_point, current_time,
tolerance, max_iterations);
}
/**
\brief Change tracker to use a predictor
\param new_predictor_choice The new predictor to be used.
\see Predictor
*/
void SetPredictor(Predictor new_predictor_choice) {
predictor_->PredictorMethod(new_predictor_choice);
predictor_order_ = predictor_->Order();
}
/**
\brief Query the currently set predictor
*/
Predictor GetPredictor() const { return predictor_->PredictorMethod(); }
/**
\brief get a const reference to the system.
*/
void SetSystem(const System& new_sys) {
tracked_system_ = std::ref(new_sys);
predictor_->ChangeSystem(tracked_system_);
corrector_->ChangeSystem(tracked_system_);
}
/**
\brief get a const reference to the system.
*/
const System& GetSystem() const { return tracked_system_.get(); }
/**
\brief See how many steps have been taken.
\return The total number of steps taken, including successes and fails.
*/
unsigned NumTotalStepsTaken() const {
return num_failed_steps_taken_ + num_successful_steps_taken_;
}
/**
\brief Set how large the stepsize should be.
\param new_stepsize The new value.
*/
void SetStepSize(RT const& new_stepsize) const {
current_stepsize_ = new_stepsize;
}
/**
\brief Switch resetting of initial step size to that of the stepping settings.
By default, initial step size is retrieved from the stepping settings at the
start of each path track. To turn this off, and re-use the previous step size
from the previously tracked path, turn off by calling this function with
false.
*/
void ReinitializeInitialStepSize(bool should_reinitialize_stepsize) {
reinitialize_stepsize_ = should_reinitialize_stepsize;
}
virtual ~Tracker() = default;
auto TrackingTolerance() const { return tracking_tolerance_; }
auto InfiniteTruncationTolerance() const {
return path_truncation_threshold_;
}
private:
// convert the base tracker into the derived type.
const D& AsDerived() const { return static_cast<const D&>(*this); }
/**
\brief Set up initialization of the internals for tracking a path.
\param start_time The time at which to start tracking.
\param end_time The time to which to track.
\param start_point The point from which to start tracking.
*/
virtual SuccessCode TrackerLoopInitialization(
CT const& start_time, CT const& end_time,
Vec<CT> const& start_point) const = 0;
/**
\brief Check internal state for whether tracking should continue.
\return Code for whether to go on. Tracking will terminate if the returned
value is not Success.
*/
virtual SuccessCode PreIterationCheck() const = 0;
/**
\brief A single iteration of the tracker loop.
\return Whether the tracker loop was successful or not. Incrementing of
counters for the base class happens automatically.
*/
virtual SuccessCode TrackerIteration() const = 0;
/**
\brief Copy the solution from whatever internal variable it is stored in, into
the output variable.
\param solution_at_endtime The output variable into which to copy the final
solution.
*/
virtual void CopyFinalSolution(Vec<CT>& solution_at_endtime) const = 0;
// virtual
// void CopyFinalSolution(Vec<dbl> & solution_at_endtime) const = 0;
protected:
template <typename ComplexType>
SuccessCode CheckGoingToInfinity() const {
if (GetSystem()
.DehomogenizePoint(std::get<Vec<ComplexType> >(current_space_))
.norm() > path_truncation_threshold_)
return SuccessCode::GoingToInfinity;
else
return SuccessCode::Success;
}
/**
\brief Function to be called before exiting the tracker loop.
*/
virtual void PostTrackCleanup() const {}
/**
\brief Reset counters used during tracking.
Your custom tracker type should almost certainly call this function.
*/
virtual void ResetCounters() const = 0;
void ResetCountersBase() const {
// reset a bunch of counters to 0.
num_consecutive_successful_steps_ = 0;
num_successful_steps_taken_ = 0;
num_failed_steps_taken_ = 0;
num_consecutive_failed_steps_ = 0;
num_total_steps_taken_ = 0;
}
/**
\brief Increment and reset counters after a successful TrackerIteration()
Your custom override, if provided, should almost certainly call this function.
*/
void IncrementBaseCountersSuccess() const {
num_successful_steps_taken_++;
num_consecutive_successful_steps_++;
current_time_ += delta_t_;
num_consecutive_failed_steps_ = 0;
}
virtual void OnStepSuccess() const = 0;
/**
\brief Increment and reset counters after a failed TrackerIteration()
Your custom override, if provided, should almost certainly call this function.
*/
void IncrementBaseCountersFail() const {
num_consecutive_successful_steps_ = 0;
num_failed_steps_taken_++;
num_consecutive_failed_steps_++;
}
virtual void OnStepFail() const = 0;
/**
\brief Check whether the path is going to infinity, as it tracks.
This check is necessary because a homotopy may be malformed, or may have
encountered a probability-0 event. That it is a 0 probability event is why
this check is disable-able via a toggle.
*/
virtual SuccessCode CheckGoingToInfinity() const = 0;
virtual void OnInfiniteTruncation() const = 0;
std::reference_wrapper<const System>
tracked_system_; ///< Reference to the system being tracked.
bool infinite_path_truncation_ =
true; /// Whether should check if the path is going to infinity while
/// tracking. On by default.
bool reinitialize_stepsize_ =
true; ///< Whether should re-initialize the stepsize with each call to
///< Trackpath. On by default.
// tracking the numbers of things
mutable unsigned
num_total_steps_taken_; ///< The number of steps taken, including
///< failures and successes.
mutable unsigned num_successful_steps_taken_; ///< The number of successful
///< steps taken so far.
mutable unsigned
num_consecutive_successful_steps_; ///< The number of CONSECUTIVE
///< successful steps taken in a row.
mutable unsigned
num_consecutive_failed_steps_; ///< The number of CONSECUTIVE failed
///< steps taken in a row.
mutable unsigned
num_failed_steps_taken_; ///< The total number of failed steps taken.
// configuration for tracking
std::shared_ptr<predict::ExplicitRKPredictor>
predictor_; // The predictor to use while tracking
unsigned predictor_order_; ///< The order of the predictor -- one less than
///< the error estimate order.
std::shared_ptr<correct::NewtonCorrector> corrector_;
unsigned digits_final_ =
0; ///< The number of digits to track to, due to being in endgame zone.
unsigned digits_tracking_tolerance_ =
5; ///< The number of digits required for tracking to given tolerance,
///< condition number notwithstanding.
NumErrorT tracking_tolerance_ = 1e-5; ///< The tracking tolerance.
NumErrorT path_truncation_threshold_ =
1e5; ///< The threshold for path truncation.
mutable CT endtime_; ///< The time we are tracking to.
mutable CT current_time_; ///< The current time.
mutable CT delta_t_; ///< The current delta_t.
mutable RT current_stepsize_; ///< The current stepsize.
// permanent temporaries
mutable RT next_stepsize_; /// The next stepsize
mutable SuccessCode step_success_code_; ///< The code for step success.
mutable unsigned
num_steps_since_last_condition_number_computation_; ///< How many steps
///< have passed
///< since the most
///< recent condition
///< number estimate.
mutable unsigned
num_successful_steps_since_stepsize_increase_; ///< How many successful
///< steps have been taken
///< since increased
///< stepsize.
mutable unsigned
num_successful_steps_since_precision_decrease_; ///< The number of
///< successful steps
///< since decreased
///< precision.
using TupOfVec = typename NeededTypes::ToTupleOfVec;
using TupOfReal = typename NeededTypes::ToTupleOfReal;
mutable TupOfVec current_space_; ///< The current space value.
mutable TupOfVec
tentative_space_; ///< After correction, the tentative next space value
mutable TupOfVec
temporary_space_; ///< After prediction, the tentative next space value.
mutable NumErrorT
condition_number_estimate_; ///< An estimate on the condition number of
///< the Jacobian
mutable NumErrorT error_estimate_; ///< An estimate on the error of a step.
mutable NumErrorT norm_J_; ///< An estimate on the norm of the Jacobian
mutable NumErrorT norm_J_inverse_; ///< An estimate on the norm of the
///< inverse of the Jacobian
mutable NumErrorT norm_delta_z_; ///< The norm of the change in space
///< resulting from a step.
mutable NumErrorT
size_proportion_; ///< The proportion of the space step size, taking into
///< account the order of the predictor.
public:
NumErrorT LatestConditionNumber() const {
return this->condition_number_estimate_;
}
NumErrorT LatestErrorEstimate() const { return this->error_estimate_; }
NumErrorT LatestNormOfStep() const { return this->norm_delta_z_; }
void SetInfiniteTruncation(bool b) { infinite_path_truncation_ = b; }
auto InfiniteTruncation() { return infinite_path_truncation_; }
unsigned NumVariables() const { return GetSystem().NumVariables(); }
auto CurrentTime() const { return current_time_; }
auto DeltaT() const { return delta_t_; }
auto CurrentStepsize() const { return current_stepsize_; }
virtual Vec<CT> CurrentPoint() const = 0;
virtual unsigned CurrentPrecision() const = 0;
};
} // namespace tracking
} // namespace bertini
#endif