Bertini 2 d882924 (2026-10-06 19:56 UTC)
Numerical algebraic geometry library (C++ core)
Loading...
Searching...
No Matches
patch.hpp
Go to the documentation of this file.
1//This file is part of Bertini 2.
2//
3//include/bertini2/system/patch.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//include/bertini2/system/patch.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 include/bertini2/system/patch.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#ifndef BERTINI_PATCH_HPP
29#define BERTINI_PATCH_HPP
30
32
36
37#include <stdexcept>
38#include <string>
39#include <vector>
40
41#include <Eigen/Dense>
42
43#include <boost/archive/text_oarchive.hpp>
44#include <boost/archive/text_iarchive.hpp>
45
46#include <boost/serialization/vector.hpp>
47
48namespace bertini {
49
50
51
52
84 class Patch
85 {
86
87 public:
88
94 Patch() : precision_(DefaultPrecision())
95 {}
96
97
101 Patch(Patch const& other)
102 {
103 variable_group_sizes_ = other.variable_group_sizes_;
104 precision_ = DefaultPrecision();
105
106 // a little shorthand unpacking the tuple
107 std::vector<Vec<complex_mp> >& coefficients_mpfr = std::get<std::vector<Vec<complex_mp> > >(this->coefficients_working_);
108 std::vector<Vec<complex_dbl> >& coefficients_dbl = std::get<std::vector<Vec<complex_dbl> > >(this->coefficients_working_);
109
110 coefficients_highest_precision_.resize(other.NumVariableGroups());
111 coefficients_mpfr.resize(variable_group_sizes_.size());
112 coefficients_dbl.resize(variable_group_sizes_.size());
113
114 for (unsigned ii(0); ii<other.NumVariableGroups(); ++ii)
115 {
116 auto curr_size = variable_group_sizes_[ii];
117
118 coefficients_highest_precision_[ii].resize(curr_size);
119 coefficients_dbl[ii].resize(curr_size);
120 coefficients_mpfr[ii].resize(curr_size);
121
122 for (unsigned jj(0); jj<curr_size; jj++)
123 {
124 coefficients_highest_precision_[ii](jj).precision(other.coefficients_highest_precision_[ii](jj).precision());
125
126 coefficients_highest_precision_[ii](jj) = other.coefficients_highest_precision_[ii](jj);
127
128 coefficients_dbl[ii](jj) = complex_dbl(coefficients_highest_precision_[ii](jj));
129 coefficients_mpfr[ii](jj) = complex_mp(coefficients_highest_precision_[ii](jj));
130
131 assert(coefficients_highest_precision_[ii](jj) == other.coefficients_highest_precision_[ii](jj));
132 }
133 }
134 }
135
140 Patch& operator=(Patch const& other) = default;
141
142
152 Patch(std::vector<unsigned> const& sizes) : coefficients_highest_precision_(sizes.size()), variable_group_sizes_(sizes), precision_(DefaultPrecision())
153 {
154 using bertini::Precision;
156
157 std::vector<Vec<complex_mp> >& coefficients_mpfr = std::get<std::vector<Vec<complex_mp> > >(coefficients_working_);
158 std::vector<Vec<complex_dbl> >& coefficients_dbl = std::get<std::vector<Vec<complex_dbl> > >(coefficients_working_);
159
160 coefficients_highest_precision_.resize(sizes.size());
161 coefficients_dbl.resize(sizes.size());
162 coefficients_mpfr.resize(sizes.size());
163
164 for (size_t ii=0; ii<sizes.size(); ++ii)
165 {
166 // this produces coefficients at maximum precision.
167 coefficients_highest_precision_[ii].resize(sizes[ii]);
168 for (unsigned jj=0; jj<sizes[ii]; ++jj)
169 {
170 // bounded-modulus draw (away from 0 and infinity), matching the linear-product /
171 // mhom / binomial start-system coefficients -- a heavy-tailed coefficient here scales
172 // the patch equation badly and feeds the same near-t=0 conditioning trouble.
173 multiprecision::RandomComplexBoundedModulusAssign(coefficients_highest_precision_[ii](jj), MaxPrecisionAllowed());
174 }
175
176 coefficients_dbl[ii].resize(sizes[ii]);
177 for (unsigned jj=0; jj<sizes[ii]; ++jj)
178 {
179 coefficients_dbl[ii](jj) = complex_dbl(coefficients_highest_precision_[ii](jj));
180 }
181
182 // assignment preserves precision of source.
183 // https://github.com/boostorg/multiprecision/issues/75
184 coefficients_mpfr[ii].resize(sizes[ii]);
185 for (unsigned jj=0; jj<sizes[ii]; ++jj)
186 {
187 coefficients_mpfr[ii](jj) = coefficients_highest_precision_[ii](jj);
188 coefficients_mpfr[ii](jj).precision(precision_);
189 }
190
191 assert(Precision(coefficients_mpfr[ii](0))==precision_);
192 }
193 }
194
195
204 Patch(std::vector<unsigned> const& sizes, std::vector<Vec<complex_mp>> const& coefficients)
205 : variable_group_sizes_(sizes), precision_(DefaultPrecision())
206 {
207 if (coefficients.size() != sizes.size())
208 throw std::invalid_argument("Patch: " + std::to_string(coefficients.size())
209 + " coefficient vectors for " + std::to_string(sizes.size()) + " variable groups");
210
211 std::vector<Vec<complex_mp> >& coefficients_mpfr = std::get<std::vector<Vec<complex_mp> > >(coefficients_working_);
212 std::vector<Vec<complex_dbl> >& coefficients_dbl = std::get<std::vector<Vec<complex_dbl> > >(coefficients_working_);
213
214 coefficients_highest_precision_.resize(sizes.size());
215 coefficients_dbl.resize(sizes.size());
216 coefficients_mpfr.resize(sizes.size());
217
218 for (size_t ii=0; ii<sizes.size(); ++ii)
219 {
220 if (static_cast<size_t>(coefficients[ii].size()) != sizes[ii])
221 throw std::invalid_argument("Patch: group " + std::to_string(ii) + " has "
222 + std::to_string(coefficients[ii].size()) + " coefficients for "
223 + std::to_string(sizes[ii]) + " variables");
224
225 coefficients_highest_precision_[ii].resize(sizes[ii]);
226 coefficients_dbl[ii].resize(sizes[ii]);
227 coefficients_mpfr[ii].resize(sizes[ii]);
228 for (unsigned jj=0; jj<sizes[ii]; ++jj)
229 {
230 // set the precision first so the copy is exact at the source's precision
231 coefficients_highest_precision_[ii](jj).precision(coefficients[ii](jj).precision());
232 coefficients_highest_precision_[ii](jj) = coefficients[ii](jj);
233 coefficients_dbl[ii](jj) = complex_dbl(coefficients_highest_precision_[ii](jj));
234 coefficients_mpfr[ii](jj) = coefficients_highest_precision_[ii](jj);
235 coefficients_mpfr[ii](jj).precision(precision_);
236 }
237 }
238 }
239
240
244 static Patch Random(std::vector<unsigned> const& sizes)
245 {
246 return Patch(sizes);
247 }
248
249
253 static Patch RandomReal(std::vector<unsigned> const& sizes)
254 {
256 using bertini::Precision;
257
258 Patch p;
259
260 p.variable_group_sizes_ = sizes;
261
262
263
264 std::vector<Vec<complex_mp> >& coefficients_mpfr = std::get<std::vector<Vec<complex_mp> > >(p.coefficients_working_);
265 std::vector<Vec<complex_dbl> >& coefficients_dbl = std::get<std::vector<Vec<complex_dbl> > >(p.coefficients_working_);
266
267 p.coefficients_highest_precision_.resize(sizes.size());
268 coefficients_mpfr.resize(sizes.size());
269 coefficients_dbl.resize(sizes.size());
270
271 for (size_t ii=0; ii<sizes.size(); ++ii)
272 {
273 p.coefficients_highest_precision_[ii].resize(sizes[ii]);
274 for (unsigned jj=0; jj<sizes[ii]; ++jj)
275 // real bounded-modulus draw: away from 0 and infinity (same recipe as the complex
276 // patch / start systems) but kept REAL, so a real patch keeps a real path real.
277 multiprecision::RandomRealBoundedModulusAssign(p.coefficients_highest_precision_[ii](jj), MaxPrecisionAllowed());
278
279 coefficients_mpfr[ii] = p.coefficients_highest_precision_[ii];
280 Precision(coefficients_mpfr[ii],DefaultPrecision());
281 assert(Precision(coefficients_mpfr[ii](0))==DefaultPrecision());
282
283 coefficients_dbl[ii].resize(sizes[ii]);
284 for (unsigned jj=0; jj<sizes[ii]; ++jj)
285 coefficients_dbl[ii](jj) = complex_dbl(p.coefficients_highest_precision_[ii](jj));
286 }
287
288 return p;
289 }
290
291
297 unsigned Precision() const
298 {
299 return precision_;
300 }
301
306 template <typename T>
307 void SyncPrecision(Vec<T> const& x) const
308 {
309 if constexpr (!std::is_same<T, complex_dbl>::value)
310 {
311 if (x.size() > 0)
312 {
313 const unsigned p = bertini::Precision(x(0));
314 if (p != precision_)
315 Precision(p);
316 }
317 }
318 }
319
327 void Precision(unsigned new_precision) const
328 {
329 if (precision_==new_precision)
330 return;
331
332 using bertini::Precision;
333 std::vector<Vec<complex_mp> >& coefficients_mpfr = std::get<std::vector<Vec<complex_mp> > >(coefficients_working_);
334
335 for (unsigned ii = 0; ii < NumVariableGroups(); ++ii)
336 {
337 for (unsigned jj=0; jj<variable_group_sizes_[ii]; ++jj)
338 {
339 if (new_precision>precision_)
340 {
341 coefficients_mpfr[ii](jj) = coefficients_highest_precision_[ii](jj);
342 coefficients_mpfr[ii](jj).precision(new_precision);
343 }
344 else
345 coefficients_mpfr[ii](jj).precision(new_precision);
346
347 assert(Precision(coefficients_mpfr[ii](jj))==new_precision);
348 }
349 }
350
351 precision_ = new_precision;
352 }
353
354
355
356
357
358
369 template<typename Derived, typename T>
370 void EvalInPlace(Eigen::MatrixBase<Derived> & function_values, Vec<T> const& x) const
371 {
372 static_assert(std::is_same<typename Derived::Scalar,T>::value,"scalar types must match");
373
374 SyncPrecision(x);
375
376 #ifndef BERTINI_DISABLE_ASSERTS
377 if (! (function_values.size()>=NumVariableGroups()) )
378 {
379 std::stringstream ss;
380 ss << "container for function values must be of length at least as long as the number of variable groups. the input vector into which to write is of length " << function_values.size();
381 throw std::runtime_error(ss.str());
382 }
383 // Formerly an assert that the input's precision MATCHED the patch's -- disabled long
384 // ago because it could not be honoured. A patch is an evaluable type, so it now
385 // self-aligns instead of demanding to be aligned for: ADR-0057.
386// );
387 #endif
388
389 // unpack from the tuple of working coefficients
390 const std::vector<Vec<T> >& coefficients = std::get<std::vector<Vec<T> > >(coefficients_working_);
391
392 unsigned offset(static_cast<unsigned>(function_values.size() - NumVariableGroups())); // by precondition this number is at least 0. the precondition is ensured by the public wrapper
393 unsigned counter(0);
394 for (unsigned ii = 0; ii < NumVariableGroups(); ++ii)
395 {
396 T& value = function_values(ii+offset);
397 value = T(-1);
398 for (unsigned jj=0; jj<variable_group_sizes_[ii]; ++jj)
399 {
400 value += x(counter)*coefficients[ii](jj);
401 counter++;
402 }
403 }
404 }
405
412 template<typename T>
413 Vec<T> Eval(Vec<T> const& x) const
414 {
415 Vec<T> function_values = Vec<T>::Zero(NumVariableGroups());
416 EvalInPlace(function_values, x);
417 return function_values;
418 }
419
430 template<typename Derived, typename T>
431 void JacobianInPlace(Eigen::MatrixBase<Derived> & jacobian, Vec<T> const& x) const
432 {
433 static_assert(std::is_same<typename Derived::Scalar,T>::value,"scalar types must match");
434
435 SyncPrecision(x); // the jacobian is constant in x, but it is written FROM the
436 // working coefficients, so they must be at x's precision
437
438
439 #ifndef BERTINI_DISABLE_ASSERTS
440 assert(jacobian.rows()>=NumVariableGroups() && "input jacobian must have at least as many rows as variable groups");
441 assert(jacobian.cols()==NumVariables() && "input jacobian must have as many columns as the patch has variables");
442 assert(
444 && "precision of input vector must match current working precision of patch during evaluation"
445 );
446 #endif
447
448 const std::vector<Vec<T> >& coefficients = std::get<std::vector<Vec<T> > >(coefficients_working_);
449
450 unsigned offset(static_cast<unsigned>(jacobian.rows() - NumVariableGroups())); // by precondition this number is at least 0. the precondition is ensured by the public wrapper
451
452 // A patch row is sparse -- one block of coefficients per variable group, zero elsewhere.
453 // Zero the rows the patch owns before writing those coefficients, so the patch FULLY
454 // defines its own rows and the caller need not pre-zero the matrix. The block-composed
455 // Jacobian path allocates J uninitialized and assigns only the block (function) rows;
456 // without this the patch rows' off-coefficient entries are read uninitialized -- benign
457 // (zeroed pages) on Linux/macOS, but garbage on Windows, where a degree-2 homotopy's
458 // Jacobian "evaluated" to ~1e252 and wrecked the AMP condition-number estimate.
459 jacobian.block(offset, 0, NumVariableGroups(), jacobian.cols()).setZero();
460
461 unsigned counter(0);
462 for (unsigned ii = 0; ii < NumVariableGroups(); ++ii)
463 for (unsigned jj=0; jj<variable_group_sizes_[ii]; ++jj)
464 jacobian(ii+offset,counter++) = coefficients[ii](jj);
465 }
466
473 template<typename T>
474 Mat<T> Jacobian(Vec<T> const& x) const
475 {
477 JacobianInPlace(jacobian, x);
478 return jacobian;
479 }
480
481
492 template<typename T>
494 {
495 // Self-align to the point, exactly as EvalInPlace and the Jacobian do -- ADR-0057.
496 // Without this, rescaling read whatever precision the working coefficients happened to
497 // be left at by the last evaluation, and the rescaled point came back REPORTING the
498 // requested precision while carrying only the older one's correct digits. Nothing
499 // downstream could see it: the assert that used to stand here demanded the caller align
500 // the precisions beforehand, and asserts are compiled out of a release build, so the
501 // one check that could have caught it was absent exactly where it mattered (b2#461).
502 SyncPrecision(x);
503
504 #ifndef BERTINI_DISABLE_ASSERTS
505 assert(x.size() == NumVariables() && "input point for rescaling to fit a patch must have same length as total number of variables being patched, in all variable groups.");
506 #endif
507
508 const std::vector<Vec<T> >& coefficients = std::get<std::vector<Vec<T> > >(coefficients_working_);
509
510 unsigned starting_index_counter(0);
511 for (unsigned ii=0; ii<NumVariableGroups(); ii++)
512 {
513 auto subvec = x.segment(starting_index_counter,variable_group_sizes_[ii]);
514 subvec /= subvec.transpose() * coefficients[ii];
515 starting_index_counter += variable_group_sizes_[ii];
516 }
517 }
518
526 template <typename T>
527 Vec<T> RescalePoint(Vec<T> const& x) const
528 {
529 Vec<T> x_rescaled = x;
530 RescalePointToFitInPlace(x_rescaled);
531 return x_rescaled;
532 }
533
539 unsigned NumVariableGroups() const
540 {
541 return static_cast<unsigned>(variable_group_sizes_.size());
542 }
543
544
548 unsigned NumVariables() const
549 {
550 unsigned num_vars(0);
551 for (auto v : variable_group_sizes_)
552 num_vars += v;
553 return num_vars;
554 }
555
564 std::vector< Vec< complex_mp > > const& Coefficients() const
565 {
566 return coefficients_highest_precision_;
567 }
568
570 std::vector<unsigned> const& VariableGroupSizes() const
571 {
572 return variable_group_sizes_;
573 }
574
575
577 friend std::ostream& operator<<(std::ostream & out, Patch const& p)
578 {
579 out << p.NumVariableGroups() << " variable groups being patched\n";
580 unsigned counter(0);
581 for (auto& c : p.coefficients_highest_precision_)
582 {
583 out << "patch " << counter++ << " has " << c.size() << " coefficients:\n";
584 out << c << "\n";
585 }
586 out << "current patch precision: " << p.Precision() << "\n";
587 return out;
588 }
589
595 bool operator==(Patch const& rhs) const
596 {
598 return false;
599 if (NumVariables()!=rhs.NumVariables())
600 return false;
601 for (unsigned ii=0; ii<NumVariableGroups(); ii++)
602 if (variable_group_sizes_[ii]!=rhs.variable_group_sizes_[ii])
603 return false;
604 for (unsigned ii=0; ii<NumVariableGroups(); ii++)
605 if (coefficients_highest_precision_[ii]!=rhs.coefficients_highest_precision_[ii])
606 return false;
607
608
609 return true;
610 }
611
615 bool operator!=(Patch const& rhs) const
616 {
617 return !(*this==rhs);
618 }
619
620 private:
621
623 //
624 // Data members
625 //
627
628 std::vector< Vec< complex_mp > > coefficients_highest_precision_;
629
630 mutable std::tuple< std::vector< Vec< complex_mp > >, std::vector< Vec< complex_dbl > > > coefficients_working_;
631
632 std::vector<unsigned> variable_group_sizes_;
633
634 mutable unsigned precision_;
635
636 // add serialization support through boost.
637
639
640 template <typename Archive>
641 void serialize(Archive& ar, const unsigned /*version*/) {
642 ar & precision_;
643
644 ar & coefficients_highest_precision_;
645
646 ar & std::get<0>(coefficients_working_);
647 ar & std::get<1>(coefficients_working_);
648 ar & variable_group_sizes_;
649
650 }
651
652 };
653
654
655}
656
657
658#endif // re: include guards
Vec< T > RescalePoint(Vec< T > const &x) const
Rescale a point so that it satisfies the patch equations herein contained.
Definition patch.hpp:527
void JacobianInPlace(Eigen::MatrixBase< Derived > &jacobian, Vec< T > const &x) const
Evaluate the Jacobian matrix, in place.
Definition patch.hpp:431
Vec< T > Eval(Vec< T > const &x) const
Evaluate the patch at a point.
Definition patch.hpp:413
friend std::ostream & operator<<(std::ostream &out, Patch const &p)
Stream-insertion for a Patch, summarizing its variable groups and coefficients.
Definition patch.hpp:577
void Precision(unsigned new_precision) const
Set the precision of the patch.
Definition patch.hpp:327
void RescalePointToFitInPlace(Vec< T > &x) const
Rescale a point so that it satisfies the patch equations herein contained.
Definition patch.hpp:493
static Patch RandomReal(std::vector< unsigned > const &sizes)
Construct random REAL patch on a space.
Definition patch.hpp:253
std::vector< unsigned > const & VariableGroupSizes() const
The number of variables in each variable group (homogenizing variable included).
Definition patch.hpp:570
Patch & operator=(Patch const &other)=default
Copy assignment.
unsigned NumVariableGroups() const
Get the number of variable groups.
Definition patch.hpp:539
void SyncPrecision(Vec< T > const &x) const
Materialize the working coefficients at the precision of the point being evaluated.
Definition patch.hpp:307
Patch()
Default constructor.
Definition patch.hpp:94
void EvalInPlace(Eigen::MatrixBase< Derived > &function_values, Vec< T > const &x) const
Evaluate the patch at a point, in place.
Definition patch.hpp:370
Patch(std::vector< unsigned > const &sizes)
Constructor making a random complex patch on a space whose structure is described by the input argume...
Definition patch.hpp:152
Mat< T > Jacobian(Vec< T > const &x) const
Evaluate the Jacobian matrix, in place.
Definition patch.hpp:474
std::vector< Vec< complex_mp > > const & Coefficients() const
The patch's highest-precision coefficients, one vector per variable group.
Definition patch.hpp:564
static Patch Random(std::vector< unsigned > const &sizes)
Construct a random complex patch on a space.
Definition patch.hpp:244
unsigned Precision() const
Get the current precision of the patch.
Definition patch.hpp:297
friend class boost::serialization::access
Definition patch.hpp:638
unsigned NumVariables() const
Get the number of variables in the patch.
Definition patch.hpp:548
bool operator==(Patch const &rhs) const
Check whether two patches are the same.
Definition patch.hpp:595
Patch(Patch const &other)
Custom copy constructor, ensuring the max-precision coefficients are copied in highest precision.
Definition patch.hpp:101
bool operator!=(Patch const &rhs) const
Check whether two patches are different.
Definition patch.hpp:615
Patch(std::vector< unsigned > const &sizes, std::vector< Vec< complex_mp > > const &coefficients)
Construct a patch from given coefficients – for a loader restoring an archived patch exactly (the can...
Definition patch.hpp:204
Bertini extensions of the Eigen linear algebra library.
The main multiprecision complex number type.
Extensions to the Boost.Multiprecision library.
complex RandomComplex(unsigned num_digits)
Produce a random complex number, at the given precision.
Definition random.hpp:465
void RandomComplexBoundedModulusAssign(complex &a, unsigned num_digits)
Assign to a a random complex number whose modulus is pulled toward 1, at the given precision.
Definition random.hpp:355
void RandomRealBoundedModulusAssign(complex &a, unsigned num_digits)
Assign to a a random REAL number (imaginary part 0) whose modulus is pulled toward 1,...
Definition random.hpp:415
complex RandomReal()
Produce a random real number , to current default precision.
Definition random.hpp:262
Definition algorithm_builder.hpp:36
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
Eigen::Matrix< NumT, Eigen::Dynamic, Eigen::Dynamic > Mat
A dynamically-sized matrix of NumT.
Definition eigen_extensions.hpp:298
constexpr unsigned MaxPrecisionAllowed()
The maximum precision (digits) the library will escalate to.
Definition num_traits.hpp:166
bmp::number< mpc_complex_backend< 0 >, bmp::et_on > complex_mp
The arbitrary-precision complex number type.
Definition mpfr_complex.hpp:58
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.