GiNaC 1.8.10
mul.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, see <https://www.gnu.org/licenses/>.
20 */
21
22#include "mul.h"
23#include "add.h"
24#include "power.h"
25#include "operators.h"
26#include "matrix.h"
27#include "indexed.h"
28#include "lst.h"
29#include "archive.h"
30#include "utils.h"
31#include "symbol.h"
32#include "compiler.h"
33
34#include <limits>
35#include <stdexcept>
36#include <vector>
37
38namespace GiNaC {
39
42 print_func<print_latex>(&mul::do_print_latex).
43 print_func<print_csrc>(&mul::do_print_csrc).
44 print_func<print_tree>(&mul::do_print_tree).
45 print_func<print_python_repr>(&mul::do_print_python_repr))
46
47
48
49// default constructor
51
53{
54}
55
57// other constructors
59
60// public
61
62mul::mul(const ex & lh, const ex & rh)
63{
67}
68
75
82
83mul::mul(const epvector & v, const ex & oc, bool do_index_renaming)
84{
85 overall_coeff = oc;
86 construct_from_epvector(v, do_index_renaming);
88}
89
96
97mul::mul(epvector && vp, const ex & oc, bool do_index_renaming)
98{
99 overall_coeff = oc;
100 construct_from_epvector(std::move(vp), do_index_renaming);
102}
103
104mul::mul(const ex & lh, const ex & mh, const ex & rh)
105{
107 factors.reserve(3);
108 factors.push_back(lh);
109 factors.push_back(mh);
110 factors.push_back(rh);
114}
115
117// archiving
119
121// functions overriding virtual functions from base classes
123
124void mul::print_overall_coeff(const print_context & c, const char *mul_sym) const
125{
126 const numeric &coeff = ex_to<numeric>(overall_coeff);
127 if (coeff.csgn() == -1)
128 c.s << '-';
129 if (!coeff.is_equal(*_num1_p) &&
131 if (coeff.is_rational()) {
132 if (coeff.is_negative())
133 (-coeff).print(c);
134 else
135 coeff.print(c);
136 } else {
137 if (coeff.csgn() == -1)
138 (-coeff).print(c, precedence());
139 else
141 }
142 c.s << mul_sym;
143 }
144}
145
146void mul::do_print(const print_context & c, unsigned level) const
147{
148 if (precedence() <= level)
149 c.s << '(';
150
152
153 bool first = true;
154 for (auto & it : seq) {
155 if (!first)
156 c.s << '*';
157 else
158 first = false;
160 }
161
162 if (precedence() <= level)
163 c.s << ')';
164}
165
166void mul::do_print_latex(const print_latex & c, unsigned level) const
167{
168 if (precedence() <= level)
169 c.s << "{(";
170
172
173 // Separate factors into those with negative numeric exponent
174 // and all others
175 exvector neg_powers, others;
176 for (auto & it : seq) {
177 GINAC_ASSERT(is_exactly_a<numeric>(it.coeff));
178 if (ex_to<numeric>(it.coeff).is_negative())
179 neg_powers.push_back(recombine_pair_to_ex(expair(it.rest, -it.coeff)));
180 else
181 others.push_back(recombine_pair_to_ex(it));
182 }
183
184 if (!neg_powers.empty()) {
185
186 // Factors with negative exponent are printed as a fraction
187 c.s << "\\frac{";
188 mul(others).eval().print(c);
189 c.s << "}{";
190 mul(neg_powers).eval().print(c);
191 c.s << "}";
192
193 } else {
194
195 // All other factors are printed in the ordinary way
196 for (auto & vit : others) {
197 c.s << ' ';
198 vit.print(c, precedence());
199 }
200 }
201
202 if (precedence() <= level)
203 c.s << ")}";
204}
205
206void mul::do_print_csrc(const print_csrc & c, unsigned level) const
207{
208 if (precedence() <= level)
209 c.s << "(";
210
213 c.s << "-";
214 else {
216 c.s << "*";
217 }
218 }
219
220 // Print arguments, separated by "*" or "/"
221 auto it = seq.begin(), itend = seq.end();
222 while (it != itend) {
223
224 // If the first argument is a negative integer power, it gets printed as "1.0/<expr>"
225 bool needclosingparenthesis = false;
226 if (it == seq.begin() && it->coeff.info(info_flags::negint)) {
227 if (is_a<print_csrc_cl_N>(c)) {
228 c.s << "recip(";
229 needclosingparenthesis = true;
230 } else
231 c.s << "1.0/";
232 }
233
234 // If the exponent is 1 or -1, it is left out
235 if (it->coeff.is_equal(_ex1) || it->coeff.is_equal(_ex_1))
236 it->rest.print(c, precedence());
237 else if (it->coeff.info(info_flags::negint))
238 ex(power(it->rest, -ex_to<numeric>(it->coeff))).print(c, level);
239 else
240 ex(power(it->rest, ex_to<numeric>(it->coeff))).print(c, level);
241
242 if (needclosingparenthesis)
243 c.s << ")";
244
245 // Separator is "/" for negative integer powers, "*" otherwise
246 ++it;
247 if (it != itend) {
248 if (it->coeff.info(info_flags::negint))
249 c.s << "/";
250 else
251 c.s << "*";
252 }
253 }
254
255 if (precedence() <= level)
256 c.s << ")";
257}
258
259void mul::do_print_python_repr(const print_python_repr & c, unsigned level) const
260{
261 c.s << class_name() << '(';
262 op(0).print(c);
263 for (size_t i=1; i<nops(); ++i) {
264 c.s << ',';
265 op(i).print(c);
266 }
267 c.s << ')';
268}
269
270bool mul::info(unsigned inf) const
271{
272 switch (inf) {
277 case info_flags::real:
282 case info_flags::even:
285 for (auto & it : seq) {
286 if (!recombine_pair_to_ex(it).info(inf))
287 return false;
288 }
290 return true;
291 return overall_coeff.info(inf);
292 }
296 return true;
298 return true;
300 return false;
301
302 bool pos = true;
303 for (auto & it : seq) {
304 const ex& factor = recombine_pair_to_ex(it);
306 continue;
308 pos = !pos;
309 else
310 return false;
311 }
313 pos = !pos;
315 return (inf == info_flags::positive? pos : !pos);
316 }
319 return true;
320 bool pos = true;
321 for (auto & it : seq) {
322 const ex& factor = recombine_pair_to_ex(it);
324 continue;
326 pos = !pos;
327 else
328 return false;
329 }
330 return (overall_coeff.info(info_flags::negative)? !pos : pos);
331 }
333 case info_flags::negint: {
334 bool pos = true;
335 for (auto & it : seq) {
336 const ex& factor = recombine_pair_to_ex(it);
338 continue;
340 pos = !pos;
341 else
342 return false;
343 }
345 pos = !pos;
347 return false;
348 return (inf ==info_flags::posint? pos : !pos);
349 }
351 bool pos = true;
352 for (auto & it : seq) {
353 const ex& factor = recombine_pair_to_ex(it);
355 continue;
357 pos = !pos;
358 else
359 return false;
360 }
362 pos = !pos;
364 return false;
365 return pos;
366 }
369 return true;
371 return false;
372 for (auto & it : seq) {
373 const ex& term = recombine_pair_to_ex(it);
375 return false;
376 }
378 return true;
379 }
380 }
381 return inherited::info(inf);
382}
383
384bool mul::is_polynomial(const ex & var) const
385{
386 for (auto & it : seq) {
387 if (!it.rest.is_polynomial(var) ||
388 (it.rest.has(var) && !it.coeff.info(info_flags::nonnegint))) {
389 return false;
390 }
391 }
392 return true;
393}
394
395int mul::degree(const ex & s) const
396{
397 // Sum up degrees of factors
398 int deg_sum = 0;
399 for (auto & it : seq) {
400 if (ex_to<numeric>(it.coeff).is_integer())
401 deg_sum += recombine_pair_to_ex(it).degree(s);
402 else {
403 if (it.rest.has(s))
404 throw std::runtime_error("mul::degree() undefined degree because of non-integer exponent");
405 }
406 }
407 return deg_sum;
408}
409
410int mul::ldegree(const ex & s) const
411{
412 // Sum up degrees of factors
413 int deg_sum = 0;
414 for (auto & it : seq) {
415 if (ex_to<numeric>(it.coeff).is_integer())
416 deg_sum += recombine_pair_to_ex(it).ldegree(s);
417 else {
418 if (it.rest.has(s))
419 throw std::runtime_error("mul::ldegree() undefined degree because of non-integer exponent");
420 }
421 }
422 return deg_sum;
423}
424
425ex mul::coeff(const ex & s, int n) const
426{
427 exvector coeffseq;
428 coeffseq.reserve(seq.size()+1);
429
430 if (n==0) {
431 // product of individual coeffs
432 // if a non-zero power of s is found, the resulting product will be 0
433 for (auto & it : seq)
434 coeffseq.push_back(recombine_pair_to_ex(it).coeff(s,n));
435 coeffseq.push_back(overall_coeff);
436 return dynallocate<mul>(coeffseq);
437 }
438
439 bool coeff_found = false;
440 for (auto & it : seq) {
441 ex t = recombine_pair_to_ex(it);
442 ex c = t.coeff(s, n);
443 if (!c.is_zero()) {
444 coeffseq.push_back(c);
445 coeff_found = 1;
446 } else {
447 coeffseq.push_back(t);
448 }
449 }
450 if (coeff_found) {
451 coeffseq.push_back(overall_coeff);
452 return dynallocate<mul>(coeffseq);
453 }
454
455 return _ex0;
456}
457
467{
469 GINAC_ASSERT(seq.size()>0);
471 return *this;
472 }
473
474 const epvector evaled = evalchildren();
475 if (unlikely(!evaled.empty())) {
476 // start over evaluating a new object
477 return dynallocate<mul>(std::move(evaled), overall_coeff);
478 }
479
480 size_t seq_size = seq.size();
481 if (overall_coeff.is_zero()) {
482 // *(...,x;0) -> 0
483 return _ex0;
484 } else if (seq_size==0) {
485 // *(;c) -> c
486 return overall_coeff;
487 } else if (seq_size==1 && overall_coeff.is_equal(_ex1)) {
488 // *(x;1) -> x
489 return recombine_pair_to_ex(*(seq.begin()));
490 } else if ((seq_size==1) &&
491 is_exactly_a<add>((*seq.begin()).rest) &&
492 ex_to<numeric>((*seq.begin()).coeff).is_equal(*_num1_p)) {
493 // *(+(x,y,...);c) -> +(*(x,c),*(y,c),...) (c numeric(), no powers of +())
494 const add & addref = ex_to<add>((*seq.begin()).rest);
495 epvector distrseq;
496 distrseq.reserve(addref.seq.size());
497 for (auto & it : addref.seq) {
498 distrseq.push_back(addref.combine_pair_with_coeff_to_pair(it, overall_coeff));
499 }
500 return dynallocate<add>(std::move(distrseq),
501 ex_to<numeric>(addref.overall_coeff).mul_dyn(ex_to<numeric>(overall_coeff)))
502 .setflag(status_flags::evaluated);
503 } else if ((seq_size >= 2) && (! (flags & status_flags::expanded))) {
504 // Strip the content and the unit part from each term. Thus
505 // things like (-x+a)*(3*x-3*a) automagically turn into - 3*(x-a)^2
506
507 auto i = seq.begin(), last = seq.end();
508 auto j = seq.begin();
509 epvector s;
510 numeric oc = *_num1_p;
511 bool something_changed = false;
512 while (i!=last) {
513 if (likely(! (is_a<add>(i->rest) && i->coeff.is_equal(_ex1)))) {
514 // power::eval has such a rule, no need to handle powers here
515 ++i;
516 continue;
517 }
518
519 // XXX: What is the best way to check if the polynomial is a primitive?
520 numeric c = i->rest.integer_content();
521 const numeric lead_coeff =
522 ex_to<numeric>(ex_to<add>(i->rest).seq.begin()->coeff).div(c);
523 const bool canonicalizable = lead_coeff.is_integer();
524
525 // XXX: The main variable is chosen in a random way, so this code
526 // does NOT transform the term into the canonical form (thus, in some
527 // very unlucky event it can even loop forever). Hopefully the main
528 // variable will be the same for all terms in *this
529 const bool unit_normal = lead_coeff.is_pos_integer();
530 if (likely((c == *_num1_p) && ((! canonicalizable) || unit_normal))) {
531 ++i;
532 continue;
533 }
534
535 if (! something_changed) {
536 s.reserve(seq_size);
537 something_changed = true;
538 }
539
540 while ((j!=i) && (j!=last)) {
541 s.push_back(*j);
542 ++j;
543 }
544
545 if (! unit_normal)
546 c = c.mul(*_num_1_p);
547
548 oc = oc.mul(c);
549
550 // divide add by the number in place to save at least 2 .eval() calls
551 const add& addref = ex_to<add>(i->rest);
552 add & primitive = dynallocate<add>(addref);
554 primitive.overall_coeff = ex_to<numeric>(primitive.overall_coeff).div_dyn(c);
555 for (auto & ai : primitive.seq)
556 ai.coeff = ex_to<numeric>(ai.coeff).div_dyn(c);
557
558 s.push_back(expair(primitive, _ex1));
559
560 ++i;
561 ++j;
562 }
563 if (something_changed) {
564 while (j!=last) {
565 s.push_back(*j);
566 ++j;
567 }
568 return dynallocate<mul>(std::move(s), ex_to<numeric>(overall_coeff).mul_dyn(oc));
569 }
570 }
571
572 return this->hold();
573}
574
576{
577 epvector s;
578 s.reserve(seq.size());
579
580 for (auto & it : seq)
581 s.push_back(expair(it.rest.evalf(), it.coeff));
582 return dynallocate<mul>(std::move(s), overall_coeff.evalf());
583}
584
585void mul::find_real_imag(ex & rp, ex & ip) const
586{
589 for (auto & it : seq) {
591 ex new_rp = factor.real_part();
592 ex new_ip = factor.imag_part();
593 if (new_ip.is_zero()) {
594 rp *= new_rp;
595 ip *= new_rp;
596 } else {
597 ex temp = rp*new_rp - ip*new_ip;
598 ip = ip*new_rp + rp*new_ip;
599 rp = temp;
600 }
601 }
602 rp = rp.expand();
603 ip = ip.expand();
604}
605
607{
608 ex rp, ip;
609 find_real_imag(rp, ip);
610 return rp;
611}
612
614{
615 ex rp, ip;
616 find_real_imag(rp, ip);
617 return ip;
618}
619
621{
622 // numeric*matrix
623 if (seq.size() == 1 && seq[0].coeff.is_equal(_ex1)
624 && is_a<matrix>(seq[0].rest))
625 return ex_to<matrix>(seq[0].rest).mul(ex_to<numeric>(overall_coeff));
626
627 // Evaluate children first, look whether there are any matrices at all
628 // (there can be either no matrices or one matrix; if there were more
629 // than one matrix, it would be a non-commutative product)
630 epvector s;
631 s.reserve(seq.size());
632
633 bool have_matrix = false;
634 epvector::iterator the_matrix;
635
636 for (auto & it : seq) {
637 const ex &m = recombine_pair_to_ex(it).evalm();
638 s.push_back(split_ex_to_pair(m));
639 if (is_a<matrix>(m)) {
640 have_matrix = true;
641 the_matrix = s.end() - 1;
642 }
643 }
644
645 if (have_matrix) {
646
647 // The product contained a matrix. We will multiply all other factors
648 // into that matrix.
649 matrix m = ex_to<matrix>(the_matrix->rest);
650 s.erase(the_matrix);
651 ex scalar = dynallocate<mul>(std::move(s), overall_coeff);
652 return m.mul_scalar(scalar);
653
654 } else
655 return dynallocate<mul>(std::move(s), overall_coeff);
656}
657
659{
660 if (seq.empty())
661 return inherited::eval_ncmul(v);
662
663 // Find first noncommutative element and call its eval_ncmul()
664 for (auto & it : seq)
665 if (it.rest.return_type() == return_types::noncommutative)
666 return it.rest.eval_ncmul(v);
667 return inherited::eval_ncmul(v);
668}
669
670bool tryfactsubs(const ex & origfactor, const ex & patternfactor, int & nummatches, exmap& repls)
671{
672 ex origbase;
673 int origexponent;
674 int origexpsign;
675
676 if (is_exactly_a<power>(origfactor) && origfactor.op(1).info(info_flags::integer)) {
677 origbase = origfactor.op(0);
678 int expon = ex_to<numeric>(origfactor.op(1)).to_int();
679 origexponent = expon > 0 ? expon : -expon;
680 origexpsign = expon > 0 ? 1 : -1;
681 } else {
682 origbase = origfactor;
683 origexponent = 1;
684 origexpsign = 1;
685 }
686
687 ex patternbase;
688 int patternexponent;
689 int patternexpsign;
690
691 if (is_exactly_a<power>(patternfactor) && patternfactor.op(1).info(info_flags::integer)) {
692 patternbase = patternfactor.op(0);
693 int expon = ex_to<numeric>(patternfactor.op(1)).to_int();
694 patternexponent = expon > 0 ? expon : -expon;
695 patternexpsign = expon > 0 ? 1 : -1;
696 } else {
697 patternbase = patternfactor;
698 patternexponent = 1;
699 patternexpsign = 1;
700 }
701
702 exmap saverepls = repls;
703 if (origexponent < patternexponent || origexpsign != patternexpsign || !origbase.match(patternbase,saverepls))
704 return false;
705 repls = saverepls;
706
707 int newnummatches = origexponent / patternexponent;
708 if (newnummatches < nummatches)
709 nummatches = newnummatches;
710 return true;
711}
712
721bool algebraic_match_mul_with_mul(const mul &e, const ex &pat, exmap& repls,
722 int factor, int &nummatches, const std::vector<bool> &subsed,
723 std::vector<bool> &matched)
724{
725 GINAC_ASSERT(subsed.size() == e.nops());
726 GINAC_ASSERT(matched.size() == e.nops());
727
728 if (factor == (int)pat.nops())
729 return true;
730
731 for (size_t i=0; i<e.nops(); ++i) {
732 if(subsed[i] || matched[i])
733 continue;
734 exmap newrepls = repls;
735 int newnummatches = nummatches;
736 if (tryfactsubs(e.op(i), pat.op(factor), newnummatches, newrepls)) {
737 matched[i] = true;
738 if (algebraic_match_mul_with_mul(e, pat, newrepls, factor+1,
739 newnummatches, subsed, matched)) {
740 repls = newrepls;
741 nummatches = newnummatches;
742 return true;
743 }
744 else
745 matched[i] = false;
746 }
747 }
748
749 return false;
750}
751
752bool mul::has(const ex & pattern, unsigned options) const
753{
755 return basic::has(pattern,options);
756 if(is_a<mul>(pattern)) {
757 exmap repls;
758 int nummatches = std::numeric_limits<int>::max();
759 std::vector<bool> subsed(nops(), false);
760 std::vector<bool> matched(nops(), false);
761 if(algebraic_match_mul_with_mul(*this, pattern, repls, 0, nummatches,
762 subsed, matched))
763 return true;
764 }
765 return basic::has(pattern, options);
766}
767
768ex mul::algebraic_subs_mul(const exmap & m, unsigned options) const
769{
770 std::vector<bool> subsed(nops(), false);
771 ex divide_by = 1;
772 ex multiply_by = 1;
773
774 for (auto & it : m) {
775
776 if (is_exactly_a<mul>(it.first)) {
777retry1:
778 int nummatches = std::numeric_limits<int>::max();
779 std::vector<bool> currsubsed(nops(), false);
780 exmap repls;
781
782 if (!algebraic_match_mul_with_mul(*this, it.first, repls, 0, nummatches, subsed, currsubsed))
783 continue;
784
785 for (size_t j=0; j<subsed.size(); j++)
786 if (currsubsed[j])
787 subsed[j] = true;
788 ex subsed_pattern
789 = it.first.subs(repls, subs_options::no_pattern);
790 divide_by *= pow(subsed_pattern, nummatches);
791 ex subsed_result
792 = it.second.subs(repls, subs_options::no_pattern);
793 multiply_by *= pow(subsed_result, nummatches);
794 goto retry1;
795
796 } else {
797
798 for (size_t j=0; j<this->nops(); j++) {
799 int nummatches = std::numeric_limits<int>::max();
800 exmap repls;
801 if (!subsed[j] && tryfactsubs(op(j), it.first, nummatches, repls)){
802 subsed[j] = true;
803 ex subsed_pattern
804 = it.first.subs(repls, subs_options::no_pattern);
805 divide_by *= pow(subsed_pattern, nummatches);
806 ex subsed_result
807 = it.second.subs(repls, subs_options::no_pattern);
808 multiply_by *= pow(subsed_result, nummatches);
809 }
810 }
811 }
812 }
813
814 bool subsfound = false;
815 for (size_t i=0; i<subsed.size(); i++) {
816 if (subsed[i]) {
817 subsfound = true;
818 break;
819 }
820 }
821 if (!subsfound)
823
824 return ((*this)/divide_by)*multiply_by;
825}
826
828{
829 // The base class' method is wrong here because we have to be careful at
830 // branch cuts. power::conjugate takes care of that already, so use it.
831 std::unique_ptr<epvector> newepv(nullptr);
832 for (auto i=seq.begin(); i!=seq.end(); ++i) {
833 if (newepv) {
834 newepv->push_back(split_ex_to_pair(recombine_pair_to_ex(*i).conjugate()));
835 continue;
836 }
838 ex c = x.conjugate();
839 if (c.is_equal(x)) {
840 continue;
841 }
842 newepv.reset(new epvector);
843 newepv->reserve(seq.size());
844 for (auto j=seq.begin(); j!=i; ++j) {
845 newepv->push_back(*j);
846 }
847 newepv->push_back(split_ex_to_pair(c));
848 }
850 if (!newepv && are_ex_trivially_equal(x, overall_coeff)) {
851 return *this;
852 }
853 return thisexpairseq(newepv ? std::move(*newepv) : seq, x);
854}
855
856
857// protected
858
861ex mul::derivative(const symbol & s) const
862{
863 size_t num = seq.size();
864 exvector addseq;
865 addseq.reserve(num);
866
867 // D(a*b*c) = D(a)*b*c + a*D(b)*c + a*b*D(c)
868 epvector mulseq = seq;
869 auto i = seq.begin(), end = seq.end();
870 auto i2 = mulseq.begin();
871 while (i != end) {
872 expair ep = split_ex_to_pair(pow(i->rest, i->coeff - _ex1) *
873 i->rest.diff(s));
874 ep.swap(*i2);
875 addseq.push_back(dynallocate<mul>(mulseq, overall_coeff * i->coeff));
876 ep.swap(*i2);
877 ++i; ++i2;
878 }
879 return dynallocate<add>(addseq);
880}
881
882int mul::compare_same_type(const basic & other) const
883{
884 return inherited::compare_same_type(other);
885}
886
887unsigned mul::return_type() const
888{
889 if (seq.empty()) {
890 // mul without factors: should not happen, but commutates
892 }
893
894 bool all_commutative = true;
895 epvector::const_iterator noncommutative_element; // point to first found nc element
896
897 epvector::const_iterator i = seq.begin(), end = seq.end();
898 while (i != end) {
899 unsigned rt = i->rest.return_type();
901 return rt; // one ncc -> mul also ncc
902 if ((rt == return_types::noncommutative) && (all_commutative)) {
903 // first nc element found, remember position
904 noncommutative_element = i;
905 all_commutative = false;
906 }
907 if ((rt == return_types::noncommutative) && (!all_commutative)) {
908 // another nc element found, compare type_infos
909 if (noncommutative_element->rest.return_type_tinfo() != i->rest.return_type_tinfo()) {
910 // different types -> mul is ncc
912 }
913 }
914 ++i;
915 }
916 // all factors checked
918}
919
921{
922 if (seq.empty())
923 return make_return_type_t<mul>(); // mul without factors: should not happen
924
925 // return type_info of first noncommutative element
926 for (auto & it : seq)
927 if (it.rest.return_type() == return_types::noncommutative)
928 return it.rest.return_type_tinfo();
929
930 // no noncommutative element found, should not happen
931 return make_return_type_t<mul>();
932}
933
934ex mul::thisexpairseq(const epvector & v, const ex & oc, bool do_index_renaming) const
935{
936 return dynallocate<mul>(v, oc, do_index_renaming);
937}
938
939ex mul::thisexpairseq(epvector && vp, const ex & oc, bool do_index_renaming) const
940{
941 return dynallocate<mul>(std::move(vp), oc, do_index_renaming);
942}
943
945{
946 if (is_exactly_a<power>(e)) {
947 const power & powerref = ex_to<power>(e);
948 if (is_exactly_a<numeric>(powerref.exponent))
949 return expair(powerref.basis,powerref.exponent);
950 }
951 return expair(e,_ex1);
952}
953
955 const ex & c) const
956{
957 GINAC_ASSERT(is_exactly_a<numeric>(c));
958
959 // First, try a common shortcut:
960 if (is_exactly_a<symbol>(e))
961 return expair(e, c);
962
963 // trivial case: exponent 1
964 if (c.is_equal(_ex1))
965 return split_ex_to_pair(e);
966
967 // to avoid duplication of power simplification rules,
968 // we create a temporary power object
969 // otherwise it would be hard to correctly evaluate
970 // expression like (4^(1/3))^(3/2)
971 return split_ex_to_pair(pow(e,c));
972}
973
975 const ex & c) const
976{
977 GINAC_ASSERT(is_exactly_a<numeric>(p.coeff));
978 GINAC_ASSERT(is_exactly_a<numeric>(c));
979
980 // First, try a common shortcut:
981 if (is_exactly_a<symbol>(p.rest))
982 return expair(p.rest, p.coeff * c);
983
984 // trivial case: exponent 1
985 if (c.is_equal(_ex1))
986 return p;
987 if (p.coeff.is_equal(_ex1))
988 return expair(p.rest, c);
989
990 // to avoid duplication of power simplification rules,
991 // we create a temporary power object
992 // otherwise it would be hard to correctly evaluate
993 // expression like (4^(1/3))^(3/2)
995}
996
998{
999 if (p.coeff.is_equal(_ex1))
1000 return p.rest;
1001 else
1002 return dynallocate<power>(p.rest, p.coeff);
1003}
1004
1006{
1007 if (is_exactly_a<mul>(it->rest) &&
1008 ex_to<numeric>(it->coeff).is_integer()) {
1009 // combined pair is product with integer power -> expand it
1011 return true;
1012 }
1013 if (is_exactly_a<numeric>(it->rest)) {
1014 if (it->coeff.is_equal(_ex1)) {
1015 // pair has coeff 1 and must be moved to the end
1016 return true;
1017 }
1019 if (!ep.is_equal(*it)) {
1020 // combined pair is a numeric power which can be simplified
1021 *it = ep;
1022 return true;
1023 }
1024 }
1025 return false;
1026}
1027
1029{
1030 return _ex1;
1031}
1032
1034{
1035 GINAC_ASSERT(is_exactly_a<numeric>(overall_coeff));
1036 GINAC_ASSERT(is_exactly_a<numeric>(c));
1037 overall_coeff = ex_to<numeric>(overall_coeff).mul_dyn(ex_to<numeric>(c));
1038}
1039
1040void mul::combine_overall_coeff(const ex & c1, const ex & c2)
1041{
1042 GINAC_ASSERT(is_exactly_a<numeric>(overall_coeff));
1043 GINAC_ASSERT(is_exactly_a<numeric>(c1));
1044 GINAC_ASSERT(is_exactly_a<numeric>(c2));
1045 overall_coeff = ex_to<numeric>(overall_coeff).mul_dyn(ex_to<numeric>(c1).power(ex_to<numeric>(c2)));
1046}
1047
1048bool mul::can_make_flat(const expair & p) const
1049{
1050 GINAC_ASSERT(is_exactly_a<numeric>(p.coeff));
1051
1052 // (x*y)^c == x^c*y^c if c ∈ ℤ
1053 return p.coeff.info(info_flags::integer);
1054}
1055
1057{
1058 if (is_exactly_a<mul>(e)) {
1059 for (auto & it : ex_to<mul>(e).seq) {
1060 if (is_exactly_a<add>(it.rest) && it.coeff.info(info_flags::posint))
1061 return true;
1062 }
1063 } else if (is_exactly_a<power>(e)) {
1064 if (is_exactly_a<add>(e.op(0)) && e.op(1).info(info_flags::posint))
1065 return true;
1066 }
1067 return false;
1068}
1069
1070ex mul::expand(unsigned options) const
1071{
1072 // Check for trivial case: expanding the monomial (~ 30% of all calls)
1073 bool monomial_case = true;
1074 for (const auto & i : seq) {
1075 if (!is_a<symbol>(i.rest) || !i.coeff.info(info_flags::integer)) {
1076 monomial_case = false;
1077 break;
1078 }
1079 }
1080 if (monomial_case) {
1082 return *this;
1083 }
1084
1085 // do not rename indices if the object has no indices at all
1089
1090 const bool skip_idx_rename = !(options & expand_options::expand_rename_idx);
1091
1092 // First, expand the children
1093 epvector expanded = expandchildren(options);
1094 const epvector & expanded_seq = (expanded.empty() ? seq : expanded);
1095
1096 // Now, look for all the factors that are sums and multiply each one out
1097 // with the next one that is found while collecting the factors which are
1098 // not sums
1099 ex last_expanded = _ex1;
1100
1101 epvector non_adds;
1102 non_adds.reserve(expanded_seq.size());
1103
1104 for (const auto & cit : expanded_seq) {
1105 if (is_exactly_a<add>(cit.rest) &&
1106 (cit.coeff.is_equal(_ex1))) {
1107 if (is_exactly_a<add>(last_expanded)) {
1108
1109 // Expand a product of two sums, aggressive version.
1110 // Caring for the overall coefficients in separate loops can
1111 // sometimes give a performance gain of up to 15%!
1112
1113 const int sizedifference = ex_to<add>(last_expanded).seq.size()-ex_to<add>(cit.rest).seq.size();
1114 // add2 is for the inner loop and should be the bigger of the two sums
1115 // in the presence of asymptotically good sorting:
1116 const add& add1 = (sizedifference<0 ? ex_to<add>(last_expanded) : ex_to<add>(cit.rest));
1117 const add& add2 = (sizedifference<0 ? ex_to<add>(cit.rest) : ex_to<add>(last_expanded));
1118 epvector distrseq;
1119 distrseq.reserve(add1.seq.size()+add2.seq.size());
1120
1121 // Multiply add2 with the overall coefficient of add1 and append it to distrseq:
1122 if (!add1.overall_coeff.is_zero()) {
1123 if (add1.overall_coeff.is_equal(_ex1))
1124 distrseq.insert(distrseq.end(), add2.seq.begin(), add2.seq.end());
1125 else
1126 for (const auto & i : add2.seq)
1127 distrseq.push_back(expair(i.rest, ex_to<numeric>(i.coeff).mul_dyn(ex_to<numeric>(add1.overall_coeff))));
1128 }
1129
1130 // Multiply add1 with the overall coefficient of add2 and append it to distrseq:
1131 if (!add2.overall_coeff.is_zero()) {
1132 if (add2.overall_coeff.is_equal(_ex1))
1133 distrseq.insert(distrseq.end(), add1.seq.begin(), add1.seq.end());
1134 else
1135 for (const auto & i : add1.seq)
1136 distrseq.push_back(expair(i.rest, ex_to<numeric>(i.coeff).mul_dyn(ex_to<numeric>(add2.overall_coeff))));
1137 }
1138
1139 // Compute the new overall coefficient and put it together:
1140 ex tmp_accu = dynallocate<add>(distrseq, add1.overall_coeff*add2.overall_coeff);
1141
1142 exvector add1_dummy_indices, add2_dummy_indices, add_indices;
1143 lst dummy_subs;
1144
1145 if (!skip_idx_rename) {
1146 for (const auto & i : add1.seq) {
1147 add_indices = get_all_dummy_indices_safely(i.rest);
1148 add1_dummy_indices.insert(add1_dummy_indices.end(), add_indices.begin(), add_indices.end());
1149 }
1150 for (const auto & i : add2.seq) {
1151 add_indices = get_all_dummy_indices_safely(i.rest);
1152 add2_dummy_indices.insert(add2_dummy_indices.end(), add_indices.begin(), add_indices.end());
1153 }
1154
1155 sort(add1_dummy_indices.begin(), add1_dummy_indices.end(), ex_is_less());
1156 sort(add2_dummy_indices.begin(), add2_dummy_indices.end(), ex_is_less());
1157 dummy_subs = rename_dummy_indices_uniquely(add1_dummy_indices, add2_dummy_indices);
1158 }
1159
1160 // Multiply explicitly all non-numeric terms of add1 and add2:
1161 for (const auto & i2 : add2.seq) {
1162 // We really have to combine terms here in order to compactify
1163 // the result. Otherwise it would become waayy tooo bigg.
1164 numeric oc(*_num0_p);
1165 epvector distrseq2;
1166 distrseq2.reserve(add1.seq.size());
1167 const ex i2_new = (skip_idx_rename || (dummy_subs.op(0).nops() == 0) ?
1168 i2.rest :
1169 i2.rest.subs(ex_to<lst>(dummy_subs.op(0)),
1170 ex_to<lst>(dummy_subs.op(1)), subs_options::no_pattern));
1171 for (const auto & i1 : add1.seq) {
1172 // Don't push_back expairs which might have a rest that evaluates to a numeric,
1173 // since that would violate an invariant of expairseq:
1174 const ex rest = dynallocate<mul>(i1.rest, i2_new);
1175 if (is_exactly_a<numeric>(rest)) {
1176 oc += ex_to<numeric>(rest).mul(ex_to<numeric>(i1.coeff).mul(ex_to<numeric>(i2.coeff)));
1177 } else {
1178 distrseq2.push_back(expair(rest, ex_to<numeric>(i1.coeff).mul_dyn(ex_to<numeric>(i2.coeff))));
1179 }
1180 }
1181 tmp_accu += dynallocate<add>(std::move(distrseq2), oc);
1182 }
1183 last_expanded = tmp_accu;
1184 } else {
1185 if (!last_expanded.is_equal(_ex1))
1186 non_adds.push_back(split_ex_to_pair(last_expanded));
1187 last_expanded = cit.rest;
1188 }
1189
1190 } else {
1191 non_adds.push_back(cit);
1192 }
1193 }
1194
1195 // Now the only remaining thing to do is to multiply the factors which
1196 // were not sums into the "last_expanded" sum
1197 if (is_exactly_a<add>(last_expanded)) {
1198 size_t n = last_expanded.nops();
1199 exvector distrseq;
1200 distrseq.reserve(n);
1201 exvector va;
1202 if (! skip_idx_rename) {
1203 va = get_all_dummy_indices_safely(mul(non_adds));
1204 sort(va.begin(), va.end(), ex_is_less());
1205 }
1206
1207 for (size_t i=0; i<n; ++i) {
1208 epvector factors = non_adds;
1209 if (skip_idx_rename)
1210 factors.push_back(split_ex_to_pair(last_expanded.op(i)));
1211 else
1212 factors.push_back(split_ex_to_pair(rename_dummy_indices_uniquely(va, last_expanded.op(i))));
1213 ex term = dynallocate<mul>(factors, overall_coeff);
1214 if (can_be_further_expanded(term)) {
1215 distrseq.push_back(term.expand());
1216 } else {
1217 if (options == 0)
1218 ex_to<basic>(term).setflag(status_flags::expanded);
1219 distrseq.push_back(term);
1220 }
1221 }
1222
1223 return dynallocate<add>(distrseq).setflag(options == 0 ? status_flags::expanded : 0);
1224 }
1225
1226 non_adds.push_back(split_ex_to_pair(last_expanded));
1227 ex result = dynallocate<mul>(non_adds, overall_coeff);
1228 if (can_be_further_expanded(result)) {
1229 return result.expand();
1230 } else {
1231 if (options == 0)
1232 ex_to<basic>(result).setflag(status_flags::expanded);
1233 return result;
1234 }
1235}
1236
1237
1239// new virtual functions which can be overridden by derived classes
1241
1242// none
1243
1245// non-virtual functions in this class
1247
1248
1257{
1258 auto cit = seq.begin(), last = seq.end();
1259 while (cit!=last) {
1260 const ex & factor = recombine_pair_to_ex(*cit);
1261 const ex & expanded_factor = factor.expand(options);
1262 if (!are_ex_trivially_equal(factor,expanded_factor)) {
1263
1264 // something changed, copy seq, eval and return it
1265 epvector s;
1266 s.reserve(seq.size());
1267
1268 // copy parts of seq which are known not to have changed
1269 auto cit2 = seq.begin();
1270 while (cit2!=cit) {
1271 s.push_back(*cit2);
1272 ++cit2;
1273 }
1274
1275 // copy first changed element
1276 s.push_back(split_ex_to_pair(expanded_factor));
1277 ++cit2;
1278
1279 // copy rest
1280 while (cit2!=last) {
1282 ++cit2;
1283 }
1284 return s;
1285 }
1286 ++cit;
1287 }
1288
1289 return epvector(); // nothing has changed
1290}
1291
1293
1294} // namespace GiNaC
Interface to GiNaC's sums of expressions.
Archiving of GiNaC expressions.
#define GINAC_ASSERT(X)
Assertion macro for checking invariances.
Definition assertion.h:32
Sum of expressions.
Definition add.h:31
expair combine_pair_with_coeff_to_pair(const expair &p, const ex &c) const override
Definition add.cpp:547
This class is the ABC (abstract base class) of GiNaC's class hierarchy.
Definition basic.h:104
const basic & clearflag(unsigned f) const
Clear some status_flags.
Definition basic.h:290
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
virtual bool has(const ex &other, unsigned options=0) const
Test for occurrence of a pattern.
Definition basic.cpp:279
unsigned flags
of type status_flags
Definition basic.h:301
virtual void print(const print_context &c, unsigned level=0) const
Output to stream.
Definition basic.cpp:115
ex subs_one_level(const exmap &m, unsigned options) const
Helper function for subs().
Definition basic.cpp:584
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
void do_print_tree(const print_tree &c, unsigned level) const
Tree output to stream.
Definition basic.cpp:174
Wrapper template for making GiNaC classes out of STL containers.
Definition container.h:72
ex op(size_t i) const override
Return operand/member at position i.
Definition container.h:294
Lightweight wrapper for GiNaC's symbolic objects.
Definition ex.h:72
bool match(const ex &pattern) const
Check whether expression matches a specified pattern.
Definition ex.cpp:95
const_iterator begin() const noexcept
Definition ex.h:662
ex expand(unsigned options=0) const
Expand an expression.
Definition ex.cpp:73
bool is_equal(const ex &other) const
Definition ex.h:345
int degree(const ex &s) const
Definition ex.h:173
ex evalf() const
Definition ex.h:121
ex conjugate() const
Definition ex.h:146
const_iterator end() const noexcept
Definition ex.h:667
size_t nops() const
Definition ex.h:135
ex imag_part() const
Definition ex.h:148
ex subs(const exmap &m, unsigned options=0) const
Definition ex.h:841
bool info(unsigned inf) const
Definition ex.h:132
bool is_zero() const
Definition ex.h:213
void print(const print_context &c, unsigned level=0) const
Print expression to stream.
Definition ex.cpp:54
ex op(size_t i) const
Definition ex.h:136
int ldegree(const ex &s) const
Definition ex.h:174
ex real_part() const
Definition ex.h:147
ex evalm() const
Definition ex.h:122
ex coeff(const ex &s, int n=1) const
Definition ex.h:175
A pair of expressions.
Definition expair.h:37
void swap(expair &other)
Swap contents with other expair.
Definition expair.h:81
ex rest
first member of pair, an arbitrary expression
Definition expair.h:89
ex coeff
second member of pair, must be numeric
Definition expair.h:90
bool is_equal(const expair &other) const
Member-wise check for canonical ordering equality.
Definition expair.h:48
A sequence of class expair.
Definition expairseq.h:49
size_t nops() const override
Number of operands/members.
void construct_from_epvector(const epvector &v, bool do_index_renaming=false)
void construct_from_2_ex(const ex &lh, const ex &rh)
bool is_canonical() const
Check if this expairseq is in sorted (canonical) form.
void construct_from_exvector(const exvector &v)
epvector evalchildren() const
Member-wise evaluate the expairs in this sequence.
ex op(size_t i) const override
Return operand/member at position i.
@ expand_rename_idx
used internally by mul::expand()
Definition flags.h:33
@ algebraic
enable algebraic matching
Definition flags.h:42
Symbolic matrices.
Definition matrix.h:37
Product of expressions.
Definition mul.h:31
ex thisexpairseq(const epvector &v, const ex &oc, bool do_index_renaming=false) const override
Create an object of this type.
Definition mul.cpp:934
bool info(unsigned inf) const override
Information about the object.
Definition mul.cpp:270
ex real_part() const override
Definition mul.cpp:606
bool expair_needs_further_processing(epp it) override
Definition mul.cpp:1005
ex evalf() const override
Evaluate object numerically.
Definition mul.cpp:575
epvector expandchildren(unsigned options) const
Member-wise expand the expairs representing this sequence.
Definition mul.cpp:1256
unsigned precedence() const override
Return relative operator precedence (for parenthezing output).
Definition mul.h:50
bool is_polynomial(const ex &var) const override
Check whether this is a polynomial in the given variables.
Definition mul.cpp:384
return_type_t return_type_tinfo() const override
Definition mul.cpp:920
ex default_overall_coeff() const override
Definition mul.cpp:1028
void do_print_csrc(const print_csrc &c, unsigned level) const
Definition mul.cpp:206
unsigned return_type() const override
Definition mul.cpp:887
void do_print(const print_context &c, unsigned level) const
Definition mul.cpp:146
void do_print_python_repr(const print_python_repr &c, unsigned level) const
Definition mul.cpp:259
ex conjugate() const override
Definition mul.cpp:827
bool can_make_flat(const expair &p) const override
Definition mul.cpp:1048
ex imag_part() const override
Definition mul.cpp:613
expair combine_ex_with_coeff_to_pair(const ex &e, const ex &c) const override
Definition mul.cpp:954
void combine_overall_coeff(const ex &c) override
Definition mul.cpp:1033
int ldegree(const ex &s) const override
Return degree of lowest power in object s.
Definition mul.cpp:410
ex eval() const override
Perform automatic term rewriting rules in this class.
Definition mul.cpp:466
expair split_ex_to_pair(const ex &e) const override
Form an expair from an ex, using the corresponding semantics.
Definition mul.cpp:944
ex expand(unsigned options=0) const override
Expand expression, i.e.
Definition mul.cpp:1070
int degree(const ex &s) const override
Return degree of highest power in object s.
Definition mul.cpp:395
void do_print_latex(const print_latex &c, unsigned level) const
Definition mul.cpp:166
static bool can_be_further_expanded(const ex &e)
Definition mul.cpp:1056
ex derivative(const symbol &s) const override
Implementation of ex::diff() for a product.
Definition mul.cpp:861
void find_real_imag(ex &, ex &) const
Definition mul.cpp:585
ex coeff(const ex &s, int n=1) const override
Return coefficient of degree n in object s.
Definition mul.cpp:425
friend class power
Definition mul.h:36
ex recombine_pair_to_ex(const expair &p) const override
Form an ex out of an expair, using the corresponding semantics.
Definition mul.cpp:997
ex evalm() const override
Evaluate sums, products and integer powers of matrices.
Definition mul.cpp:620
void print_overall_coeff(const print_context &c, const char *mul_sym) const
Definition mul.cpp:124
expair combine_pair_with_coeff_to_pair(const expair &p, const ex &c) const override
Definition mul.cpp:974
ex algebraic_subs_mul(const exmap &m, unsigned options) const
Definition mul.cpp:768
ex eval_ncmul(const exvector &v) const override
Definition mul.cpp:658
bool has(const ex &other, unsigned options=0) const override
Test for occurrence of a pattern.
Definition mul.cpp:752
mul(const ex &lh, const ex &rh)
Definition mul.cpp:62
This class is a wrapper around CLN-numbers within the GiNaC class hierarchy.
Definition numeric.h:81
bool is_pos_integer() const
True if object is an exact integer greater than zero.
Definition numeric.cpp:1160
numeric integer_content() const override
Definition normal.cpp:327
bool is_integer() const
True if object is a non-complex integer.
Definition numeric.cpp:1153
const numeric mul(const numeric &other) const
Numerical multiplication method.
Definition numeric.cpp:879
const numeric div(const numeric &other) const
Numerical division method.
Definition numeric.cpp:889
This class holds a two-component object, a basis and and exponent representing exponentiation.
Definition power.h:38
ex exponent
Definition power.h:105
Base class for print_contexts.
Definition print.h:101
Base context for C source output.
Definition print.h:156
Context for latex-parsable output.
Definition print.h:121
Context for python-parsable output.
Definition print.h:137
@ expanded
.expand(0) has already done its job (other expand() options ignore this flag)
Definition flags.h:203
@ evaluated
.eval() has already done its job
Definition flags.h:202
@ hash_calculated
.calchash() has already done its job
Definition flags.h:204
@ no_pattern
disable pattern matching
Definition flags.h:50
@ algebraic
enable algebraic substitutions
Definition flags.h:52
Basic CAS symbol.
Definition symbol.h:38
Definition of optimizing macros.
#define likely(cond)
Definition compiler.h:31
#define unlikely(cond)
Definition compiler.h:30
upvec factors
Definition factor.cpp:1429
unsigned options
Definition factor.cpp:2473
size_t n
Definition factor.cpp:1431
size_t c
Definition factor.cpp:756
ex x
Definition factor.cpp:1609
mvec m
Definition factor.cpp:757
size_t last
Definition factor.cpp:1433
Interface to GiNaC's indexed expressions.
Definition of GiNaC's lst.
Interface to symbolic matrices.
Interface to GiNaC's products of expressions.
Definition add.cpp:35
const numeric * _num_1_p
Definition utils.cpp:350
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
std::vector< expair > epvector
expair-vector
Definition expairseq.h:32
const ex _ex1
Definition utils.cpp:384
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 ex _ex_1
Definition utils.cpp:351
bool algebraic_match_mul_with_mul(const mul &e, const ex &pat, exmap &repls, int factor, int &nummatches, const std::vector< bool > &subsed, std::vector< bool > &matched)
Checks whether e matches to the pattern pat and the (possibly to be updated) list of replacements rep...
Definition mul.cpp:721
print_func< print_context >(&varidx::do_print). print_func< print_latex >(&varidx
Definition idx.cpp:43
const numeric * _num1_p
Definition utils.cpp:383
epvector::iterator epp
expair-vector pointer
Definition expairseq.h:33
ex factor(const ex &poly, unsigned options)
Interface function to the outside world.
Definition factor.cpp:2574
lst rename_dummy_indices_uniquely(const exvector &va, const exvector &vb)
Similar to above, where va and vb are the same and the return value is a list of two lists for substi...
Definition indexed.cpp:1460
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
const ex _ex0
Definition utils.cpp:368
bool tryfactsubs(const ex &origfactor, const ex &patternfactor, int &nummatches, exmap &repls)
Definition mul.cpp:670
std::vector< ex > exvector
Definition basic.h:47
const numeric * _num0_p
Definition utils.cpp:366
exvector get_all_dummy_indices_safely(const ex &e)
More reliable version of the form.
Definition indexed.cpp:1394
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
To distinguish between different kinds of non-commutative objects.
Definition registrar.h:42
Interface to GiNaC's symbolic objects.
Interface to several small and furry utilities needed within GiNaC but not of any interest to the use...

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