Bertini 2 d882924 (2026-10-06 19:56 UTC)
Numerical algebraic geometry library (C++ core)
Loading...
Searching...
No Matches
blend_block.hpp
Go to the documentation of this file.
1//This file is part of Bertini 2.
2//
3//blend_block.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//blend_block.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 blend_block.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
57
58#pragma once
59
60#include <vector>
61#include <memory>
62#include <cassert>
63
64#include <boost/serialization/vector.hpp>
65#include <boost/serialization/shared_ptr.hpp>
66#include <boost/serialization/split_member.hpp>
67
73
74namespace bertini {
75namespace blocks {
76
78template <typename SystemT>
80{
81public:
82 using Nd = std::shared_ptr<node::Node>;
83 using Var = std::shared_ptr<node::Variable>;
84 using OperandPtr = std::shared_ptr<const SystemT>;
85
87 BlendBlock() : precision_(DefaultPrecision()) {}
88
94 BlendBlock(Var path_variable, std::vector<Nd> coefficients, std::vector<OperandPtr> operands)
95 : path_variable_(std::move(path_variable)),
96 coefficients_(std::move(coefficients)),
97 operands_(std::move(operands)),
98 precision_(DefaultPrecision())
99 {
100 assert(!operands_.empty() && "BlendBlock needs at least one operand");
101 assert(coefficients_.size() == operands_.size() && "one coefficient per operand");
102 derivative_coefficients_.reserve(coefficients_.size());
103 for (auto const& c : coefficients_)
104 derivative_coefficients_.push_back(c->Differentiate(path_variable_));
105 }
106
107 // Memory-isolating copy (ADR-0027): each operand System is deep-copied via the
108 // System copy constructor, which shares its immutable node DAG and compiled SLP Program but
109 // gives it its own per-thread evaluation Memory. The coefficient-system cache is reset (rebuilt
110 // lazily per copy). The coefficient nodes / path variable are shared --- they are never written
111 // during evaluation. This lets path tracking Clone a System into per-thread copies that share
112 // no mutable state.
114 BlendBlock(BlendBlock const& other)
115 : path_variable_(other.path_variable_),
116 coefficients_(other.coefficients_),
117 derivative_coefficients_(other.derivative_coefficients_),
118 precision_(other.precision_)
119 {
120 operands_.reserve(other.operands_.size());
121 for (auto const& op : other.operands_)
122 operands_.push_back(std::make_shared<const SystemT>(*op));
123 // coefficient_system_ deliberately left null: rebuilt lazily per copy
124 }
125
128 {
129 if (this != &other)
130 {
131 path_variable_ = other.path_variable_;
132 coefficients_ = other.coefficients_;
133 derivative_coefficients_ = other.derivative_coefficients_;
134 precision_ = other.precision_;
135 operands_.clear();
136 operands_.reserve(other.operands_.size());
137 for (auto const& op : other.operands_)
138 operands_.push_back(std::make_shared<const SystemT>(*op));
139 coefficient_system_.reset();
140 }
141 return *this;
142 }
143
145 BlendBlock(BlendBlock&&) = default;
148
150 size_t NumFunctions() const
151 {
152 return operands_.empty() ? 0 : operands_.front()->NumNaturalFunctions();
153 }
154
158 std::vector<Nd> const& Coefficients() const { return coefficients_; }
160 std::vector<OperandPtr> const& Operands() const { return operands_; }
162 Var const& PathVariable() const { return path_variable_; }
163
167 void Describe(std::ostream& out, size_t& row, VariableGroup const& /*vars*/, bool verbose) const
168 {
169 const size_t k = NumFunctions();
170 out << " ";
172 out << " = ";
173 for (size_t i = 0; i < coefficients_.size(); ++i)
174 out << (i ? " + " : "") << "(" << coefficients_[i] << ")*"
175 << static_cast<char>('A' + (i < 26 ? i : 25));
176 out << " (blend of " << operands_.size() << " systems)\n";
177 row += k;
178 if (verbose)
179 for (size_t i = 0; i < operands_.size(); ++i)
180 {
181 const char label = static_cast<char>('A' + (i < 26 ? i : 25));
182 out << " " << label << ":\n";
183 auto f = operands_[i]->NaturalFunctionsAsNodes();
184 for (size_t j = 0; j < f.size(); ++j)
185 out << " " << label << "_" << j << " = " << f[j] << "\n";
186 }
187 }
188
194 static int CombineDegrees(int a, int b)
195 {
196 if (a < 0 || b < 0)
197 return -1;
198 return std::max(a, b);
199 }
200
204 std::vector<int> Degrees() const
205 {
206 std::vector<int> d(NumFunctions(), 0);
207 for (auto const& op : operands_)
208 {
209 auto od = op->Degrees();
210 for (size_t i = 0; i < d.size() && i < od.size(); ++i) d[i] = CombineDegrees(d[i], od[i]);
211 }
212 return d;
213 }
214
215 std::vector<int> Degrees(VariableGroup const& vars) const
216 {
217 std::vector<int> d(NumFunctions(), 0);
218 for (auto const& op : operands_)
219 {
220 auto od = op->Degrees(vars);
221 for (size_t i = 0; i < d.size() && i < od.size(); ++i) d[i] = CombineDegrees(d[i], od[i]);
222 }
223 return d;
224 }
225
226 // A blend is the coupling homotopy, built from already-prepared (homogenized) operands; its
227 // operands are shared_ptr<const System> and cannot be mutated, so Homogenize is a no-op. It
228 // is homogeneous/polynomial iff all its operands are.
230 void Homogenize(VariableGroup const&, std::shared_ptr<node::Variable> const&) {}
232 bool IsHomogeneous(VariableGroup const&) const
233 {
234 for (auto const& op : operands_) if (!op->IsHomogeneous()) return false;
235 return true;
236 }
237
238 bool IsPolynomial(VariableGroup const&) const
239 {
240 for (auto const& op : operands_) if (!op->IsPolynomial()) return false;
241 return true;
242 }
243
245 bool DependsOnPathVariable() const { return true; }
246
248 bool HasConstantJacobian() const { return false; }
249
251 void Differentiate() const {}
252
258 {
259 if (path_variable_)
260 path_variable_ = std::static_pointer_cast<node::Variable>(node::Reintern(path_variable_, memo));
261 for (auto& c : coefficients_)
262 c = node::Reintern(c, memo);
263 derivative_coefficients_.clear();
264 derivative_coefficients_.reserve(coefficients_.size());
265 for (auto const& c : coefficients_)
266 derivative_coefficients_.push_back(c->Differentiate(path_variable_));
267 for (auto& op : operands_)
268 std::const_pointer_cast<SystemT>(op)->ReinternNodes(memo);
269 coefficient_system_.reset(); // rebuilt lazily against the reinterned coefficients
270 }
271
273 unsigned Precision() const { return precision_; }
274
276 void Precision(unsigned new_precision) const
277 {
278 // short-circuit when already materialized here. Each holder of mp values keeps its
279 // own "materialized at" tag; System deliberately keeps none and simply fans out on
280 // every evaluation, which is cheap precisely because of this early return (ADR-0057).
281 if (precision_==new_precision)
282 return;
283 // operands are Systems: they self-align from the point they are evaluated at, so
284 // nothing is pushed into them here (ADR-0057)
285 // The coefficients are evaluated through the coefficient sub-system's SLP (which carries
286 // its own precision; see EvalCoefficients), so the coefficient nodes and the shared path
287 // variable are no longer evaluated during tracking. Their precision is vestigial and
288 // left untouched, keeping the shared node DAG read-only across threads (ADR-0027).
289 precision_ = new_precision;
290 }
291
293 template <typename T>
294 void EvalInPlace(Eigen::Ref<Vec<T>> result, Vec<T> const& vars, T const& path_value) const
295 {
296 SyncPrecision(vars);
297 result.setZero();
298 const Eigen::Index k = static_cast<Eigen::Index>(NumFunctions());
299 const Vec<T> coeffs = EvalCoefficients<T>(path_value);
300 for (size_t i = 0; i < operands_.size(); ++i)
301 {
302 const Vec<T> fi = operands_[i]->template Eval<T>(vars);
303 result += coeffs(static_cast<Eigen::Index>(i)) * fi.head(k);
304 }
305 }
306
308 template <typename T>
309 void JacobianInPlace(Eigen::Ref<Mat<T>> J, Vec<T> const& vars, T const& path_value) const
310 {
311 SyncPrecision(vars);
312 J.setZero();
313 const Eigen::Index k = static_cast<Eigen::Index>(NumFunctions());
314 const Vec<T> coeffs = EvalCoefficients<T>(path_value);
315 for (size_t i = 0; i < operands_.size(); ++i)
316 {
317 const Mat<T> Ji = operands_[i]->template Jacobian<T>(vars);
318 J += coeffs(static_cast<Eigen::Index>(i)) * Ji.topRows(k);
319 }
320 }
321
323 template <typename T>
324 void TimeDerivInPlace(Eigen::Ref<Vec<T>> result, Vec<T> const& vars, T const& path_value) const
325 {
326 SyncPrecision(vars);
327 result.setZero();
328 const Eigen::Index k = static_cast<Eigen::Index>(NumFunctions());
329 const Vec<T> coeffs = EvalCoefficients<T>(path_value);
330 const size_t n = operands_.size();
331 for (size_t i = 0; i < n; ++i)
332 {
333 const Vec<T> fi = operands_[i]->template Eval<T>(vars);
334 result += coeffs(static_cast<Eigen::Index>(n + i)) * fi.head(k);
335 }
336 }
337
338private:
342 template <typename T>
343 void SyncPrecision(Vec<T> const& vars) const
344 {
345 if constexpr (!std::is_same<T, complex_dbl>::value)
346 {
347 if (vars.size() > 0)
348 {
349 const unsigned p = bertini::Precision(vars(0));
350 if (p != precision_)
351 Precision(p);
352 }
353 }
354 }
355
361 SystemT& EnsureCoefficientSystem() const
362 {
363 if (!coefficient_system_)
364 {
365 auto sys = std::make_shared<SystemT>();
366 for (auto const& c : coefficients_)
367 sys->AddFunction(c);
368 for (auto const& c : derivative_coefficients_)
369 sys->AddFunction(c);
370 sys->AddVariableGroup(VariableGroup{path_variable_});
371 coefficient_system_ = sys;
372 }
373 return *coefficient_system_;
374 }
375
377 template <typename T>
378 Vec<T> EvalCoefficients(T const& path_value) const
379 {
380 // cs is a System: it self-aligns from t_point below, so nothing is pushed into it here
381 auto& cs = EnsureCoefficientSystem();
382 Vec<T> t_point(1);
383 t_point(0) = path_value;
384 return cs.template Eval<T>(t_point);
385 }
386
387 Var path_variable_;
388 std::vector<Nd> coefficients_;
389 std::vector<Nd> derivative_coefficients_;
390 std::vector<OperandPtr> operands_;
391 mutable unsigned precision_;
392
395 mutable std::shared_ptr<SystemT> coefficient_system_;
396
398
399 // Operands are shared_ptr<const SystemT>; serialize them as non-const pointers so we
400 // never ask boost to deserialize into a const object (const-ness is a local concern).
401 template <typename Archive>
402 void save(Archive& ar, const unsigned /*version*/) const
403 {
404 ar & precision_;
405 ar & path_variable_;
406 ar & coefficients_;
407 ar & derivative_coefficients_;
408 std::vector<std::shared_ptr<SystemT>> ops;
409 ops.reserve(operands_.size());
410 for (auto const& o : operands_)
411 ops.push_back(std::const_pointer_cast<SystemT>(o));
412 ar & ops;
413 }
414
415 template <typename Archive>
416 void load(Archive& ar, const unsigned /*version*/)
417 {
418 ar & precision_;
419 ar & path_variable_;
420 ar & coefficients_;
421 ar & derivative_coefficients_;
422 std::vector<std::shared_ptr<SystemT>> ops;
423 ar & ops;
424 operands_.assign(ops.begin(), ops.end());
425 }
426
427 BOOST_SERIALIZATION_SPLIT_MEMBER()
428};
429
430} // namespace blocks
431} // namespace bertini
BlendBlock & operator=(BlendBlock &&)=default
Move assignment.
BlendBlock(BlendBlock &&)=default
Move constructor.
BlendBlock(Var path_variable, std::vector< Nd > coefficients, std::vector< OperandPtr > operands)
Definition blend_block.hpp:94
void Reintern(node::ReinternMemo &memo)
Re-intern this block's nodes after deserialization (ADR-0042): path variable and coefficient nodes (g...
Definition blend_block.hpp:257
bool DependsOnPathVariable() const
A blend always depends on the path variable (through its coefficients).
Definition blend_block.hpp:245
std::vector< OperandPtr > const & Operands() const
Get the operand systems being blended.
Definition blend_block.hpp:160
unsigned Precision() const
Get the block's current working precision.
Definition blend_block.hpp:273
BlendBlock(BlendBlock const &other)
Memory-isolating copy constructor (deep-copies each operand system; ADR-0027).
Definition blend_block.hpp:114
void EvalInPlace(Eigen::Ref< Vec< T > > result, Vec< T > const &vars, T const &path_value) const
H(x,t) = sum_i c_i(t) * f_i(x) (natural functions only).
Definition blend_block.hpp:294
std::vector< int > Degrees(VariableGroup const &vars) const
Per-function degrees with respect to a given variable group.
Definition blend_block.hpp:215
std::vector< int > Degrees() const
The blend c_0(t)f_0 + c_1(t)f_1 + ... has, per function, the max degree of its operands in the space ...
Definition blend_block.hpp:204
BlendBlock()
Construct an empty blend block at the current default precision.
Definition blend_block.hpp:87
void TimeDerivInPlace(Eigen::Ref< Vec< T > > result, Vec< T > const &vars, T const &path_value) const
dH/dt = sum_i c_i'(t) * f_i(x) (operands are autonomous in t)
Definition blend_block.hpp:324
Var const & PathVariable() const
Get the shared path variable the coefficients are functions of.
Definition blend_block.hpp:162
static int CombineDegrees(int a, int b)
Combine two per-function degrees, propagating the not-a-polynomial sentinel.
Definition blend_block.hpp:194
void JacobianInPlace(Eigen::Ref< Mat< T > > J, Vec< T > const &vars, T const &path_value) const
dH/dx = sum_i c_i(t) * (df_i/dx) (natural rows only)
Definition blend_block.hpp:309
std::vector< Nd > const & Coefficients() const
Accessors for the function-tree expansion (System::ExpandToFunctionTree): the blend is H = sum_i coef...
Definition blend_block.hpp:158
std::shared_ptr< node::Node > Nd
Shorthand for a shared pointer to a generic node.
Definition blend_block.hpp:82
size_t NumFunctions() const
The number of (natural) functions the blend contributes; the owning System adds any patch.
Definition blend_block.hpp:150
void Precision(unsigned new_precision) const
Set the block's working precision (propagated to the operand systems).
Definition blend_block.hpp:276
std::shared_ptr< node::Variable > Var
Shorthand for a shared pointer to a variable node.
Definition blend_block.hpp:83
void Differentiate() const
Analytic block: nothing symbolic to differentiate.
Definition blend_block.hpp:251
void Homogenize(VariableGroup const &, std::shared_ptr< node::Variable > const &)
No-op: a blend's operands are already-prepared and cannot be homogenized.
Definition blend_block.hpp:230
friend class boost::serialization::access
Definition blend_block.hpp:397
bool IsHomogeneous(VariableGroup const &) const
Query whether the blend is homogeneous (iff all operands are).
Definition blend_block.hpp:232
bool HasConstantJacobian() const
A blend does not have a constant Jacobian.
Definition blend_block.hpp:248
void Describe(std::ostream &out, size_t &row, VariableGroup const &, bool verbose) const
Human-facing description: terse shows the blend f_a..f_b = c_0(t)*A + c_1(t)*B, with the coefficient ...
Definition blend_block.hpp:167
bool IsPolynomial(VariableGroup const &) const
Query whether the blend is polynomial (iff all operands are).
Definition blend_block.hpp:238
std::shared_ptr< const SystemT > OperandPtr
Shorthand for a shared pointer to a (const) operand system.
Definition blend_block.hpp:84
BlendBlock & operator=(BlendBlock const &other)
Memory-isolating copy assignment (deep-copies each operand system; ADR-0027).
Definition blend_block.hpp:127
Small shared helpers for the per-block Describe methods – the human-facing printing of a block-compos...
Bertini extensions of the Eigen linear algebra library.
Collects the various header files which define the Bertini2 function tree node types.
void PrintRowLabel(std::ostream &out, size_t row, size_t n)
f_k for a single row, or f_a..f_b for a contiguous range of n rows starting at row.
Definition describe.hpp:71
Definition blend_block.hpp:75
std::map< Node const *, std::shared_ptr< Node > > ReinternMemo
Memo for one re-interning pass: loaded node -> its interned replacement.
Definition reintern.hpp:57
std::shared_ptr< Node > Reintern(std::shared_ptr< Node > const &root, ReinternMemo &memo)
Rebuild the DAG rooted at root through the interning factories (memoized, post-order),...
Definition algorithm_builder.hpp:36
Eigen::Matrix< NumT, Eigen::Dynamic, 1 > Vec
A dynamically-sized column vector of NumT.
Definition eigen_extensions.hpp:296
std::vector< std::shared_ptr< node::Variable > > VariableGroup
An ordered group of variables (the unit of homogenization / variable grouping).
Definition node.hpp:69
Eigen::Matrix< NumT, Eigen::Dynamic, Eigen::Dynamic > Mat
A dynamically-sized matrix of NumT.
Definition eigen_extensions.hpp:298
auto DefaultPrecision()
Get the global default precision (digits) for multiprecision numbers.
Definition mpfr_complex.hpp:135
unsigned Precision(Eigen::MatrixBase< Derived > const &v)
Get the precision of an Eigen object.
Definition eigen_extensions.hpp:339
Provides an Eigen-like NumTraits struct for querying traits of a number type.
Re-intern deserialized node DAGs into the live hash-consing tables (ADR-0042).