GiNaC 1.8.10
Gt.cpp
Go to the documentation of this file.
1
5/*
6 * GiNaC Copyright (C) 1999-2026 Johannes Gutenberg University Mainz, Germany
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23#include <type_traits>
24#include <sstream>
25#include <limits>
26#include <cln/cln.h>
27#include "add.h"
28#include "mul.h"
29#include "power.h"
30#include "lst.h"
31#include "matrix.h"
32#include "symbol.h"
33#include "numeric.h"
34#include "inifcns.h"
35#include "operators.h"
36#include "relational.h"
38#include "hash_seed.h"
39#include "constant.h"
40#include "Gt.h"
41#include "Gt_helpers.h"
42
43#define DEBUG_GT 0
44// 0: no debugging output
45// 1: print each call to a transformation function (zisToFundamental, regularise, ...)
46// 2: also output entire expression, just before q-expansion
47// 3: also output entire expression after each group of transformation function
48// 4: output entire expression after each individual transformation function
49// 5: print intermediate results of transformation functions
50
51namespace GiNaC {
52
53
56 print_func<print_python>(&Gt::do_print).
57 print_func<print_latex>(&Gt::do_print_latex))
58
59
60Gt::kernel::kernel() : kernel(-1,0,0) {}
61
62Gt::kernel::kernel(size_t ni, ex zi, int deform)
63 : ni(ni), zi(zi), deform(deform)
64{
65 check();
66}
67
69{
70 if (arg.nops() != 2 && arg.nops() != 3)
71 throw std::invalid_argument("Gt: first argument must be a list of tuples");
72 ni = to_integer(arg.op(0), false);
73 zi = arg.op(1);
74 deform = (arg.nops() == 3) ? to_integer(arg.op(2), true) : 0;
75
76 check();
77}
78
80{
81 if (ni == 0) {
82 zi = 0;
83 deform = 0;
84 }
85 else {
86 if (zi.evalf().is_zero())
87 zi = 0;
88 if (std::abs(deform) > 1)
89 deform /= std::abs(deform);
90 }
91}
92
93
95// default constructor
97
98Gt::Gt() :
101{}
102
104// other constructors
106
107// public
108Gt::Gt(const ex& args_, const ex& z_, const ex& tau_) : z(z_), tau(tau_)
109{
110 setArgs(args_);
111
112 if (is_a<numeric>(tau.evalf()) && !ex_to<numeric>(tau.evalf()).imag().is_positive())
113 throw std::runtime_error("Gt: Im(tau) needs to be greater than 0 b");
114 if (nargs >= 64)
115 throw std::runtime_error("Gt: Too many kernels");
116}
117
118Gt::Gt(std::vector<kernel> args_, ex z_, ex tau_)
119 : nargs(args_.size()), args(args_), z(z_), tau(tau_)
120{
121
122 if (args.empty())
123 throw std::invalid_argument("Gt: first argument must be a non-empty list");
124 for (kernel& k : args)
125 k.check();
126 if (is_a<numeric>(tau.evalf()) && !ex_to<numeric>(tau.evalf()).imag().is_positive())
127 throw std::runtime_error("Gt: Im(tau) needs to be greater than 0 a");
128
129 if (nargs >= 64)
130 throw std::runtime_error("Gt: Too many kernels");
131}
132
133void Gt::setArgs(const ex& new_args)
134{
135 if (!is_a<lst>(new_args))
136 throw std::invalid_argument("Gt: first argument must be a list");
137
138 nargs = new_args.nops();
139 if (nargs == 0)
140 throw std::invalid_argument("Gt: first argument must be a non-empty list");
141
142 args.clear();
143 args.reserve(nargs);
144 for (size_t i = 0; i < nargs; i++) {
145 if (is_a<lst>(new_args.op(i)))
146 args.emplace_back(ex_to<lst>(new_args.op(i)));
147 else
148 throw std::invalid_argument("Gt: first argument must be a list of tuples");
149 }
150}
151
153// archiving
155
156void Gt::read_archive(const archive_node& n, lst& sym_lst)
157{
158 inherited::read_archive(n, sym_lst);
159 ex args;
160 n.find_ex("args", args, sym_lst);
161 n.find_ex("z", z, sym_lst);
162 n.find_ex("tau", tau, sym_lst);
163 setArgs(args);
164}
165
167{
168 inherited::archive(n);
169 lst lst_args;
170 for (size_t i = 0; i < nargs; i++)
171 lst_args.append(lst{args[i].ni, args[i].zi, args[i].deform});
172 n.add_ex("args", lst_args);
173 n.add_ex("z", z);
174 n.add_ex("tau", tau);
175}
176
178// functions overriding virtual functions from base classes
180
181void Gt::do_print(const print_context & c, unsigned level) const
182{
183 c.s << "Gt({";
184 for (size_t i = 0; i < nargs; i++) {
185 c.s << (i?",":"") << "{" << args[i].ni << "," << args[i].zi;
186 if (args[i].deform)
187 c.s << "," << args[i].deform;
188 c.s << "}";
189 }
190 c.s << "}," << z << "," << tau << ")";
191}
192
193void Gt::do_print_latex(const print_latex & c, unsigned level) const
194{
195 c.s << "Gt({";
196 for (size_t i = 0; i < nargs; i++)
197 c.s << "\\begin{matrix}" << args[i].ni << "\\\\" << args[i].zi << "\\end{matrix}";
198 c.s << ";" << z << "," << tau << "\\right)";
199}
200
201int Gt::compare_same_type(const basic& other) const
202{
203 GINAC_ASSERT(is_exactly_a<Gt>(other));
204
205 // Compare (in this order): nargs, kernels (ni, zi, deform), z, tau
206 const Gt& o = static_cast<const Gt&>(other);
207 if (nargs > o.nargs) return +1;
208 if (nargs < o.nargs) return -1;
209 for (size_t i = 0; i < nargs; i++) {
210 if (args[i].ni > o.args[i].ni) return +1;
211 if (args[i].ni < o.args[i].ni) return -1;
212 const int cmp = args[i].zi.compare(o.args[i].zi);
213 if (cmp > 0) return +1;
214 if (cmp < 0) return -1;
215 if (args[i].deform > o.args[i].deform) return +1;
216 if (args[i].deform < o.args[i].deform) return -1;
217 }
218 const int cmp = z.compare(o.z);
219 if (cmp > 0) return +1;
220 if (cmp < 0) return -1;
221 return tau.compare(o.tau);
222}
223
225{
227 return *this;
228
229 // Check if all kernels are {0,0}
230 for (size_t i = 0; i < nargs; i++)
231 if (args[i].ni != 0)
232 break;
233 else if (i == nargs-1)
234 return pow(z,nargs) / factorial(nargs);
235
236 // Check if all kernels are equal
237 if (nargs > 1) {
238 bool all_kernels_equal = true;
239 for (size_t i = 1; i < nargs && all_kernels_equal; i++)
240 all_kernels_equal &= args[i].ni == args[0].ni && args[i].zi == args[0].zi;
241 if (all_kernels_equal)
242 return pow(Gt(std::vector<kernel>(1,args[0]),z,tau),nargs) / factorial(nargs);
243 }
244
245 return this->hold();
246}
247
249{
250 // If all arguments are numeric, evaluate Gt
251 bool all_numeric = is_a<numeric>(z) && is_a<numeric>(tau);
252 for (const kernel& k : args)
253 all_numeric &= is_a<numeric>(k.zi);
254 if (all_numeric)
255 return evaluate();
256
257 // Otherwise, evaluate arguments, as much as possible
258 bool already_evaluated = true;
259 std::vector<kernel> new_args;
260 new_args.reserve(nargs);
261 for (const kernel& k : args) {
262 new_args.emplace_back(k.ni, k.zi.evalf(), k.deform);
263 already_evaluated &= are_ex_trivially_equal(k.zi, new_args.back().zi);
264 }
265 const ex nz = z.evalf();
266 const ex ntau = tau.evalf();
267 already_evaluated &= are_ex_trivially_equal(z, nz);
268 already_evaluated &= are_ex_trivially_equal(tau, ntau);
269
270 if (already_evaluated)
271 return *this;
272 return dynallocate<Gt>(new_args, nz, ntau);
273}
274
275ex Gt::subs(const exmap & m, unsigned options) const
276{
277 std::vector<kernel> args_subs;
278 args_subs.reserve(nargs);
279 for (const kernel& k : args)
280 args_subs.emplace_back(k.ni, k.zi.subs(m, options), k.deform);
281 return Gt(args_subs, z.subs(m, options), tau.subs(m, options));
282}
283
284
285unsigned Gt::calchash() const
286{
287 // adapted from basic::calchash()
288 unsigned v = make_hash_seed(typeid(*this));
289 for (const kernel& k : args) {
290 v = rotate_left(v) ^ numeric{k.ni}.gethash();
291 v = rotate_left(v) ^ k.zi.gethash();
292 v = rotate_left(v) ^ numeric{k.deform}.gethash();
293 }
294 v = rotate_left(v) ^ z.gethash();
295 v = rotate_left(v) ^ tau.gethash();
296
299 hashvalue = v;
300 }
301
302 return v;
303}
304
305bool Gt::has(const ex & other, unsigned options) const
306{
307 for (const kernel& k : args)
308 if (k.zi.has(other, options))
309 return true;
310 return tau.has(other, options) || z.has(other, options);
311}
312
313bool Gt::match(const ex & pattern, exmap& repl_lst) const
314{
315 if (typeid(*this) != typeid(ex_to<basic>(pattern)))
316 return false;
317 throw std::logic_error("Gt::match not implemented\n");
318}
319
320
321// Shift the zi by 1 and/or tau, such that they lay in the fundamental domain
322ex Gt::zisToFundamental(const ex* points) const
323{
324 if (DEBUG_GT) std::cout << "zisToFundamental " << *this << std::endl;
325
326 // When its z argument gets shifted by tau, each integration kernel turns into a linear combination of kernels
327 // g^(n)(z + a*tau, tau) = sum_{k=0}^n (-2pi i a)^k g^(n-k)(z,tau); a ∈ Z
328
329 // First, we loop over all the integrations and build a list of the terms in this sum
330 // Then we construct Gt corresponding to all possible combinations of terms from these lists
331
332 // Step 1:
333 // For each integration, we need to
334 // a) Shift zi, to the fundemantal domain
335 // b) Depending on this shift express the kernel as a linear combination of shifted kernels
336
337 std::vector<ex> zis_shifted;
338 zis_shifted.reserve(nargs);
339 const numeric ntau = to_numeric(tau, points);
340
341 // Shifted kernels
342 // This is a vector (for each integration) of vectors (for each term) of shifted kernels
343 // A shifted kernel is represented by its prefactor and ni
344 // We do not need zi, as it is identical for all shifted kernels of a given integration
345 std::vector<std::vector<std::pair<ex, size_t>>> shifted_kernels;
346 shifted_kernels.reserve(nargs);
347
348 size_t nterms = 1;
349 for (size_t i = 0; i < nargs; i++) {
350 // a) Each zi can be written as a linear combination of 1 and tau, with Im(tau) > 0
351 const numeric& nzi = to_numeric(args[i].zi, points);
352 // zi = A-round(B)*Re(tau) + i B Im(tau), A,B ∈ R
353 // we want to shift A,B ∈ [-0.5,0.5)
354 // For this, we need to shift A and B by -floor(A+0.5) and -floor(B+0.5) respectively, where floor(..) rounds towards negative infinity
355 const numeric B = nzi.imag() / ntau.imag();
356 const cln::cl_I shiftB = -cln::floor1(cln::realpart((B + 0.5).to_cl_N()));
357 const numeric A = nzi.real() + numeric{shiftB}*ntau.real();
358 const cln::cl_I shiftA = -cln::floor1(cln::realpart((A + 0.5).to_cl_N()));
359 const ex zi_shifted = args[i].zi + numeric{shiftA} + numeric{shiftB}*tau;
360 if (to_numeric(zi_shifted, points).is_zero())
361 zis_shifted.push_back(0);
362 else
363 zis_shifted.push_back(zi_shifted.normal());
364
365 // b) List of shifted kernels resulting from current integration
366 shifted_kernels.emplace_back();
367 std::vector<std::pair<ex, size_t>>& shifted_kernels_i = shifted_kernels.back();
368
369 // no shift in tau -> kernel stays unchanged
370 if (cln::zerop(shiftB)) {
371 shifted_kernels_i.reserve(1);
372 shifted_kernels_i.emplace_back(1, args[i].ni);
373 continue;
374 }
375
376 // shift in tau -> use formula from above
377 shifted_kernels_i.reserve(args[i].ni + 1);
378 for (size_t k = 0; k <= args[i].ni; k++)
379 shifted_kernels_i.emplace_back(pow(-2*Pi*I*numeric{shiftB},k)/factorial(k), args[i].ni-k);
380 nterms *= args[i].ni + 1;
381 }
382
383 // Step 2:
384 // Take all possible combinations of exactly one shifted kernel from each integration and build Gts out of them
385 // Use a recursive lambda function to implement this nargs-dimensional outer product
386 std::vector<ex> result;
387 result.reserve(nterms);
388 std::function<void(size_t,ex,std::vector<kernel>&)> build_Gts = [&](size_t i, ex factor, std::vector<kernel>& new_args) -> void {
389 if (i < nargs) {
390 // If we have not yet considered all integrations, call this function recursively with all possible shifted kernels from the current integration added to the list of arguments
391 new_args.emplace_back(-1, zis_shifted[i], args[i].deform);
392 for (size_t k = 0; k < shifted_kernels[i].size(); k++) {
393 new_args.back().ni = shifted_kernels[i][k].second;
394 build_Gts(i+1, factor * shifted_kernels[i][k].first, new_args);
395 }
396 new_args.pop_back();
397 }
398 else {
399 // Once we have considered all the integrations, build the Gt and add it to the output
400 result.emplace_back(factor * Gt(new_args, z, tau));
401
402 const numeric nz = to_numeric(z, points);
403 for (const kernel& k : new_args)
404 if (k.ni == 1 && to_numeric(k.zi, points) == nz)
405 throw std::logic_error((std::stringstream{} << "Gt: pole coincides with endpoint: " << Gt(new_args, z, tau)).str());
406 }
407 };
408 // Start looping over the first integration
409 std::vector<kernel> new_args;
410 new_args.reserve(nargs);
411 build_Gts(0, ex{1}, new_args);
412
413 return add(result);
414}
415
416// Use shuffle regularisation to isolate Gt({{1,0}},z,tau)
417ex Gt::regularise(const ex* points) const
418{
419 if (DEBUG_GT) std::cout << "regularise " << *this << std::endl;
420
421 // No need to regularise
422 if (nargs == 1)
423 return *this;
424
425 size_t n; // number of {1,0} kernels at the end of the argument list
426 for (n = 0; n < nargs; n++)
427 if (args[nargs-1-n].ni != 1 || !to_numeric(args[nargs-1-n].zi, points).is_zero())
428 break;
429
430 // No regularisation necessary
431 if (n == 0)
432 return *this;
433
434 // Special case where all letters need regularisation
435 const Gt singular{std::vector<kernel>(1,kernel{1,0}),z,tau};
436 if (n == nargs)
437 return pow(singular,nargs)/factorial(nargs);
438
439 // Special case where all but one letter need regularisation
440 if (n == nargs-1) {
441 std::vector<ex> result;
442 result.reserve(nargs);
443 for (size_t i = 0; i < nargs; i++) {
444 std::vector<kernel> new_args;
445 new_args.reserve(i+1);
446 for (size_t j = 0; j < i; j++)
447 new_args.emplace_back(1,0);
448 new_args.emplace_back(args[0].ni, args[0].zi);
449 result.emplace_back(pow(-1,i) * Gt(new_args,z,tau) * pow(singular,n-i)/factorial(n-i));
450 }
451 return add(result);
452 }
453
454 // General case
455 // let w = a b c
456 // where a .. any combination of letters
457 // b .. a single letter that should not be regularised
458 // c .. a combination of n letters that should be regularised
459 // then (lemma 3.2.5 of arXiv:1506.07243)
460 // w = sum_{i=0}^n (-1)^i shuffle( shuffle( a, c_i .. c_1 ) b, c_{i+1} .. c_n )
461 // in the present case, c_i = {1,0}, such that this formula simplifies to
462 // w = sum_{i=0}^n (-1)^i Gt[shuffle( a, {1,0}^i ) b] * Gt[{1,0}]^(n-i)/(n-i)!
463
464 const size_t na = nargs-n-1;
465 const std::vector<kernel> a(args.begin(), args.begin() + na);
466 const kernel& b = args[na];
467 std::vector<kernel> ci; // ci = {1,0}^i
468 ci.reserve(n + 1);
469 std::vector<ex> result;
470 for (size_t i = 0; i <= n; i++) {
471 if (i)
472 ci.emplace_back(1,0);
473
474 multi_iterator_shuffle<kernel> shuffle{a, ci};
475 for (shuffle.init(); !shuffle.overflow(); shuffle++) {
476 std::vector<kernel> new_args;
477 new_args.reserve(na + i);
478 for (const kernel& k : shuffle.get_vector())
479 new_args.emplace_back(k);
480 new_args.emplace_back(b);
481 result.emplace_back(pow(-1,i) * Gt(new_args,z,tau) * pow(singular,n-i)/factorial(n-i));
482 }
483 }
484
485 return add(result);
486}
487
488// Find moebius tranform that shifts tau to fundamental domain
490{
491 numeric ntau = to_numeric(tau, points);
492 if (!ntau.imag().is_positive())
493 throw std::runtime_error("Gt: Im(tau) <= 0");
494 matrix transform{{1,0},{0,1}};
495
496 bool loop;
497 do {
498 loop = false;
499
500 // if |Re(tau)| > 0.5, shift tau by -round(Re(tau)), where round(..) rounds to the nearest integer
501 const cln::cl_I shift{-round1(cln::realpart(ntau.to_cl_N()))};
502 if (!cln::zerop(shift)) {
503 transform = matrix{{1,numeric{shift}},{0,1}}.mul(transform);
504 ntau += numeric{shift};
505 loop = true;
506 }
507
508 // Mirror tau on unit circle
509 // Slightly deviate from definition in paper, to avoid introducing extra terms without improving convergence
510 // (abs(ntau) < 1 || (abs(ntau) == 1 && !ntau.real().is_positive()))
511 if (abs(ntau) < 1) {
512 ntau = -1/ntau;
513 transform = matrix{{0,-1},{1,0}}.mul(transform);
514 loop = true;
515 }
516 } while (loop);
517
518 return transform;
519}
520
521// Apply moebius transform to shift tau
522ex Gt::tauToFundamental(const ex* points) const
523{
524 if (DEBUG_GT) std::cout << "tauToFundamental " << *this << std::endl;
525
526 // Find the Moebius transform that moves tau to the fundamental domain
527 // The zis and z are transformed with the inverse transformation matrix
528 const matrix transform = findMoebiusTransform(points);
529 if (DEBUG_GT>=5) std::cout << "transform " << transform << std::endl;
530 const ex& a = transform(0,0), b = transform(0,1), c = transform(1,0), d = transform(1,1);
531 const ex& ci = -c, di = a; // parameters of the inverse transformation (det=1)
532 const ex newTau = ((a*tau+b)/(c*tau+d)).normal(), newTau1 = (ci*newTau+di).normal();
533
534 if (nargs == 1 && args[0].ni == 1 && to_numeric(args[0].zi, points).is_zero())
535 return Gt(std::vector<kernel>(1,kernel{1,0}), z*newTau1, newTau) - log(newTau1) + I*Pi*ci/newTau1 * pow(z*newTau1,2);
536
537 // Helper classes for integrating Gts
538 struct integration_result {
539 ex prefactor; // independent of integration variable
540 size_t power; // power of integration variable
541 std::vector<kernel> args; // Gt args; to not store z or tau, as they come from context
542
543 integration_result(ex prefactor, size_t power, std::vector<kernel> args)
544 : prefactor(prefactor), power(power), args(args) {}
545
546 ex evaluate(const ex upper_bound, const ex& tau) {
547 return prefactor * pow(upper_bound, power) * (args.empty() ? ex{1} : Gt(std::move(args), upper_bound, tau));
548 }
549 };
550 struct integrand_term {
551 ex prefactor; // independent of integration variable
552 size_t power; // power of integration variable
553 kernel kern; // unintegrated kernel
554 std::vector<kernel> args; // Gt args; to not store z or tau, as they come from context
555
556 integrand_term(ex prefactor, size_t power, kernel kern, std::vector<kernel> args)
557 : prefactor(prefactor), power(power), kern(kern), args(args) {}
558
559 void integrate(std::vector<integration_result>& result) { // append result to vector
560 if (args.empty() && kern.ni==0) // Neither Gt nor kernel -> integrate monomial
561 result.emplace_back(prefactor/(power+1), power+1, std::vector<kernel>{});
562 else {
563 args.reserve(args.size() + 1 + power);
564 args.emplace(args.begin(), kern); // New args: append kernel or {0,0} to previous arglist
565 result.emplace_back(prefactor,power,args);
566 while (power > 0) { // Integration by parts -> iterate the above
567 prefactor *= -int(power--);
568 args.emplace(args.begin(), kernel{0,0});
569 result.emplace_back(prefactor,power,args);
570 }
571 }
572 };
573 };
574
575 std::vector<integration_result> result{1,integration_result{1,0,std::vector<kernel>{}}}; // = 1
576 for (size_t i = 0; i < nargs; i++) {
577 // Loop over integration kernels (starting from innermost integration)
578 const size_t ni = args[nargs - i - 1].ni;
579 const ex zi_scaled = args[nargs - i - 1].zi * newTau1;
580 // Build integrand, consisting of transformation of current kernel and of result from previous integration
581 size_t num_terms = 0;
582 std::vector<integrand_term> integrand;
583 if (ci == 0) {
584 // Special case where c=0 -> only k=0 term remains -> replace ci^k by 1
585 for (const integration_result& prev_result : result) {
586 const kernel kern{ni, zi_scaled, args[nargs - i - 1].deform};
587 integrand.emplace_back(prev_result.prefactor/newTau1, prev_result.power, kern, prev_result.args);
588 num_terms += 1 + prev_result.power * (kern.ni && !prev_result.args.empty());
589 }
590 }
591 else {
592 // General case:
593 // g^(n)(z, tau) = sum_{k=0}^n (2PiI c z (c*newTau+d))^k (c*newTau+d)^(n-k) g^(n-k)(z (c*newTau+d), newTau) / k!
594 // Note: we have an additional (c*newTau+d)^-1, from the jacobian of the change of variables dw -> dw'
595 integrand.reserve(result.size()*(ni+1)*(ni+2)/2);
596 for (size_t k = 0; k <= ni; k++) {
597 const kernel kern{ni - k, zi_scaled, args[nargs - i - 1].deform};
598 for (size_t j = 0; j <= k; j++) {
599 const ex prefactor = pow(2*Pi*I*ci,k)*binomial(k,j)*((zi_scaled.is_zero()&&j==0)?1:pow(-zi_scaled,j))*pow(newTau1,int(ni-k)-1)/factorial(k);
600 const size_t power = k-j;
601 for (const integration_result& prev_result : result) {
602 integrand.emplace_back(prev_result.prefactor*prefactor, prev_result.power+power, kern, prev_result.args);
603 num_terms += 1 + (prev_result.power+power) * (kern.ni && !prev_result.args.empty());
604 }
605 }
606 }
607 }
608 // Do the integration
609 result.clear();
610 result.reserve(num_terms);
611 for (integrand_term& term : integrand)
612 term.integrate(result);
613 }
614
615 std::vector<ex> result_ex;
616 result_ex.reserve(result.size());
617 for (integration_result& term : result)
618 result_ex.emplace_back(term.evaluate(newTau1*z, newTau));
619 return add(result_ex);
620}
621
622// Cut integration path into equidistant segements, such that end points are in fundamental domain
623std::vector<ex> Gt::decomposeIntegrationPath(const ex* points) const
624{
625 if (DEBUG_GT) std::cout << "cutIntegrationPath " << *this << std::endl;
626
627 const numeric nz = to_numeric(z, points);
628 const numeric ntau = to_numeric(tau, points);
629
630 // z = A + i B Im(tau), A,B ∈ R
631 const numeric A = nz.real();
632 const numeric B = nz.imag() / ntau.imag();
633
634 const double nA = A.to_double();
635 const double nB = B.to_double();
636
637 // How close to the boundaries of the fundamental domain (at 0.5) to do the cut
638 const double max_A = cutIntegrationPath_threshold_real;
639 const double max_B = cutIntegrationPath_threshold_imag;
640
641 // Test if number differs by any of the zis by an integer multiple of 1 or tau
642 auto coincidesWithZi = [&](const ex& e) {
643 for (const kernel& k : args) {
644 const numeric diff = to_numeric(k.zi - e, points);
645 // z = A + B tau; A,B ∈ R
646 const numeric B = diff.imag() / ntau.imag();
647 const numeric A = diff.real() - ntau.real()*B;
648 if (B.is_integer() && A.is_integer())
649 return true;
650 }
651 return false;
652 };
653
654 // Distribute cuts evenly along the path of integration
655 std::vector<ex> cuts;
656 size_t nCuts = std::max<size_t>(std::abs(nA) / max_A, std::abs(nB) / max_B);
657 while (true) {
658 cuts.reserve(nCuts + 2);
659 cuts.emplace_back(ex{0});
660 for (size_t i = 1; i < nCuts+1; i++) {
661 cuts.emplace_back(i*z/(nCuts+1));
662 if (coincidesWithZi(cuts.back())) {
663 // If any of the cut boundaries coincides with any of the zis,
664 // increase number of cuts and try again (should be very rare)
665 nCuts++;
666 cuts.clear();
667 break;
668 }
669 }
670 if (cuts.empty())
671 continue;
672 cuts.emplace_back(z);
673 break;
674 }
675
676 return cuts;
677}
678
679// Write as combination of eMPLs wit straight integration paths
680ex Gt::applyIntegrationPath(const std::vector<ex>& endpoints) const
681{
682 return Gt_detail::deconcatenate_path<kernel>(args, endpoints, [this](std::vector<kernel>& new_args, const ex& start, const ex& end) -> ex {
683 for (kernel& k: new_args)
684 k.zi -= start;
685 return Gt(new_args, end - start, tau);
686 });
687}
688
689// Variable transformation z -> w, keep track of intergration path, expand kernels
690numeric Gt::qExpand(const ex* points) const
691{
692 if (DEBUG_GT) std::cout << "qExpand " << *this << std::endl;
693
694 const numeric nz = to_numeric(z, points);
695 const numeric ntau = to_numeric(tau, points);
696 std::vector<numeric> nzis, nqis;
697 nzis.reserve(nargs);
698 nqis.reserve(nargs);
699 for (size_t i = 0; i < nargs; i++) {
700 nzis.emplace_back(to_numeric(args[i].zi, points));
701 nqis.emplace_back(to_numeric(exp(2*Pi*I*nzis.back())));
702 }
703 const numeric qtau = to_numeric(exp(2*Pi*I*ntau));
704 const numeric qz = to_numeric(exp(2*Pi*I*nz));
705
706 if (!ntau.imag().is_positive())
707 throw std::runtime_error("Gt: Cannot perform q-expansion if Im(tau) <= 0");
708
709 // Special case of Gt({{1,0}},z,tau)
710 const bool singular = (nargs == 1 && args[0].ni == 1 && args[0].zi.is_zero());
711
712 std::vector<std::pair<numeric,int>> path;
713 path.reserve(nargs);
714
715 // Original integration path is 0->z. Need to ensure that we pass on correct side of poles
716 // For every pole of the integrand, zi, find where the integration path passes it and add
717 // a node to the path
718 // Afterwards, integration path is 0->path[0]->path[1]->...->z (which is still a straight line)
719 if (nz.real().is_positive()) {
720 for (size_t i = 0; i < nargs; i++) {
721 if (args[i].ni != 1) continue; // kernel not singular
722 if (nzis[i].real() <= 0 || nzis[i].real() >= nz.real()) continue; // Re(zi) outside integration path
723 const numeric n1 = nzis[i].imag()*nz.real(), n2 = nzis[i].real()*nz.imag();
724
725 // wi is on the contour -> find out in which direction to deform contour
726 if (abs(n1-n2) < abs(n1+n2)*pow(10,-Digits)) { // Fuzzy compare to account for floating point errors
727 if (args[i].deform >= 0) // add node only if shifting outward (deform=0 defaults to +1 -> include node)
728 path.emplace_back(nzis[i].real(), -1);
729 continue;
730 }
731
732 // wi would be outside of the contour -> ignore this pole, as we integrate along the correct side by default
733 if (n1 < n2)
734 continue;
735
736 // wi is strictly inside the contour -> add a node to the contour
737 if (n1 > n2)
738 path.emplace_back(nzis[i].real(), 0);
739 }
740 std::sort(path.begin(), path.end()); // regular sort
741 path.erase(std::unique(path.begin(), path.end()), path.end()); // delete duplicates
742 }
743 else if (nz.real().is_negative()) {
744 for (size_t i = 0; i < nargs; i++) {
745 if (args[i].ni != 1) continue; // kernel not singular
746 if (nzis[i].real() <= nz.real() || 0 <= nzis[i].real()) continue; // Re(zi) outside integration path
747 const numeric n1 = nzis[i].imag()*nz.real(), n2 = nzis[i].real()*nz.imag();
748
749 // wi is on the contour -> find out in which direction to deform contour
750 if (abs(n1-n2) < abs(n1+n2)*pow(10,-Digits)) { // Fuzzy compare to account for floating point errors
751 if (args[i].deform < 0) // add node only if shifting outward (deform=0 defaults to +1 -> omit node)
752 path.emplace_back(nzis[i].real(), -1);
753 continue;
754 }
755
756 // wi would be outside of the contour -> ignore this pole, as we integrate along the correct side by default
757 if (n1 > n2)
758 continue;
759
760 // wi is strictly inside the contour -> add a node to the contour
761 if (n1 < n2)
762 path.emplace_back(nzis[i].real(), 0);
763 }
764 std::sort(path.rbegin(), path.rend()); // reverse sort
765 path.erase(std::unique(path.begin(), path.end()), path.end()); // delete duplicates
766 }
767
768 // Change of variables: z -> w=exp(2*Pi*I*z)
769 // -> integration path changes from 0->z to 1->w
770 std::vector<ex> wpath;
771 wpath.reserve(path.size() + 2);
772 wpath.emplace_back(1);
773 for (size_t i = 0; i < path.size(); i++)
774 wpath.emplace_back(pow(qz,path[i].first/nz.real()));
775 wpath.emplace_back(qz);
776
777 // Handle poles on the integration contour by deforming contour inward or outward
778 for (size_t i = 0; i < path.size(); i++) {
779 if (path[i].second == 0) // Not on integration path
780 continue;
781 if (path[i].second > 0) // This does not happen, as we omit the node in this case
782 // If we want to integrate closer to the origin, half the imaginary part of the node.
783 wpath[i+1] -= I*ex_to<numeric>(wpath[i+1].evalf()).imag()/2;
784 else if (path[i].second < 0) {
785 // If we want to integrate further away from the origin, move the contour in positive imaginary direction.
786 // The maximum value we can move depends on the previous and following fixed points and the poles in between.
787 // Shifting only the imaginary part ensures that the order along the real axis remains unchanged.
788 const numeric prev_fix_point = ex_to<numeric>(wpath[i].evalf());
789 const numeric current_point = ex_to<numeric>(wpath[i+1].evalf());
790 const numeric next_fix_point = ex_to<numeric>(wpath[i+2].evalf());
791 numeric shift = current_point.imag();
792
793 // Iterate over all poles; if they are inbetween the current and an adjacent fixed point, they constrain
794 // the maximum shift that is allowed without crossing a pole
795 for (size_t j = 0; j < nargs; j++) {
796 if (args[j].ni != 1)
797 continue;
798 const numeric q = nqis[j];
799 if (nz.real().is_positive() && q.imag().is_negative()) continue;
800 if (nz.real().is_negative() && q.imag().is_positive()) continue;
801 if (abs(q.real()-current_point.real()) < abs(q.real()+current_point.real())*pow(10,-Digits)) // fuzzy compare
802 continue; // ignore current node
803 if (q.real() < std::min(prev_fix_point.real(), next_fix_point.real()) || q.real() > std::max(prev_fix_point.real(), next_fix_point.real()))
804 continue; // find poles in-between current node and adjacent nodes
805 // Construct straight line through adjacent nodes and pole under consideration. The point where it intersects the vertical line passing through the current node,
806 // is the furthest point to which we may shift the node.
807 if (nz.real().is_positive()) {
808 if (q.real() < current_point.real())
809 shift = std::min(shift, (next_fix_point.imag()-current_point.imag()) + (next_fix_point.real()-current_point.real()) / (q.real()-current_point.real()) * (q.imag()-current_point.imag()));
810 else
811 shift = std::min(shift, (prev_fix_point.imag()-current_point.imag()) + (prev_fix_point.real()-current_point.real()) / (q.real()-current_point.real()) * (q.imag()-current_point.imag()));
812 }
813 else if (nz.real().is_negative()) {
814 if (q.real() < current_point.real())
815 shift = std::max(shift, (next_fix_point.imag()-current_point.imag()) + (next_fix_point.real()-current_point.real()) / (q.real()-current_point.real()) * (q.imag()-current_point.imag()));
816 else
817 shift = std::max(shift, (prev_fix_point.imag()-current_point.imag()) + (prev_fix_point.real()-current_point.real()) / (q.real()-current_point.real()) * (q.imag()-current_point.imag()));
818 }
819 }
820 // Shift by half the maximum allowed value
821 wpath[i+1] += I*shift/2;
822 }
823 }
824
825 // To do the expansion, we pick which kernel(s) we want to expand and how many orders we want to add.
826 // We start with the innermost kernel that gets new orders. The construct the new terms and store them
827 // in the expansion variable. We multiply those terms by the result of the previous integration. This gives
828 // us the new terms in the integrand.
829 // We then integrate those terms and add them to the corresponding result variable. We take only the terms
830 // we just added and multiply them by all the expansion terms of the next kernel (and possibly by new terms
831 // added to that expansion). This gives us the integrand of the next integration.
832 // We iterate until the outermost integration. At this point we have updated all variables and also obtain
833 // all the terms that arise from the current expansion step (they can be used to test for convergence).
834 using Term = Gt_detail::pathintegral_term; // a single term that can be integrated
835 using Terms = Gt_detail::pathintegral_term_list; // a sum of terms
836 std::vector<size_t> orders(nargs,0); // For each kernel, how many orders of q-expansion have been performed
837 std::vector<Terms> expansion(nargs); // For each kernel, the terms of its q-expansion
838 std::vector<Terms> results(nargs+1); // For each integration, the intermediate integration result (nargs+1 to have a starting value before innermost integration)
839 results.rbegin()->add(1,0); // Initialise the input of the innermost integration to 1
840
841 // Evaluate integration result numerically; apply global factor (2*Pi*I)^(-nargs) (from Jacobian of z->w)
842 const numeric norm = to_numeric(pow(2*Pi*I, nargs).evalf());
843 auto evaluate_numeric = [&](const Terms& terms) -> numeric {
844 numeric result = 0;
845 for (const Term& term : terms)
846 result += to_numeric(nqexand_transformer(term.evaluate(qz, wpath)));
847 return result / norm;
848 };
849
850 // Helper function to compute sum_{n=1}^inf q^n n^k = Li(-k,q)
851 auto LiNegIntOrd = [](const ex& q, const size_t k) -> ex {
852 if (k == 0)
853 return q / (1 - q);
854 std::vector<ex> result;
855 result.reserve(k);
856 for (size_t i = 1; i <= k; i++) {
857 std::vector<ex> en;
858 en.reserve(i);
859 for (size_t j = 0; j < i; j++)
860 en.emplace_back(pow(-1, j) * binomial(k + 1, j) * pow(i - j, k));
861 result.emplace_back(pow(q,i) * add(en));
862 }
863 return add(result) / pow(1 - q, k + 1);
864 };
865
866 // Perform or refine the q-expansion of a given kernel, by adding one or mode additional orders in q
867 // Perform all subsequent integrations of the new terms and update intermediate data structures
868 // Return the numerical difference to the final integration result that resulted from the additional expansion
869 constexpr size_t ALL_KERNELS = std::numeric_limits<size_t>::max(); // special argument to simultaneously expand all kernels
870 auto expand_and_integrate_kernel = [&](const size_t kernel_index, const size_t num_extra_orders) -> numeric {
871 Terms new_result; // New terms added to previous integration (initially none)
872 // Start iterating from the innermost integration that receives new expansion terms
873 for (size_t i = (kernel_index==ALL_KERNELS ? 0 : kernel_index); i < nargs; i++) {
874 const size_t idx = nargs - 1 - i; // Innermost integration comes last in lists
875
876 Terms new_terms; // New terms of expansion of current kernel
877 if (kernel_index == ALL_KERNELS || kernel_index == i) { // further expand current kernel
878 const size_t ni = args[idx].ni;
879 const ex& zi = nzis[idx];
880 const ex& qi = nqis[idx];
881 const size_t order_min = (orders[idx] + 1) - 1;
882 const size_t order_max = (orders[idx] += num_extra_orders) - 1;
883
884 if (ni == 0) {
885 if (order_min == 0)
886 new_terms.add(1, 0);
887 } else {
888 if (order_min == 0) {
889 if (ni == 1) {
890 new_terms.add(I*Pi, 0);
891 if (!singular) // In case of {1,0} kernel, add -2*Pi*I/(w-1) -> effectively omit this term
892 new_terms.add(2*Pi*I*qi, 0, std::vector<ex>{}, qi);
893 }
894 if (ni%2 == 0)
895 new_terms.add(-2*zeta(ni), 0);
896 }
897
898 const int sign = ni%2 == 0 ? 1 : -1;
899 const ex factor = -pow(2*Pi*I, ni)/factorial(ni-1);
900 for (size_t i = std::max((size_t)1,order_min); i <= order_max; i++) {
901 const ex gSum = LiNegIntOrd(pow(qtau,i), ni-1).evalf();
902 new_terms.add(factor * gSum / pow(qi, i), +i);
903 new_terms.add(factor * gSum * pow(qi, i) * sign, -i);
904 }
905 }
906 }
907
908 Terms new_integrand; // New terms in integrand of current integration
909 // If the previous integration has added new terms, add to the integrand is product of
910 for (const Term& new_integration_result : new_result) // new terms from previous integration
911 for (const Term& expansion_term : expansion[idx]) // existing terms of expansion of current kernel
912 new_integrand.add(expansion_term, new_integration_result, -1);
913 // If we further expanded the current kernel, add to the integrand the product of
914 for (const Term& new_expansion_term : new_terms) // new terms of expansion of current kernel
915 for (const Term& integration_result : results[idx+1]) // previous integration result
916 new_integrand.add(integration_result, new_expansion_term, -1);
917 // All of the above terms are divided by the integration variable, which is the Jacobian of the transformation z->w
918
919 // Store expansion terms for future iterations
920 if (!new_terms.empty() && i) // no need to keep track of innermost expansion
921 expansion[idx].add(new_terms);
922
923 // Do the integration and store results for future iterations
924 new_result.clear();
925 // Integrate terms one at a time. In case new integrands arise from partial integration or partial fractioning,
926 // they are put back into the list of integrand terms, rather than integrating them directly. As a result we can
927 // combine these terms with existing terms in the integrand, such that we only have to integrate each structure
928 // once. Terms are ordered such that more complicated terms come first.
929 while (!new_integrand.empty())
930 new_integrand.pop().integrate(wpath[0], new_integrand, new_result);
931 results[idx].add(new_result);
932 }
933
934 return evaluate_numeric(new_result);
935 };
936
937 // Step 1: Expand all kernels to some starting order
938 // The starting order is estimated for each kernel based on tau, z, and zi
939 const double dz = nz.imag().to_double();
940 for (size_t i = 0; i < nargs; i++) {
941 const size_t idx = nargs - 1 - i; // Innermost integration comes last in lists
942 if (args[idx].ni == 0) {
943 // For {0,0} kernel, expansion has only one term
944 //expand_and_integrate_kernel(i, std::max((size_t)1,qExpand_minOrder));
945 expand_and_integrate_kernel(i, 1);
946 continue;
947 }
948 // Expansion parameter is exp(-2*Pi*Im(tau))
949 // Convergence is hindered by the imaginary parts of z,zi or z-zi being close to Im(tau)
950 // -> effective expansion parameter is x = exp(-2*Pi*(Im(tau)-max(abs(Im(z)),abs(Im(zi)),abs(Im(z-zi)))))
951 // => Need approximately log_x(10^-Digits) orders in expansion
952 const double dzi = nzis[idx].imag().to_double();
953 const double conv = std::max({std::abs(dz),std::abs(dzi),std::abs(dz-dzi)});
954 const size_t estimate = (size_t)to_numeric((-Digits*log(10)/(-2*Pi*(ntau.imag()-conv))).evalf()).to_double();
955 expand_and_integrate_kernel(i, std::max(estimate,qExpand_minOrder));
956 }
957
958 // Step 2: Expand all kernels alternatingly. If the impact of a single kernel's expansion is
959 // smaller than the target accuracy, stop its expansion and continue with remaining kernels.
960 const numeric accuracy_goal = abs(evaluate_numeric(results[0]))*pow(numeric{10},-numeric{Digits});
961 uint64_t iterating = (uint64_t(1) << nargs) - uint64_t(1); // i-th bit represents whether i-th kernel is still being expanded
962 do {
963 for (size_t i = 0; i < nargs; i++) {
964 if (iterating & (1 << i)) {
965 const numeric delta = abs(expand_and_integrate_kernel(i, qExpand_stepSize));
966 iterating ^= (delta < accuracy_goal) << i; // if accuracy reached, turn off corresponding bit
967 }
968 }
969 } while (iterating);
970
971 // Step 3: Expand all kernels together, until the change is smaller than the target accuracy.
972 // This is for the rare case that the expansion of each individual kernel has a small impact,
973 // but the sum of all expansions is larger.
974 numeric delta;
975 do {
976 delta = abs(expand_and_integrate_kernel(ALL_KERNELS, qExpand_stepSize));
977 } while (delta > accuracy_goal);
978
979 if (DEBUG_GT>=5) {
980 std::cout << "qExpand order:";
981 for (size_t i = 0; i < nargs; i++)
982 std::cout << " " << orders[i];
983 std::cout << std::endl;
984 }
985
986 if (singular) // In case of {1,0} kernel, add log(1-w)-log(w)
987 return evaluate_numeric(results[0]) + to_numeric(log(1 - qz).evalf() - 2*Pi*I*z, points);
988
989 return evaluate_numeric(results[0]);
990}
991
992numeric Gt::evaluate(const ex* points) const
993{
994 return ex_evaluate(*this, points);
995}
996
997
998ex Gt::ex_zisToFundamental(const ex& expr, const ex* points)
999{
1000 return apply_function_recursive(expr, [&points](const Gt& Gt) -> ex {
1001 return Gt.zisToFundamental(points);
1002 });
1003}
1004ex Gt::ex_regularise(const ex& expr, const ex* points)
1005{
1006 return apply_function_recursive(expr, [&points](const Gt& Gt) -> ex {
1007 return Gt.regularise(points);
1008 });
1009}
1010ex Gt::ex_tauToFundamental(const ex& expr, const ex* points)
1011{
1012 return apply_function_recursive(expr, [&points](const Gt& Gt) -> ex {
1013 return Gt.tauToFundamental(points);
1014 });
1015}
1016ex Gt::ex_cutIntegrationPath(const ex& expr, const ex* points)
1017{
1018 return apply_function_recursive(expr, [&points](const Gt& Gt) -> ex {
1020 });
1021}
1022numeric Gt::ex_qExpand(const ex& expr, const ex* points)
1023{
1024 return to_numeric(apply_function_recursive(expr, [&points](const Gt& Gt) -> numeric {
1025 return Gt.qExpand(points);
1026 }), points);
1027}
1028
1029// All preparation steps, output is ready for q-expansion
1030ex Gt::ex_prepare(const ex& expr, const ex* points)
1031{
1032 // Move zis to fundamental domain
1033 ex result = ex_zisToFundamental(expr, points);
1034 if (DEBUG_GT>=3) std::cout << "step1: " << result << std::endl;
1035
1036 // Apply regularisation, such that innermost kernel is not {1,0}
1037 result = ex_regularise(result, points);
1038 if (DEBUG_GT>=3) std::cout << "step2: " << result << std::endl;
1039
1040 // Move tau to its fundamental domain
1041 // This shifts the zi -> redo zi shifts and regularisation
1043 result = ex_tauToFundamental(result, points);
1044 if (DEBUG_GT>=4) std::cout << "step3a: " << result << std::endl;
1045 result = ex_zisToFundamental(result, points);
1046 if (DEBUG_GT>=4) std::cout << "step3b: " << result << std::endl;
1047 result = ex_regularise(result, points);
1048 if (DEBUG_GT>=3) std::cout << "step3: " << result << std::endl;
1049 } else
1050 if (DEBUG_GT>=2) std::cout << "(tauToFundamental skipped)" << std::endl;
1051
1052 // Cuts integration path and shift z/zis, such that z is in its fundamental domain
1053 // This shifts the zi (but not to 0) -> redo zi shifts
1054 result = ex_cutIntegrationPath(result, points);
1055 if (DEBUG_GT>=4) std::cout << "step4a: " << result << std::endl;
1056 result = ex_zisToFundamental(result, points);
1057
1058 if (DEBUG_GT>=2) std::cout << "prepared: " << result << std::endl;
1059
1060 return result;
1061}
1062
1063// Full numerical evaluation
1064numeric Gt::ex_evaluate(const ex& expr, const ex* points)
1065{
1066 if (DEBUG_GT>=4) {
1067 std::cout << "input: " << expr;
1068 if (points)
1069 std::cout << "; points=" << *points;
1070 std::cout << std::endl;
1071 }
1072 const ex prepared = ex_prepare(expr, points);
1073 const numeric result = to_numeric(ex_qExpand(prepared, points));
1074 if (DEBUG_GT>=4) std::cout << "evaluated: " << result << std::endl;
1077 return result;
1078}
1079lst Gt::lst_evaluate(const lst& list, const ex* points)
1080{
1081 if (DEBUG_GT>=4) {
1082 std::cout << "input: " << list;
1083 if (points)
1084 std::cout << "; points=" << *points;
1085 std::cout << std::endl;
1086 }
1087 const lst prepared = ex_to<lst>(ex_prepare(list, points));
1088 lst result;
1089 for (size_t i = 0; i < prepared.nops(); i++)
1090 result.append(to_numeric(ex_qExpand(prepared.op(i), points)));
1091 if (DEBUG_GT>=4) std::cout << "evaluated: " << result << std::endl;
1094 return result;
1095}
1096
1097
1098// Convenience wrappers that take references instead of pointers
1099ex Gt::ex_zisToFundamental(const ex& expr, const ex& points) {return ex_zisToFundamental(expr, &points); };
1100ex Gt::ex_regularise(const ex& expr, const ex& points) {return ex_regularise(expr, &points); };
1101ex Gt::ex_tauToFundamental(const ex& expr, const ex& points) {return ex_tauToFundamental(expr, &points); };
1102ex Gt::ex_cutIntegrationPath(const ex& expr, const ex& points) {return ex_cutIntegrationPath(expr, &points); };
1103numeric Gt::ex_qExpand(const ex& expr, const ex& points) {return ex_qExpand(expr, &points); };
1104ex Gt::ex_prepare(const ex& expr, const ex& points) {return ex_prepare(expr, &points); };
1105numeric Gt::ex_evaluate(const ex& expr, const ex& points) { return ex_evaluate(expr, &points); }
1106lst Gt::lst_evaluate(const lst& list, const ex& points) { return lst_evaluate(list, &points); }
1107
1108
1109// Apply function to every Gt in an expression
1110ex Gt::apply_function_recursive(const ex& input, const std::function<ex(const Gt&)>& func)
1111{
1112 return Gt_detail::TransformExpressionWithCache<Gt>([&func](const ex& expr) -> ex { return func(ex_to<Gt>(expr)); })(input);
1113}
1114
1116{
1117 nqexand_transformer.clear_cache();
1118}
1119
1120
1121// Evaluate expression numerically, throw error if not possible
1122// Optionally take a list of replacement rules
1123numeric Gt::to_numeric(const ex& expr, const ex* points)
1124{
1125 // Substitute points twice, in case some of the zis are given in terms of tau
1126 const ex replaced = (points ? expr.subs(*points).subs(*points) : expr).evalf();
1127
1128 if (is_a<numeric>(replaced))
1129 return ex_to<numeric>(replaced);
1130
1131 throw std::invalid_argument((std::stringstream{} << "Gt: argument '" << expr << "' is not numeric, evaluated to '" << replaced << "'").str());
1132}
1133long Gt::to_integer(const ex& expr, const bool allow_negative)
1134{
1135 if (is_a<numeric>(expr)) {
1136 const numeric n = ex_to<numeric>(expr);
1137 if (allow_negative ? n.is_integer() : n.is_nonneg_integer())
1138 return n.to_long();
1139 }
1140
1141 throw std::invalid_argument((std::stringstream{} << "Gt: argument '" << expr << "' is not " << (allow_negative ? "an" : "a non-negative") << " integer").str());
1142}
1143
1146size_t Gt::qExpand_minOrder = 4;
1147size_t Gt::qExpand_stepSize = 4;
1148bool Gt::auto_clear_polylog_cache = false;
1149bool Gt::enable_tauToFundamental = true;
1151{
1152 // Clear cache when precision target increases
1153 Digits.add_callback([](long change) -> void {
1154 if (change>0)Gt::clear_polylog_cache();
1155 });
1156 return Gt_detail::TransformExpressionWithCache<G2_SERIAL>{
1157 [](const ex& obj) {return obj.evalf();}, // evaluate polylogs (cached)
1158 [](const ex& obj) {return obj.evalf();} // evaluate other objects
1159 };
1160}();
1161
1162
1164} // namespace GiNaC
#define DEBUG_GT
Definition Gt.cpp:43
Interface to GiNaC's elliptic multiple polylogarithms.
Interface to helper functions for elliptic multiple polylogarithms.
Interface to GiNaC's sums of expressions.
#define GINAC_ASSERT(X)
Assertion macro for checking invariances.
Definition assertion.h:32
Elliptic Multiple Polylogarithm.
Definition Gt.h:45
std::vector< ex > decomposeIntegrationPath(const ex *points=nullptr) const
Definition Gt.cpp:623
static void clear_polylog_cache()
Definition Gt.cpp:1115
std::vector< kernel > args
Definition Gt.h:166
static ex ex_zisToFundamental(const ex &expr, const ex *points=nullptr)
Definition Gt.cpp:998
static numeric ex_qExpand(const ex &expr, const ex *points=nullptr)
Definition Gt.cpp:1022
bool match(const ex &pattern, exmap &repl_lst) const override
Check whether the expression matches a given pattern.
Definition Gt.cpp:313
ex z
Definition Gt.h:167
static ex ex_prepare(const ex &expr, const ex *points=nullptr)
Definition Gt.cpp:1030
matrix findMoebiusTransform(const ex *points=nullptr) const
Definition Gt.cpp:489
ex subs(const exmap &m, unsigned options=0) const override
Substitute a set of objects by arbitrary expressions.
Definition Gt.cpp:275
static ex apply_function_recursive(const ex &input, const std::function< ex(const Gt &)> &func)
Definition Gt.cpp:1110
static ex ex_cutIntegrationPath(const ex &expr, const ex *points=nullptr)
Definition Gt.cpp:1016
void setArgs(const ex &args)
Definition Gt.cpp:133
static long to_integer(const ex &expr, const bool allow_negative)
Definition Gt.cpp:1133
numeric qExpand(const ex *points=nullptr) const
Definition Gt.cpp:690
unsigned calchash() const override
Compute the hash value of an object and if it makes sense to store it in the objects status_flags,...
Definition Gt.cpp:285
static double cutIntegrationPath_threshold_imag
Definition Gt.h:144
ex eval() const override
Perform automatic non-interruptive term rewriting rules.
Definition Gt.cpp:224
void do_print_latex(const print_latex &c, unsigned level) const
Definition Gt.cpp:193
ex regularise(const ex *points=nullptr) const
Definition Gt.cpp:417
static lst lst_evaluate(const lst &list, const ex *points=nullptr)
Definition Gt.cpp:1079
static ex ex_tauToFundamental(const ex &expr, const ex *points=nullptr)
Definition Gt.cpp:1010
ex evalf() const override
Evaluate object numerically.
Definition Gt.cpp:248
void do_print(const print_context &c, unsigned level) const
Definition Gt.cpp:181
void archive(archive_node &n) const override
Save (a.k.a.
Definition Gt.cpp:166
static Gt_detail::TransformExpressionWithCache< G2_SERIAL > nqexand_transformer
Definition Gt.h:170
void read_archive(const archive_node &n, lst &syms) override
Read (a.k.a.
Definition Gt.cpp:156
static size_t qExpand_minOrder
Definition Gt.h:148
static numeric to_numeric(const ex &expr, const ex *points=nullptr)
Definition Gt.cpp:1123
static bool auto_clear_polylog_cache
Definition Gt.h:158
static bool enable_tauToFundamental
Definition Gt.h:162
ex tauToFundamental(const ex *points=nullptr) const
Definition Gt.cpp:522
ex zisToFundamental(const ex *points=nullptr) const
Definition Gt.cpp:322
static size_t qExpand_stepSize
Definition Gt.h:155
numeric evaluate(const ex *points=nullptr) const
Definition Gt.cpp:992
bool has(const ex &other, unsigned options=0) const override
Test for occurrence of a pattern.
Definition Gt.cpp:305
static numeric ex_evaluate(const ex &expr, const ex *points=nullptr)
Definition Gt.cpp:1064
static double cutIntegrationPath_threshold_real
Definition Gt.h:143
size_t nargs
Definition Gt.h:165
ex applyIntegrationPath(const std::vector< ex > &endpoints) const
Definition Gt.cpp:680
Gt(const ex &args, const ex &z, const ex &tau)
Definition Gt.cpp:108
ex tau
Definition Gt.h:167
static ex ex_regularise(const ex &expr, const ex *points=nullptr)
Definition Gt.cpp:1004
void add_callback(digits_changed_callback callback)
Add a new callback function.
Definition numeric.cpp:2567
Sum of expressions.
Definition add.h:31
add(const ex &lh, const ex &rh)
Definition add.cpp:58
This class stores all properties needed to record/retrieve the state of one object of class basic (or...
Definition archive.h:48
This class is the ABC (abstract base class) of GiNaC's class hierarchy.
Definition basic.h:104
unsigned gethash() const
Definition basic.h:271
const basic & setflag(unsigned f) const
Set some status_flags.
Definition basic.h:287
ex diff(const symbol &s, unsigned nth=1) const
Default interface of nth derivative ex::diff(s, n).
Definition basic.cpp:645
unsigned hashvalue
hash value
Definition basic.h:302
unsigned flags
of type status_flags
Definition basic.h:301
const basic & hold() const
Stop further evaluation.
Definition basic.cpp:886
virtual int compare_same_type(const basic &other) const
Returns order relation between two objects of same type.
Definition basic.cpp:718
virtual ex normal(exmap &repl, exmap &rev_lookup, lst &modifier) const
Default implementation of ex::normal().
Definition normal.cpp:2217
int compare(const basic &other) const
Compare objects syntactically to establish canonical ordering.
Definition basic.cpp:815
Wrapper template for making GiNaC classes out of STL containers.
Definition container.h:72
size_t nops() const override
Number of operands/members.
Definition container.h:117
ex op(size_t i) const override
Return operand/member at position i.
Definition container.h:294
container & append(const ex &b)
Add element at back.
Definition container.h:390
Lightweight wrapper for GiNaC's symbolic objects.
Definition ex.h:72
unsigned gethash() const
Definition ex.h:233
ex normal() const
Normalization of rational functions.
Definition normal.cpp:2518
bool has(const ex &pattern, unsigned options=0) const
Definition ex.h:151
ex evalf() const
Definition ex.h:121
size_t nops() const
Definition ex.h:135
ex subs(const exmap &m, unsigned options=0) const
Definition ex.h:841
int compare(const ex &other) const
Definition ex.h:322
bool is_zero() const
Definition ex.h:213
ex op(size_t i) const
Definition ex.h:136
This class holds one index of an indexed object.
Definition idx.h:35
Symbolic matrices.
Definition matrix.h:37
matrix mul(const matrix &other) const
Product of matrices.
Definition matrix.cpp:586
The class multi_iterator_shuffle defines a multi_iterator, which runs over all shuffles of a and b.
This class is a wrapper around CLN-numbers within the GiNaC class hierarchy.
Definition numeric.h:81
const numeric real() const
Real part of a number.
Definition numeric.cpp:1338
bool is_positive() const
True if object is not complex and greater than zero.
Definition numeric.cpp:1135
bool is_integer() const
True if object is a non-complex integer.
Definition numeric.cpp:1153
bool is_negative() const
True if object is not complex and less than zero.
Definition numeric.cpp:1144
cln::cl_N to_cl_N() const
Returns a new CLN object of type cl_N, representing the value of *this.
Definition numeric.cpp:1331
const numeric imag() const
Imaginary part of a number.
Definition numeric.cpp:1345
const numeric mul(const numeric &other) const
Numerical multiplication method.
Definition numeric.cpp:879
double to_double() const
Converts numeric types to machine's double.
Definition numeric.cpp:1321
bool is_zero() const
True if object is zero.
Definition numeric.cpp:1128
This class holds a two-component object, a basis and and exponent representing exponentiation.
Definition power.h:38
Base class for print_contexts.
Definition print.h:101
Context for latex-parsable output.
Definition print.h:121
@ evaluated
.eval() has already done its job
Definition flags.h:202
@ hash_calculated
.calchash() has already done its job
Definition flags.h:204
Interface to GiNaC's constant types and some special constants.
unsigned options
Definition factor.cpp:2473
vector< int > k
Definition factor.cpp:1434
size_t n
Definition factor.cpp:1431
size_t c
Definition factor.cpp:756
mvec m
Definition factor.cpp:757
Type-specific hash seed.
Interface to GiNaC's initially known functions.
Definition of GiNaC's lst.
Interface to symbolic matrices.
Interface to GiNaC's products of expressions.
Definition add.cpp:35
bool is_zero(const ex &thisex)
Definition ex.h:835
const numeric I
Imaginary unit.
Definition numeric.cpp:1432
const numeric pow(const numeric &x, const numeric &y)
Definition numeric.h:250
std::map< ex, ex, ex_is_less > exmap
Definition basic.h:49
B & dynallocate(Args &&... args)
Constructs a new (class basic or derived) B object on the heap.
Definition basic.h:333
const numeric abs(const numeric &x)
Absolute value.
Definition numeric.cpp:2319
function zeta(const T1 &p1)
Definition inifcns.h:110
bool are_ex_trivially_equal(const ex &e1, const ex &e2)
Compare two objects of class quickly without doing a deep tree traversal.
Definition ex.h:699
const numeric binomial(const numeric &n, const numeric &k)
The Binomial coefficients.
Definition numeric.cpp:2144
print_func< print_dflt >(&diracone::do_print). print_func< print_latex >(&diracone
Definition clifford.cpp:51
const numeric exp(const numeric &x)
Exponential function.
Definition numeric.cpp:1438
const numeric factorial(const numeric &n)
Factorial combinatorial function.
Definition numeric.cpp:2112
const constant Pi("Pi", PiEvalf, "\\pi", domain::positive)
Pi.
Definition constant.h:84
const numeric log(const numeric &x)
Natural logarithm.
Definition numeric.cpp:1449
const numeric real(const numeric &x)
Definition numeric.h:310
_numeric_digits Digits
Accuracy in decimal digits.
Definition numeric.cpp:2590
static unsigned make_hash_seed(const std::type_info &tinfo)
We need a hash function which gives different values for objects of different types.
Definition hash_seed.h:36
unsigned rotate_left(unsigned n)
Rotate bits of unsigned value by one bit to the left.
Definition utils.h:47
ex factor(const ex &poly, unsigned options)
Interface function to the outside world.
Definition factor.cpp:2574
GINAC_IMPLEMENT_REGISTERED_CLASS_OPT_T(lst, basic, print_func< print_context >(&lst::do_print). print_func< print_tree >(&lst::do_print_tree)) template<> bool lst GINAC_BIND_UNARCHIVER(lst)
Specialization of container::info() for lst.
Definition lst.cpp:41
double to_double(const numeric &x)
Definition numeric.h:307
Makes the interface to the underlying bignum package available.
Interface to GiNaC's overloaded operators.
Interface to GiNaC's symbolic exponentiation (basis^exponent).
#define GINAC_IMPLEMENT_REGISTERED_CLASS_OPT(classname, supername, options)
Macro for inclusion in the implementation of each registered class.
Definition registrar.h:183
Interface to relations between expressions.
size_t ni
Definition Gt.h:55
void check()
Definition Gt.cpp:79
Interface to GiNaC's symbolic objects.
Utilities for summing over multiple indices.

This page is part of the GiNaC developer's reference. It was generated automatically by doxygen. For an introduction, see the tutorial.