//This file is part of Bertini 2.
//
//cauchy_endgame.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.
//
//cauchy_endgame.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 cauchy_endgame.hpp. If not, see .
//
// Copyright(C) 2015 - 2021 by Bertini2 Development Team
//
// See 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
// Tim Hodges, Colorado State University
#pragma once
#include "bertini2/endgames/base_endgame.hpp"
namespace bertini{ namespace endgame{
/**
\class CauchyEndgame
\brief Class used to finish tracking paths during Homotopy Continuation.
## Explanation
The bertini::CauchyEngame class enables us to finish tracking on possibly singular paths on an arbitrary square homotopy.
The intended usage is to:
1. Create a system, tracker, and instantiate some settings.
2. Using the tracker created track to the engame boundary.
3. Create a CauchyEndgame, associating it to the system you are going to solve or track on.
4. For each path being tracked send the CauchyEndgame the time value and other variable values at that time.
5. The CauchyEndgame, if successful, will store the target systems solution at $t = 0$.
## Example Usage
Below we demonstrate a basic usage of the CauchyEndgame class to find the singularity at $t = 0$.
The pattern is as described above: create an instance of the class, feeding it the system to be used, and the endgame boundary time and other variable values at the endgame boundary.
\code{.cpp}
using namespace bertini::tracking;
using RealT = tracking::TrackerTraits::BaseRealType; // Real types
using ComplexT = tracking::TrackerTraits::BaseComplexType; Complex types
// 1. Define the polynomial system that we wish to solve.
System target_sys;
Var x = Variable::Make("x"), t = Variable::Make("t"), y = Variable::Make("y");
VariableGroup vars{x,y};
target_sys.AddVariableGroup(vars);
target_sys.AddFunction((pow(x-1,3));
target_sys.AddFunction((pow(y-1,2));
// 1b. Homogenize and patch the polynomial system to work over projective space.
sys.Homogenize();
sys.AutoPatch();
// 2. Create a start system, for us we will use a total degree start system.
auto TD_start_sys = bertini::start_system::TotalDegree(target_sys);
// 2b. Creating homotopy between the start system and system we wish to solve.
auto my_homotopy = (1-t)*target_sys + t*TD_start_sys*Rational::Rand(); //the random number is our gamma for a random path between t = 1 and t = 0.
my_homotopy.AddPathVariable(t);
//Sets up configuration settings for our particular system.
auto precision_config = PrecisionConfig(my_homotopy);
// 3. Creating a tracker. For us this is an AMPTracker.
AMPTracker tracker(my_homotopy);
//Tracker setup of settings.
SteppingConfig stepping_preferences;
stepping_preferences.initial_step_size = RealT(1)/RealT(5);// change a stepping preference
NewtonConfig newton_preferences;
tracker.Setup(TestedPredictor,
RealFromString("1e-6"),
RealFromString("1e5"),
stepping_preferences,
newton_preferences);
tracker.PrecisionSetup(precision_config);
//We start at t = 1, and will stop at t = 0.1 before starting the endgames.
ComplexT t_start(1), t_endgame_boundary(0.1);
//This will hold our solutions at t = 0.1
std::vector > my_homotopy_solutions_at_endgame_boundary;
// result holds the value we track to at 0.1, and tracking success will report if we are unsucessful.
Vec result;
//4. Track all points to 0.1
for (unsigned ii = 0; ii < TD_start_sys.NumStartPoints(); ++ii)
{
DefaultPrecision(ambient_precision);
my_homotopy.precision(ambient_precision); // making sure our precision is all set up
auto start_point = TD_start_sys.StartPoint(ii);
tracker.TrackPath(result,t_start,t_endgame_boundary,start_point);
my_homotopy_solutions_at_endgame_boundary.push_back(result);
}
//Settings for the endgames.
config::Tolerances tolerances;
CauchyConfig cauchy_settings;
cauchy_settings.fail_safe_maximum_cycle_number = 6;
// 5. Create a cauchy endgame, and use them to get the soutions at t = 0.
EndgameSelector::Cauchy my_cauchy_endgame(tracker,cauchy_settings,tolerances);
std::vector > my_homotopy_solutions;
std::vector > my_homotopy_divergent_paths;
for(auto s : my_homotopy_solutions_at_endgame_boundary)
{
SuccessCode endgame_success = my_cauchy_endgame.Run(t_endgame_boundary,s);
if(endgame_success == SuccessCode::Success)
{
my_homotopy_solutions.push_back(my_homotopy.DehomogenizePoint(my_endgame.FinalApproximation()));
}
else
{
my_homotopy_divergent_paths.push_back(my_homotopy.DehomogenizePoint(my_endgame.FinalApproximation()));
}
}
\endcode
If this documentation is insufficient, please contact the authors with suggestions, or get involved! Pull requests welcomed.
## Testing
Test suite driving this class: endgames_test.
File: test/endgames/generic_cauchy_test.hpp
File: test/endgames/amp_cauchy_test.cpp
File: test/endgames/fixed_double_cauchy_test.cpp
FIle: test/endgames/fixed_multiple_cauchy_test.cpp
*/
template
class CauchyEndgame :
public virtual EndgameBase, PrecT>
{
public:
using BaseEGT = EndgameBase, PrecT>;
using FinalEGT = CauchyEndgame;
using TrackerType = typename PrecT::TrackerType;
using BaseComplexType = typename tracking::TrackerTraits::BaseComplexType;
using BaseRealType = typename tracking::TrackerTraits::BaseRealType;
using EmitterType = CauchyEndgame;
protected:
using EndgameBase, PrecT>::NotifyObservers;
using TupleOfTimes = typename BaseEGT::TupleOfTimes;
using TupleOfSamps = typename BaseEGT::TupleOfSamps;
using BCT = BaseComplexType;
using BRT = BaseRealType;
using Configs = typename AlgoTraits::NeededConfigs;
using ConfigsAsTuple = typename Configs::ToTuple;
/**
\brief A deque of times that are specifically used to compute the power series approximation for the Cauchy endgame.
*/
mutable TupleOfTimes pseg_times_;
/**
\brief A deque of samples that are in correspondence with the pseg_times_. These samples are also used to compute the first power series approximation for the Cauchy endgame.
*/
mutable TupleOfSamps pseg_samples_; //samples used for the first approximation.
/**
\brief A deque of times that are collected by CircleTrack. These samples are used to compute all approximations of the origin after the first.
*/
mutable TupleOfTimes cauchy_times_;
/**
\brief A deque of samples collected by CircleTrack. Computed a mean of the values of this deque, after a loop has been closed, will give an approximation of the origin.
*/
mutable TupleOfSamps cauchy_samples_;
public:
/**
\brief Function that clears all samples and times from data members for the Cauchy endgame
*/
template
void ClearTimesAndSamples()
{
std::get >(pseg_times_).clear();
std::get >(cauchy_times_).clear();
std::get >(pseg_samples_).clear();
std::get >(cauchy_samples_).clear();}
/**
\brief Setter for the time values for the power series approximation of the Cauchy endgame.
*/
template
void SetPSEGTimes(TimeCont pseg_times_to_set)
{ std::get >(pseg_times_) = pseg_times_to_set;}
/**
\brief Getter for the time values for the power series approximation of the Cauchy endgame.
*/
template
TimeCont& GetPSEGTimes() {return std::get >(pseg_times_);}
template
const TimeCont& GetPSEGTimes() const {return std::get >(pseg_times_);}
/**
\brief Setter for the space values for the power series approximation of the Cauchy endgame.
*/
template
void SetPSEGSamples(SampCont const& pseg_samples_to_set) { std::get >(pseg_samples_) = pseg_samples_to_set;}
/**
\brief Getter for the space values for the power series approximation of the Cauchy endgame.
Available in const and non-const flavors
*/
template
SampCont& GetPSEGSamples() {return std::get >(pseg_samples_);}
template
const SampCont& GetPSEGSamples() const {return std::get >(pseg_samples_);}
/**
\brief Setter for the space values for the Cauchy endgame.
*/
template
void SetCauchySamples(SampCont const& cauchy_samples_to_set)
{
std::get >(cauchy_samples_) = cauchy_samples_to_set;
}
/**
\brief Getter for the space values for the Cauchy endgame.
Available in const and non-const flavors
*/
template
SampCont& GetCauchySamples()
{
return std::get >(cauchy_samples_);
}
template
const SampCont& GetCauchySamples() const { return std::get >(cauchy_samples_); }
/**
\brief Setter for the time values for the Cauchy endgame.
*/
template
void SetCauchyTimes(TimeCont const& cauchy_times_to_set)
{
std::get >(cauchy_times_) = cauchy_times_to_set;
}
/**
\brief Getter for the time values for the Cauchy endgame.
*/
template
TimeCont& GetCauchyTimes()
{
return std::get >(cauchy_times_);
}
template
const TimeCont& GetCauchyTimes() const
{
return std::get >(cauchy_times_);
}
const BCT& LatestTimeImpl() const
{
return GetPSEGTimes().back();
}
/**
\brief Setter for the specific settings in tracking_conifg.hpp under Cauchy.
*/
void SetCauchySettings(CauchyConfig const& new_cauchy_settings)
{
this->template Set(new_cauchy_settings);
}
/**
\brief Getter for the specific settings in tracking_conifg.hpp under Cauchy.
*/
const auto& GetCauchySettings() const
{
return this->template Get();
}
explicit CauchyEndgame(TrackerType const& tr,
const ConfigsAsTuple& settings )
: BaseEGT(tr, settings), EndgamePrecPolicyBase(tr)
{ }
template< typename... Ts >
CauchyEndgame(TrackerType const& tr, const Ts&... ts ) : CauchyEndgame(tr, Configs::Unpermute( ts... ) )
{}
virtual ~CauchyEndgame() = default;
void ValidateConfigs()
{
if (this->EndgameSettings().num_sample_points < 3) // need to make sure we won't track right through the origin.
{
std::stringstream err_msg;
err_msg << "ERROR: The number of sample points " << this->EndgameSettings().num_sample_points << " for circle tracking must be >= 3";
throw std::runtime_error(err_msg.str());
}
}
/**
\brief Function to track around the origin
## Input:
starting_time: time value that we start from to track around the origin
target_time: the time value that we are centering out loops around, default is t = 0
starting_sample: an approximate solution of the homotopy at t = starting_time
## Output:
SuccessCode: This reports back if we were successful in advancing time.
##Details:
\tparam CT The complex number type.
Depeding on the number of samples points, we make a polgon around the origin with that many vertices. This function should be called the same number of times
as paths converging to the solution we are approximating.
*/
template
SuccessCode CircleTrack(CT const& target_time)
{
using bertini::Precision;
using RT = typename Eigen::NumTraits::Real;
using std::acos;
ValidateConfigs();
auto& circle_times = std::get >(cauchy_times_);
auto& circle_samples = std::get >(cauchy_samples_);
CT starting_time = circle_times.back(); // take a COPY here, so won't invalidate it later
// the initial sample has already been added to the sample repo... so don't do that here, please
const auto num_vars = this->GetSystem().NumVariables();
for (unsigned ii = 0; ii < this->EndgameSettings().num_sample_points; ++ii)
{
const Vec& current_sample = circle_samples.back();
const CT& current_time = circle_times.back();
#ifndef BERTINI_DISABLE_PRECISION_CHECKS
if (Precision(current_time)!=Precision(current_sample)){
std::stringstream err_msg;
err_msg << "current time and sample for circle track must be of same precision. respective precisions: " << Precision(current_time) << " " << Precision(current_sample) << std::endl;
throw std::runtime_error(err_msg.str());
}
#endif
//set up the time value for the next sample.
using std::polar;
#ifndef USE_BMP_COMPLEX
using bertini::polar;
#endif
//Generalized since we could have a nonzero target time.
using std::arg;
RT radius = abs(starting_time - target_time), angle = arg(starting_time - target_time); // generalized for nonzero target_time.
auto next_sample = Vec(num_vars);
CT next_time = (ii==this->EndgameSettings().num_sample_points-1)
?
starting_time
:
polar(radius, (ii+1)*2*acos(static_cast