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.

767 lines
24 KiB

// This file is part of Bertini 2.
//
// straight_line_program.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.
//
// straight_line_program.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 straight_line_program.hpp. If not, see
// <http://www.gnu.org/licenses/>.
//
// Copyright(C) 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
// michael mumm, university of wisconsin eau claire
/**
\file system/straight_line_program.hpp
\brief Provides the bertini::StraightLineProgram class.
*/
#ifndef BERTINI_SLP_HPP
#define BERTINI_SLP_HPP
#pragma once
#include <assert.h>
#include <boost/serialization/utility.hpp>
#include <map>
#include <vector>
#include "detail/visitor.hpp"
#include "eigen_extensions.hpp"
#include "function_tree/forward_declares.hpp"
#include "mpfr_complex.hpp"
#include "mpfr_extensions.hpp"
// code copied from Bertini1's file include/bertini.h
/*
typedef struct
{
int num_funcs;
int num_hom_var_gp;
int num_var_gp;
int *type; // 0 - hom_var_gp, 1 - var_gp
int *size; // size of the group of the user listed variables (total size
= size + type) } preproc_data;
typedef struct
{
point_d funcVals;
point_d parVals;
vec_d parDer;
mat_d Jv;
mat_d Jp;
} eval_struct_d;
typedef struct
{
point_mp funcVals;
point_mp parVals;
vec_mp parDer;
mat_mp Jv;
mat_mp Jp;
} eval_struct_mp;
The straight-line program structure. This is the way that polynomials are
stored internally. typedef struct { int *prog; // The program instructions.
(a big integer array) int size; // size of the instruction program. int
memSize; // Amount of memory it needs in workspace (for temp and final
results). num_t *nums; // The array of real numbers. int precision; // The
precision at which evaluation should occur
// INFO NEEDED FOR M-HOM:
int num_var_gps; // The total number of variable groups (i.e., m from
m-hom). int *var_gp_sizes; // The size of each of the groups. int
index_of_first_number_for_proj_trans; // The address of the first number used
in the projective transformation polynomials.
// STOP LOCATIONS:
int numInstAtEndUpdate; // instruction number at end of update. i.e. i
= 0; while (i < numInstAtEndUpdate) .. int numInstAtEndParams; // instruction
number at end of params. i.e. i = numInstAtEndUpdate; while (i <
numInstAtEndParams) .. int numInstAtEndFnEval; // instruction number at end of
function eval. i.e. i = numInstAtEndParams; while (i < numInstAtEndFnEval) ..
int numInstAtEndPDeriv; // instruction number at end of param diff.
i.e. i = numInstAtEndFnEval; while (i < numInstAtEndPDeriv) .. int
numInstAtEndJvEval; // instruction number at end of Jv eval. i.e. i =
numInstAtEndPDeriv; while (i < numInstAtEndJvEval) ..
// for Jp eval: i = numInstAtEndJvEval; while (i < size) ..
// INPUT AMOUNTS:
int numVars; // Number of variables in the function being computed.
int numPathVars; // Number of path variables. Ought to be 1 usually.
int numNums; // Number of real numbers used in evaluation.
int numConsts; // Number of constants.
// OUTPUT AMOUNTS:
int numPars; // Number of parameters
int numFuncs; // Number of coordinate functions in the homotopy.
int numSubfuncs; // Number of subfunctions.
// INPUT LOCATIONS:
int inpVars; // Where the input variable values are stored.
int inpPathVars; // Where the values of the path variables are
stored. int IAddr; // Where the constant I is stored. int numAddr; // Where
the first num_t is stored. int constAddr; // Where the first constant is
stored.
// OUTPUT LOCATIONS:
int evalPars; // Where U(t), for given t, is stored.
int evalDPars; // Where the derivatives of the parameters are stored.
int evalFuncs; // Where H(x,t) is stored.
int evalJVars; // Where the Jacobian w.r.t. vars is stored.
int evalJPars; // Where the Jacobian w.r.t. pars is stored.
int evalSubs; // Where the subfunctions are stored
int evalJSubsV; // Where the derivatives of the subfunctions w.r.t.
vars are stored. int evalJSubsP; // Where the derivatives of the subfunctions
w.r.t. pars are stored. } prog_t;
*/
namespace bertini {
class SLPCompiler;
class System; // a forward declaration, solving the circular inclusion problem
enum Operation { // we'll start with the binary ones
Add = 1 << 0,
Subtract = 1 << 1,
Multiply = 1 << 2,
Divide = 1 << 3,
Power = 1 << 4,
Exp = 1 << 5,
Log = 1 << 6,
Negate = 1 << 7,
Sqrt = 1 << 8,
Sin = 1 << 9,
Cos = 1 << 10,
Tan = 1 << 11,
Asin = 1 << 12,
Acos = 1 << 13,
Atan = 1 << 14,
Assign = 1 << 15,
IntPower = 1 << 16,
};
const int BinaryOperations =
Add | Subtract | Multiply | Divide | Power | IntPower;
const int TrigOperations = Sin | Cos | Tan | Asin | Acos | Atan;
const int UnaryOperations = Exp | Log | Negate | Assign | TrigOperations | Sqrt;
constexpr bool IsUnary(Operation op) { return op & UnaryOperations; }
constexpr bool IsBinary(Operation op) { return op & BinaryOperations; }
std::string OpcodeToString(Operation op);
/**
\class StraightLineProgram
An implementation of straight-line programs, implemented with strong
inspiration from Bertini1's implementation.
One constructs a SLP from a system, like
```
System my_system();
StraightLineProgram slp(my_system);
```
Maybe you don't need to know this, but in construction the SLP uses a helper
class, the SLPCompiler
Patches are just functions in this framework. The variables appear at the
front of the memory, then functions, then derivatives. This should make
copying data out easy, because it's all in one place.
In contrast to Bertini1 SLP's, we don't put all the numbers at the front --
they just get scattered through the SLP's memory.
*/
class StraightLineProgram {
friend SLPCompiler;
private:
using Nd = std::shared_ptr<const node::Node>;
public:
/**
\struct OutputLocations
A struct encapsulating the starting locations of things in the SLP
*/
struct OutputLocations {
size_t Functions{0};
size_t Jacobian{0};
size_t TimeDeriv{0};
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive& ar, const unsigned version) {
ar & Functions;
ar & Jacobian;
ar & TimeDeriv;
}
};
/**
\struct InputLocations
A struct encapsulating the starting locations of things in the SLP
*/
struct InputLocations {
size_t Variables{0};
size_t Time{0};
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive& ar, const unsigned version) {
ar & Variables;
ar & Time;
}
};
/**
\struct NumberOf
A struct encapsulating the numbers of things appearing in the SLP
*/
struct NumberOf {
size_t Functions{0};
size_t Variables{0};
size_t Jacobian{0};
size_t TimeDeriv{0};
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive& ar, const unsigned version) {
ar & Functions;
ar & Variables;
ar & Jacobian;
ar & TimeDeriv;
}
};
/**
The constructor -- how to make a SLP from a System.
*/
StraightLineProgram(System const& sys);
StraightLineProgram() = default;
template <typename Derived>
void Eval(Eigen::MatrixBase<Derived> const& variable_values) const {
using NumT = typename Derived::Scalar;
SetVariableValues(variable_values);
Eval<NumT>();
}
/**
\brief copies the variable values into the Matrix base and the path variables
into the complex type time
\tparam Derived derived type
\tparam ComplexT complex type
\param variable_values dervied matrixBase of variable values
\param time complex type for time
*/
template <typename Derived, typename ComplexT>
void Eval(Eigen::MatrixBase<Derived> const& variable_values,
ComplexT const& time) const {
using NumT = typename Derived::Scalar;
static_assert(std::is_same<NumT, ComplexT>::value,
"scalar types must be the same");
// 1. copy variable values into memory locations they're supposed to go in
SetVariableValues(variable_values);
SetPathVariable(time);
Eval<NumT>();
}
/**
\brief loops through the instructions in memory and evaluates each operation
\tparam NumT numeric type
uses a switch to find different operations from memory to make sure its
performing the correct evaluations
todo: implement a compile-time version of this using Boost.Hana
*/
template <typename NumT>
void Eval() const; // this definition is in cpp, along with the lines that
// instantiate the needed versions.
// a placeholder function that needs to be written. now just calls eval,
// since the eval functionality is both functions and jacobian wrapped
// together -- we don't keep arrays of their locations separately yet, so that
// would be the starting point.
template <typename T>
void EvalFunctions() const {
this->Eval<T>();
}
// a placeholder function that needs to be written. now just calls eval,
// since the eval functionality is both functions and jacobian wrapped
// together -- we don't keep arrays of their locations separately yet, so that
// would be the starting point.
template <typename T>
void EvalJacobian() const {
this->Eval<T>();
}
// a placeholder function that needs to be written. now just calls eval,
// since the eval functionality is both functions and jacobian wrapped
// together -- we don't keep arrays of their locations separately yet, so that
// would be the starting point.
template <typename T>
void EvalTimeDeriv() const {
this->Eval<T>();
}
/**
\brief assignts the computed values of functions into the given vector
\tparam NumT numeric type
\param result The vector you're going to store the values into
the function will NOT automatically resize your vector for you to be the
correct size
*/
template <typename NumT>
void GetFuncValsInPlace(Vec<NumT>& result) const {
if (!is_evaluated_) this->EvalFunctions<NumT>();
auto& memory = std::get<std::vector<NumT>>(memory_);
// copy content
for (int ii = 0; ii < number_of_.Functions; ++ii) {
result(ii) = memory[ii + output_locations_.Functions];
}
}
/**
\brief retrieves the computed values of jacobians
\tparam NumT numeric type
\param result The vector you're going to store the values into
the function will NOT automatically resize your vector for you to be the
correct size
*/
template <typename NumT>
void GetJacobianInPlace(Mat<NumT>& result) const {
if (!is_evaluated_) this->EvalJacobian<NumT>();
auto& memory = std::get<std::vector<NumT>>(memory_);
// copy content
for (int jj = 0; jj < number_of_.Variables; ++jj) {
for (int ii = 0; ii < number_of_.Functions; ++ii) {
result(ii, jj) =
memory[ii + jj * number_of_.Functions + output_locations_.Jacobian];
}
}
}
/**
\brief copies the values of the time derivatives into your given vector
\tparam NumT numeric type
\param result The vector you're going to store the values into
the function will automatically resize your vector for you to be the correct
size
*/
template <typename NumT>
void GetTimeDerivInPlace(Vec<NumT>& result) const {
if (!is_evaluated_) this->EvalTimeDeriv<NumT>();
auto& memory = std::get<std::vector<NumT>>(memory_);
// 1. make container, size correctly.
// 2. copy content
for (int ii = 0; ii < number_of_.Functions; ++ii) {
result(ii) = memory[ii + output_locations_.TimeDeriv];
}
}
/**
\brief creates the Vec<NumT> to be used in the overloaded function
\tparam NumT numeric type
*/
template <typename NumT>
Vec<NumT> GetFuncVals() const {
Vec<NumT> return_me(this->NumFunctions());
GetFuncValsInPlace(return_me);
return return_me;
}
/**
\brief creates the Vec<NumT> to be used in the overloaded function
\tparam NumT numeric type
*/
template <typename NumT>
Mat<NumT> GetJacobian() const {
Mat<NumT> return_me(this->NumFunctions(), this->NumVariables());
GetJacobianInPlace(return_me);
return return_me;
}
/**
\brief creates the Vec<NumT> to be used in the overloaded function
\tparam NumT numeric type
*/
template <typename NumT>
Vec<NumT> GetTimeDeriv() const {
Vec<NumT> return_me(this->NumFunctions());
GetTimeDerivInPlace(return_me);
return return_me;
}
inline unsigned NumFunctions() const { return number_of_.Functions; }
inline unsigned NumVariables() const { return number_of_.Variables; }
/**
\brief Get the current precision of the SLP.
\return The number of digits
*/
inline unsigned precision() const { return precision_; }
/**
\brief change the precision of the SLP.
Downsamples from the true values.
\param new_precision The new number of digits
*/
void precision(unsigned new_precision) const;
/**
\brief Does this SLP have a path variable?
\return Well, does it?
*/
bool HavePathVariable() const { return this->has_path_variable_; }
/**
\brief Overloaded operator for printing to an arbirtary out stream.
*/
friend std::ostream& operator<<(std::ostream& out,
const StraightLineProgram& s);
/**
\brief Copy the values of the variables from the passed in vector to memory
\param variable_values The vector of current variable values.
*/
template <typename Derived>
void SetVariableValues(
Eigen::MatrixBase<Derived> const& variable_values) const {
using NumT = typename Derived::Scalar;
#ifndef BERTINI_DISABLE_PRECISION_CHECKS
if (!std::is_same<NumT, dbl_complex>::value &&
Precision(variable_values) != this->precision_) {
std::stringstream err_msg;
err_msg << "variable_values and SLP must be of same precision. "
"respective precisions: "
<< Precision(variable_values) << " " << this->precision_
<< std::endl;
throw std::runtime_error(err_msg.str());
}
#endif
using NumT = typename Derived::Scalar;
auto& memory =
std::get<std::vector<NumT>>(memory_); // unpack for local reference
for (int ii = 0; ii < number_of_.Variables; ++ii) {
// assign to memory
memory[ii + input_locations_.Variables] = variable_values(ii);
}
is_evaluated_ = false;
}
/**
\brief Copy the current time value to memory
\param time The current time
\tparam ComplexT the complex numeric type.
If the SLP doesn't have a path variable, then this will throw.
*/
template <typename ComplexT>
void SetPathVariable(ComplexT const& time) const {
#ifndef BERTINI_DISABLE_PRECISION_CHECKS
if (Precision(time) != DoublePrecision() &&
Precision(time) != this->precision_) {
std::stringstream err_msg;
err_msg << "time value and SLP must be of same precision. respective "
"precisions: "
<< Precision(time) << " " << this->precision_ << std::endl;
throw std::runtime_error(err_msg.str());
}
#endif
if (!this->HavePathVariable())
throw std::runtime_error(
"calling Eval with path variable, but this StraightLineProgram "
"doesn't have one.");
// then actually copy the path variable into where it goes in memory
auto& memory =
std::get<std::vector<ComplexT>>(memory_); // unpack for local reference
memory[input_locations_.Time] = time;
is_evaluated_ = false;
}
using IntT = int; // this needs to co-vary on the stored type inside the
// node. node should stop using mpz, it's slow.
private:
/**
\brief Add an instruction to memory. This one's for binary operations
\param binary_op The opcode, from the enum.
\param in_loc1 The location of the first operand
\param in_loc2 The locatiion in memory of the second operand
\param out_loc Where in memory to put the result of the operation.
*/
void AddInstruction(Operation binary_op, size_t in_loc1, size_t in_loc2,
size_t out_loc);
/**
\brief Add an instruction to memory. This one's for unary operations
\param unary_op The opcode, from the enum.
\param in_loc The location of the one and only operand
\param out_loc Where in memory to put the result of the operation.
*/
void AddInstruction(Operation unary_op, size_t in_loc, size_t out_loc);
/**
\brief Add a number to the memory at location, and memoize it for precision
changing later.
*/
void AddNumber(Nd const num, size_t loc);
template <typename NumT>
auto& GetMemory() const {
return std::get<std::vector<NumT>>(this->memory_);
}
template <typename NumT>
void CopyNumbersIntoMemory() const;
mutable unsigned precision_ = 16; //< The current working number of digits
bool has_path_variable_ = false; //< Does this SLP have a path variable?
NumberOf number_of_; //< Quantities of things
OutputLocations output_locations_; //< Where to find outputs, like functions
//and derivatives
InputLocations
input_locations_; //< Where to find inputs, like variables and time
mutable std::tuple<std::vector<dbl_complex>, std::vector<mpfr_complex>>
memory_; //< The memory of the object. Numbers and variables, plus temp
//results and output locations. It's all one block. That's why
//it's called a SLP!
std::vector<IntT> integers_;
std::vector<size_t> instructions_; //< The instructions. The opcodes are
//stored as size_t's, as well as the
//locations of operands and results.
std::vector<std::pair<Nd, size_t>>
true_values_of_numbers_; //< the size_t is where in memory to downsample
//to.
mutable bool is_evaluated_ = false;
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive& ar, const unsigned version) {
ar & precision_;
ar & has_path_variable_;
ar & number_of_;
ar & output_locations_;
ar & input_locations_;
ar& std::get<std::vector<dbl_complex>>(memory_);
ar& std::get<std::vector<mpfr_complex>>(memory_);
ar & integers_;
ar & instructions_;
ar & true_values_of_numbers_;
ar & is_evaluated_;
}
};
class SLPCompiler
: public VisitorBase,
// IF YOU ADD A THING HERE, YOU MUST ADD IT ABOVE AND IN THE CPP SOURCE
// symbols and roots
public Visitor<node::Variable>,
public Visitor<node::Integer>,
public Visitor<node::Float>,
public Visitor<node::Rational>,
public Visitor<node::Function>,
public Visitor<node::Jacobian>,
public Visitor<node::Differential>,
// arithmetic
public Visitor<node::SumOperator>,
public Visitor<node::MultOperator>,
public Visitor<node::IntegerPowerOperator>,
public Visitor<node::PowerOperator>,
public Visitor<node::ExpOperator>,
public Visitor<node::LogOperator>,
public Visitor<node::NegateOperator>,
public Visitor<node::SqrtOperator>,
// the trig operators
public Visitor<node::SinOperator>,
public Visitor<node::ArcSinOperator>,
public Visitor<node::CosOperator>,
public Visitor<node::ArcCosOperator>,
public Visitor<node::TanOperator>,
public Visitor<node::ArcTanOperator>,
public Visitor<node::special_number::Pi>,
public Visitor<node::special_number::E>
// also missing -- linears and difflinears.
// these abstract base types left out,
// but commented here to explain why
// public Visitor<node::Operator>,// abstract
// public Visitor<node::UnaryOperator>,// abstract
// public Visitor<node::NaryOperator>,// abstract
// public Visitor<node::TrigOperator>,// abstract
{
private:
using Nd = std::shared_ptr<const node::Node>;
using SLP = StraightLineProgram;
public:
SLP Compile(System const& sys);
// IF YOU ADD A THING HERE, YOU MUST ADD IT ABOVE AND IN THE CPP SOURCE
// symbols and roots
virtual void Visit(node::Variable const& n);
virtual void Visit(node::Integer const& n);
virtual void Visit(node::Float const& n);
virtual void Visit(node::Rational const& n);
virtual void Visit(node::Function const& n);
virtual void Visit(node::Jacobian const& n);
virtual void Visit(node::Differential const& n);
// arithmetic
virtual void Visit(node::SumOperator const& n);
virtual void Visit(node::MultOperator const& n);
virtual void Visit(node::IntegerPowerOperator const& n);
virtual void Visit(node::PowerOperator const& n);
virtual void Visit(node::ExpOperator const& n);
virtual void Visit(node::LogOperator const& n);
virtual void Visit(node::NegateOperator const& n);
virtual void Visit(node::SqrtOperator const& n);
// the trig operators
virtual void Visit(node::SinOperator const& n);
virtual void Visit(node::ArcSinOperator const& n);
virtual void Visit(node::CosOperator const& n);
virtual void Visit(node::ArcCosOperator const& n);
virtual void Visit(node::TanOperator const& n);
virtual void Visit(node::ArcTanOperator const& n);
virtual void Visit(node::special_number::Pi const& n);
virtual void Visit(node::special_number::E const& n);
// missing -- linear and difflinear
private:
/**
\brief Provides a uniform interface for dealing with all numeric node types.
*/
template <typename NodeT>
void DealWithNumber(NodeT const& n) {
auto nd =
n.shared_from_this(); // make a shared pointer to the node, so that it
// survives, and we get polymorphism
this->slp_under_construction_.AddNumber(
nd, next_available_complex_); // register the number with the SLP
this->locations_encountered_nodes_[nd] =
next_available_complex_++; // add to found symbols in the compiler,
// increment counter.
}
/**
\brief Reset the compiler to compile another SLP from another system.
*/
void Clear();
size_t next_available_complex_ =
0; //< Where should the next complex number go in memory?
size_t next_available_int_ = 0; //< Where should the next integer go?
using IntT = int; // this needs to co-vary on the stored type inside the
// node. node should stop using mpz, it's slow.
std::map<Nd, size_t>
locations_encountered_nodes_; //< A registry of pointers-to-nodes and
//location in memory on where to find
//*their results*
std::map<IntT, size_t> locations_integers_;
std::map<Nd, size_t> locations_top_level_functions_and_derivatives_;
SLP slp_under_construction_; //< the under-construction SLP. will be
//returned at end of `compile`
};
} // namespace bertini
#endif // for the ifndef include guards