From 6e4eb6ad70453ef14e275313a56ea180eac9cae0 Mon Sep 17 00:00:00 2001 From: Jens Vollinga Date: Wed, 19 Apr 2006 21:18:35 +0000 Subject: [PATCH] New branch "experimental_fclasses" created for testing new function system. --- check/exam_indexed.cpp | 449 +++++ check/exam_pseries.cpp | 379 ++++ ginac/Makefile.am | 49 +- ginac/flags.h | 293 ++- ginac/function.cpp | 196 ++ ginac/function.h | 193 ++ ginac/function.pl | 657 ------ ginac/ginac.h | 81 +- ginac/inifcns.cpp | 747 +++++-- ginac/inifcns.h | 188 +- ginac/inifcns_exp.cpp | 903 +++++++++ ginac/inifcns_exp.h | 164 ++ ginac/inifcns_gamma.cpp | 145 -- ginac/inifcns_polylog.cpp | 3969 +++++++++++++++++++++++++++++++++++++ ginac/inifcns_polylog.h | 148 ++ ginac/inifcns_trans.cpp | 743 ------- ginac/inifcns_trig.cpp | 1116 +++++++++++ ginac/inifcns_trig.h | 258 +++ ginac/input_parser.yy | 207 ++ ginac/integral.cpp | 478 +++++ ginac/matrix.cpp | 2227 ++++++++++++++------- ginac/normal.cpp | 3104 +++++++++++++++++++---------- ginac/power.cpp | 1387 +++++++------ ginac/pseries.cpp | 1177 +++++++++++ ginac/registrar.cpp | 46 + ginac/registrar.h | 200 ++ ginac/symbol.cpp | 357 ++-- ginac/symmetry.cpp | 498 +++++ ginsh/ginsh_parser.yy | 682 ++++--- 29 files changed, 16407 insertions(+), 4634 deletions(-) create mode 100644 check/exam_indexed.cpp create mode 100644 check/exam_pseries.cpp create mode 100644 ginac/function.cpp create mode 100644 ginac/function.h delete mode 100755 ginac/function.pl create mode 100644 ginac/inifcns_exp.cpp create mode 100644 ginac/inifcns_exp.h delete mode 100644 ginac/inifcns_gamma.cpp create mode 100644 ginac/inifcns_polylog.cpp create mode 100644 ginac/inifcns_polylog.h delete mode 100644 ginac/inifcns_trans.cpp create mode 100644 ginac/inifcns_trig.cpp create mode 100644 ginac/inifcns_trig.h create mode 100644 ginac/input_parser.yy create mode 100644 ginac/integral.cpp create mode 100644 ginac/pseries.cpp create mode 100644 ginac/registrar.cpp create mode 100644 ginac/registrar.h create mode 100644 ginac/symmetry.cpp diff --git a/check/exam_indexed.cpp b/check/exam_indexed.cpp new file mode 100644 index 00000000..dcfa0ae5 --- /dev/null +++ b/check/exam_indexed.cpp @@ -0,0 +1,449 @@ +/** @file exam_indexed.cpp + * + * Here we test manipulations on GiNaC's indexed objects. */ + +/* + * GiNaC Copyright (C) 1999-2005 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "exams.h" + +static unsigned check_equal(const ex &e1, const ex &e2) +{ + ex e = e1 - e2; + if (!e.is_zero()) { + clog << e1 << "-" << e2 << " erroneously returned " + << e << " instead of 0" << endl; + return 1; + } + return 0; +} + +static unsigned check_equal_simplify(const ex &e1, const ex &e2) +{ + ex e = simplify_indexed(e1) - e2; + if (!e.is_zero()) { + clog << "simplify_indexed(" << e1 << ")-" << e2 << " erroneously returned " + << e << " instead of 0" << endl; + return 1; + } + return 0; +} + +static unsigned check_equal_simplify(const ex &e1, const ex &e2, const scalar_products &sp) +{ + ex e = simplify_indexed(e1, sp) - e2; + if (!e.is_zero()) { + clog << "simplify_indexed(" << e1 << ")-" << e2 << " erroneously returned " + << e << " instead of 0" << endl; + return 1; + } + return 0; +} + +static unsigned delta_check() +{ + // checks identities of the delta tensor + + unsigned result = 0; + + symbol s_i("i"), s_j("j"), s_k("k"); + idx i(s_i, 3), j(s_j, 3), k(s_k, 3); + symbol A("A"); + + // symmetry + result += check_equal(delta_tensor(i, j), delta_tensor(j, i)); + + // trace = dimension of index space + result += check_equal(delta_tensor(i, i), 3); + result += check_equal_simplify(delta_tensor(i, j) * delta_tensor(i, j), 3); + + // contraction with delta tensor + result += check_equal_simplify(delta_tensor(i, j) * indexed(A, k), delta_tensor(i, j) * indexed(A, k)); + result += check_equal_simplify(delta_tensor(i, j) * indexed(A, j), indexed(A, i)); + result += check_equal_simplify(delta_tensor(i, j) * indexed(A, i), indexed(A, j)); + result += check_equal_simplify(delta_tensor(i, j) * delta_tensor(j, k) * indexed(A, i), indexed(A, k)); + + return result; +} + +static unsigned metric_check() +{ + // checks identities of the metric tensor + + unsigned result = 0; + + symbol s_mu("mu"), s_nu("nu"), s_rho("rho"), s_sigma("sigma"); + varidx mu(s_mu, 4), nu(s_nu, 4), rho(s_rho, 4), sigma(s_sigma, 4); + symbol A("A"); + + // becomes delta tensor if indices have opposite variance + result += check_equal(metric_tensor(mu, nu.toggle_variance()), delta_tensor(mu, nu.toggle_variance())); + + // scalar contraction = dimension of index space + result += check_equal(metric_tensor(mu, mu.toggle_variance()), 4); + result += check_equal_simplify(metric_tensor(mu, nu) * metric_tensor(mu.toggle_variance(), nu.toggle_variance()), 4); + + // contraction with metric tensor + result += check_equal_simplify(metric_tensor(mu, nu) * indexed(A, nu), metric_tensor(mu, nu) * indexed(A, nu)); + result += check_equal_simplify(metric_tensor(mu, nu) * indexed(A, nu.toggle_variance()), indexed(A, mu)); + result += check_equal_simplify(metric_tensor(mu, nu) * indexed(A, mu.toggle_variance()), indexed(A, nu)); + result += check_equal_simplify(metric_tensor(mu, nu) * metric_tensor(mu.toggle_variance(), rho.toggle_variance()) * indexed(A, nu.toggle_variance()), indexed(A, rho.toggle_variance())); + result += check_equal_simplify(metric_tensor(mu, rho) * metric_tensor(nu, sigma) * indexed(A, rho.toggle_variance(), sigma.toggle_variance()), indexed(A, mu, nu)); + result += check_equal_simplify(indexed(A, mu.toggle_variance()) * metric_tensor(mu, nu) - indexed(A, mu.toggle_variance()) * metric_tensor(nu, mu), 0); + result += check_equal_simplify(indexed(A, mu.toggle_variance(), nu.toggle_variance()) * metric_tensor(nu, rho), indexed(A, mu.toggle_variance(), rho)); + + // contraction with delta tensor yields a metric tensor + result += check_equal_simplify(delta_tensor(mu, nu.toggle_variance()) * metric_tensor(nu, rho), metric_tensor(mu, rho)); + result += check_equal_simplify(metric_tensor(mu, nu) * indexed(A, nu.toggle_variance()) * delta_tensor(mu.toggle_variance(), rho), indexed(A, rho)); + + return result; +} + +static unsigned epsilon_check() +{ + // checks identities of the epsilon tensor + + unsigned result = 0; + + symbol s_mu("mu"), s_nu("nu"), s_rho("rho"), s_sigma("sigma"), s_tau("tau"); + symbol d("d"); + varidx mu(s_mu, 4), nu(s_nu, 4), rho(s_rho, 4), sigma(s_sigma, 4), tau(s_tau, 4); + varidx mu_co(s_mu, 4, true), nu_co(s_nu, 4, true), rho_co(s_rho, 4, true), sigma_co(s_sigma, 4, true), tau_co(s_tau, 4, true); + + // antisymmetry + result += check_equal(lorentz_eps(mu, nu, rho, sigma) + lorentz_eps(sigma, rho, mu, nu), 0); + + // convolution is zero + result += check_equal(lorentz_eps(mu, nu, rho, nu_co), 0); + result += check_equal(lorentz_eps(mu, nu, mu_co, nu_co), 0); + result += check_equal_simplify(lorentz_g(mu_co, nu_co) * lorentz_eps(mu, nu, rho, sigma), 0); + + // contraction with symmetric tensor is zero + result += check_equal_simplify(lorentz_eps(mu, nu, rho, sigma) * indexed(d, sy_symm(), mu_co, nu_co), 0); + result += check_equal_simplify(lorentz_eps(mu, nu, rho, sigma) * indexed(d, sy_symm(), nu_co, sigma_co, rho_co), 0); + result += check_equal_simplify(lorentz_eps(mu, nu, rho, sigma) * indexed(d, mu_co) * indexed(d, nu_co), 0); + result += check_equal_simplify(lorentz_eps(mu_co, nu, rho, sigma) * indexed(d, mu) * indexed(d, nu_co), 0); + ex e = lorentz_eps(mu, nu, rho, sigma) * indexed(d, mu_co) - lorentz_eps(mu_co, nu, rho, sigma) * indexed(d, mu); + result += check_equal_simplify(e, 0); + + // contractions of epsilon tensors + result += check_equal_simplify(lorentz_eps(mu, nu, rho, sigma) * lorentz_eps(mu_co, nu_co, rho_co, sigma_co), -24); + result += check_equal_simplify(lorentz_eps(tau, nu, rho, sigma) * lorentz_eps(mu_co, nu_co, rho_co, sigma_co), -6 * delta_tensor(tau, mu_co)); + + return result; +} + +class symm_fcn : public function +{ + GINAC_DECLARE_FUNCTION_2P(symm_fcn) +public: + virtual ex eval(int level = 0) const + { + // Canonicalize argument order according to the symmetry properties + exvector v = seq; + int sig = canonicalize(v.begin(), sy_symm(0, 1)); + if (sig != INT_MAX) { + // Something has changed while sorting arguments, more evaluations later + if (sig == 0) + return 0; + return ex(sig) * thiscontainer(v); + } + return this->hold(); + } +}; +GINAC_IMPLEMENT_FUNCTION(symm_fcn) + +class anti_fcn : public function +{ + GINAC_DECLARE_FUNCTION_2P(anti_fcn) +public: + virtual ex eval(int level = 0) const + { + // Canonicalize argument order according to the symmetry properties + exvector v = seq; + int sig = canonicalize(v.begin(), sy_anti(0, 1)); + if (sig != INT_MAX) { + // Something has changed while sorting arguments, more evaluations later + if (sig == 0) + return 0; + return ex(sig) * thiscontainer(v); + } + return this->hold(); + } +}; +GINAC_IMPLEMENT_FUNCTION(anti_fcn) + +static unsigned symmetry_check() +{ + // check symmetric/antisymmetric objects + + unsigned result = 0; + + idx i(symbol("i"), 3), j(symbol("j"), 3), k(symbol("k"), 3), l(symbol("l"), 3); + symbol A("A"), B("B"), C("C"); + ex e; + + result += check_equal(indexed(A, sy_symm(), i, j), indexed(A, sy_symm(), j, i)); + result += check_equal(indexed(A, sy_anti(), i, j) + indexed(A, sy_anti(), j, i), 0); + result += check_equal(indexed(A, sy_anti(), i, j, k) - indexed(A, sy_anti(), j, k, i), 0); + e = indexed(A, sy_symm(), i, j, k) * + indexed(B, sy_anti(), l, k, i); + result += check_equal_simplify(e, 0); + e = indexed(A, sy_symm(), i, i, j, j) * + indexed(B, sy_anti(), k, l); // GiNaC 0.8.0 had a bug here + result += check_equal_simplify(e, e); + + symmetry R = sy_symm(sy_anti(0, 1), sy_anti(2, 3)); + e = indexed(A, R, i, j, k, l) + indexed(A, R, j, i, k, l); + result += check_equal(e, 0); + e = indexed(A, R, i, j, k, l) + indexed(A, R, i, j, l, k); + result += check_equal(e, 0); + e = indexed(A, R, i, j, k, l) - indexed(A, R, j, i, l, k); + result += check_equal(e, 0); + e = indexed(A, R, i, j, k, l) + indexed(A, R, k, l, j, i); + result += check_equal(e, 0); + + e = indexed(A, i, j); + result += check_equal(symmetrize(e) + antisymmetrize(e), e); + e = indexed(A, sy_symm(), i, j, k, l); + result += check_equal(symmetrize(e), e); + result += check_equal(antisymmetrize(e), 0); + e = indexed(A, sy_anti(), i, j, k, l); + result += check_equal(symmetrize(e), 0); + result += check_equal(antisymmetrize(e), e); + + e = (indexed(A, sy_anti(), i, j, k, l) * (indexed(B, j) * indexed(C, k) + indexed(B, k) * indexed(C, j)) + indexed(B, i, l)).expand(); + result += check_equal_simplify(e, indexed(B, i, l)); + + result += check_equal(symm_fcn(0, 1) + symm_fcn(1, 0), 2*symm_fcn(0, 1)); + result += check_equal(anti_fcn(0, 1) + anti_fcn(1, 0), 0); + result += check_equal(anti_fcn(0, 0), 0); + + return result; +} + +static unsigned scalar_product_check() +{ + // check scalar product replacement + + unsigned result = 0; + + idx i(symbol("i"), 3), j(symbol("j"), 3); + symbol A("A"), B("B"), C("C"); + ex e; + + scalar_products sp; + sp.add(A, B, 0); // A and B are orthogonal + sp.add(A, C, 0); // A and C are orthogonal + sp.add(A, A, 4); // A^2 = 4 (A has length 2) + + e = (indexed(A + B, i) * indexed(A + C, i)).expand(expand_options::expand_indexed); + result += check_equal_simplify(e, indexed(B, i) * indexed(C, i) + 4, sp); + e = indexed(A, i, i) * indexed(B, j, j); // GiNaC 0.8.0 had a bug here + result += check_equal_simplify(e, e, sp); + + return result; +} + +static unsigned edyn_check() +{ + // Relativistic electrodynamics + + // Test 1: check transformation laws of electric and magnetic fields by + // applying a Lorentz boost to the field tensor + + unsigned result = 0; + + symbol beta("beta"); + ex gamma = 1 / sqrt(1 - pow(beta, 2)); + symbol Ex("Ex"), Ey("Ey"), Ez("Ez"); + symbol Bx("Bx"), By("By"), Bz("Bz"); + + // Lorentz transformation matrix (boost along x axis) + matrix L(4, 4); + L = gamma, -beta*gamma, 0, 0, + -beta*gamma, gamma, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1; + + // Electromagnetic field tensor + matrix F(4, 4); + F = 0, -Ex, -Ey, -Ez, + Ex, 0, -Bz, By, + Ey, Bz, 0, -Bx, + Ez, -By, Bx, 0; + + // Indices + symbol s_mu("mu"), s_nu("nu"), s_rho("rho"), s_sigma("sigma"); + varidx mu(s_mu, 4), nu(s_nu, 4), rho(s_rho, 4), sigma(s_sigma, 4); + + // Apply transformation law of second rank tensor + ex e = (indexed(L, mu, rho.toggle_variance()) + * indexed(L, nu, sigma.toggle_variance()) + * indexed(F, rho, sigma)).simplify_indexed(); + + // Extract transformed electric and magnetic fields + ex Ex_p = e.subs(lst(mu == 1, nu == 0)).normal(); + ex Ey_p = e.subs(lst(mu == 2, nu == 0)).normal(); + ex Ez_p = e.subs(lst(mu == 3, nu == 0)).normal(); + ex Bx_p = e.subs(lst(mu == 3, nu == 2)).normal(); + ex By_p = e.subs(lst(mu == 1, nu == 3)).normal(); + ex Bz_p = e.subs(lst(mu == 2, nu == 1)).normal(); + + // Check results + result += check_equal(Ex_p, Ex); + result += check_equal(Ey_p, gamma * (Ey - beta * Bz)); + result += check_equal(Ez_p, gamma * (Ez + beta * By)); + result += check_equal(Bx_p, Bx); + result += check_equal(By_p, gamma * (By + beta * Ez)); + result += check_equal(Bz_p, gamma * (Bz - beta * Ey)); + + // Test 2: check energy density and Poynting vector of electromagnetic field + + // Minkowski metric + ex eta = diag_matrix(lst(1, -1, -1, -1)); + + // Covariant field tensor + ex F_mu_nu = (indexed(eta, mu.toggle_variance(), rho.toggle_variance()) + * indexed(eta, nu.toggle_variance(), sigma.toggle_variance()) + * indexed(F, rho, sigma)).simplify_indexed(); + + // Energy-momentum tensor + ex T = (-indexed(eta, rho, sigma) * F_mu_nu.subs(s_nu == s_rho) + * F_mu_nu.subs(lst(s_mu == s_nu, s_nu == s_sigma)) + + indexed(eta, mu.toggle_variance(), nu.toggle_variance()) + * F_mu_nu.subs(lst(s_mu == s_rho, s_nu == s_sigma)) + * indexed(F, rho, sigma) / 4).simplify_indexed() / (4 * Pi); + + // Extract energy density and Poynting vector + ex E = T.subs(lst(s_mu == 0, s_nu == 0)).normal(); + ex Px = T.subs(lst(s_mu == 0, s_nu == 1)); + ex Py = T.subs(lst(s_mu == 0, s_nu == 2)); + ex Pz = T.subs(lst(s_mu == 0, s_nu == 3)); + + // Check results + result += check_equal(E, (Ex*Ex+Ey*Ey+Ez*Ez+Bx*Bx+By*By+Bz*Bz) / (8 * Pi)); + result += check_equal(Px, (Ez*By-Ey*Bz) / (4 * Pi)); + result += check_equal(Py, (Ex*Bz-Ez*Bx) / (4 * Pi)); + result += check_equal(Pz, (Ey*Bx-Ex*By) / (4 * Pi)); + + return result; +} + +static unsigned spinor_check() +{ + // check identities of the spinor metric + + unsigned result = 0; + + symbol psi("psi"); + spinidx A(symbol("A")), B(symbol("B")), C(symbol("C")), D(symbol("D")); + ex A_co = A.toggle_variance(), B_co = B.toggle_variance(); + ex e; + + e = spinor_metric(A_co, B_co) * spinor_metric(A, B); + result += check_equal_simplify(e, 2); + e = spinor_metric(A_co, B_co) * spinor_metric(B, A); + result += check_equal_simplify(e, -2); + e = spinor_metric(A_co, B_co) * spinor_metric(A, C); + result += check_equal_simplify(e, delta_tensor(B_co, C)); + e = spinor_metric(A_co, B_co) * spinor_metric(B, C); + result += check_equal_simplify(e, -delta_tensor(A_co, C)); + e = spinor_metric(A_co, B_co) * spinor_metric(C, A); + result += check_equal_simplify(e, -delta_tensor(B_co, C)); + e = spinor_metric(A, B) * indexed(psi, B_co); + result += check_equal_simplify(e, indexed(psi, A)); + e = spinor_metric(A, B) * indexed(psi, A_co); + result += check_equal_simplify(e, -indexed(psi, B)); + e = spinor_metric(A_co, B_co) * indexed(psi, B); + result += check_equal_simplify(e, -indexed(psi, A_co)); + e = spinor_metric(A_co, B_co) * indexed(psi, A); + result += check_equal_simplify(e, indexed(psi, B_co)); + e = spinor_metric(D, A) * spinor_metric(A_co, B_co) * spinor_metric(B, C) - spinor_metric(D, A_co) * spinor_metric(A, B_co) * spinor_metric(B, C); + result += check_equal_simplify(e, 0); + + return result; +} + +static unsigned dummy_check() +{ + // check dummy index renaming/repositioning + + unsigned result = 0; + + symbol p("p"), q("q"); + idx i(symbol("i"), 3), j(symbol("j"), 3), n(symbol("n"), 3); + varidx mu(symbol("mu"), 4), nu(symbol("nu"), 4); + ex e; + + e = indexed(p, i) * indexed(q, i) - indexed(p, j) * indexed(q, j); + result += check_equal_simplify(e, 0); + + e = indexed(p, i) * indexed(p, i) * indexed(q, j) * indexed(q, j) + - indexed(p, n) * indexed(p, n) * indexed(q, j) * indexed(q, j); + result += check_equal_simplify(e, 0); + + e = indexed(p, mu, mu.toggle_variance()) - indexed(p, nu, nu.toggle_variance()); + result += check_equal_simplify(e, 0); + + e = indexed(p, mu.toggle_variance(), nu, mu) * indexed(q, i) + - indexed(p, mu, nu, mu.toggle_variance()) * indexed(q, i); + result += check_equal_simplify(e, 0); + + e = indexed(p, mu, mu.toggle_variance()) - indexed(p, nu.toggle_variance(), nu); + result += check_equal_simplify(e, 0); + e = indexed(p, mu.toggle_variance(), mu) - indexed(p, nu, nu.toggle_variance()); + result += check_equal_simplify(e, 0); + + // GiNaC 1.2.1 had a bug here because p.i*p.i -> (p.i)^2 + e = indexed(p, i) * indexed(p, i) * indexed(p, j) + indexed(p, j); + ex fi = exprseq(e.get_free_indices()); + if (!fi.is_equal(exprseq(j))) { + clog << "get_free_indices(" << e << ") erroneously returned " + << fi << " instead of (.j)" << endl; + ++result; + } + + return result; +} + +unsigned exam_indexed() +{ + unsigned result = 0; + + cout << "examining indexed objects" << flush; + clog << "----------indexed objects:" << endl; + + result += delta_check(); cout << '.' << flush; + result += metric_check(); cout << '.' << flush; + result += epsilon_check(); cout << '.' << flush; + result += symmetry_check(); cout << '.' << flush; + result += scalar_product_check(); cout << '.' << flush; + result += edyn_check(); cout << '.' << flush; + result += spinor_check(); cout << '.' << flush; + result += dummy_check(); cout << '.' << flush; + + if (!result) { + cout << " passed " << endl; + clog << "(no output)" << endl; + } else { + cout << " failed " << endl; + } + + return result; +} diff --git a/check/exam_pseries.cpp b/check/exam_pseries.cpp new file mode 100644 index 00000000..4021c4fc --- /dev/null +++ b/check/exam_pseries.cpp @@ -0,0 +1,379 @@ +/** @File exam_pseries.cpp + * + * Series expansion test (Laurent and Taylor series). */ + +/* + * GiNaC Copyright (C) 1999-2005 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "exams.h" + +static symbol x("x"); + +static unsigned check_series(const ex &e, const ex &point, const ex &d, int order = 8) +{ + ex es = e.series(x==point, order); + ex ep = ex_to(es).convert_to_poly(); + if (!(ep - d).expand().is_zero()) { + clog << "series expansion of " << e << " at " << point + << " erroneously returned " << ep << " (instead of " << d + << ")" << endl; + clog << tree << (ep-d) << dflt; + return 1; + } + return 0; +} + +// Series expansion +static unsigned exam_series1() +{ + using GiNaC::log; + + symbol a("a"); + symbol b("b"); + unsigned result = 0; + ex e, d; + + e = pow(a+b, x); + d = 1 + Order(pow(x, 1)); + result += check_series(e, 0, d, 1); + + e = sin(x); + d = x - pow(x, 3) / 6 + pow(x, 5) / 120 - pow(x, 7) / 5040 + Order(pow(x, 8)); + result += check_series(e, 0, d); + + e = cos(x); + d = 1 - pow(x, 2) / 2 + pow(x, 4) / 24 - pow(x, 6) / 720 + Order(pow(x, 8)); + result += check_series(e, 0, d); + + e = exp(x); + d = 1 + x + pow(x, 2) / 2 + pow(x, 3) / 6 + pow(x, 4) / 24 + pow(x, 5) / 120 + pow(x, 6) / 720 + pow(x, 7) / 5040 + Order(pow(x, 8)); + result += check_series(e, 0, d); + + e = pow(1 - x, -1); + d = 1 + x + pow(x, 2) + pow(x, 3) + pow(x, 4) + pow(x, 5) + pow(x, 6) + pow(x, 7) + Order(pow(x, 8)); + result += check_series(e, 0, d); + + e = x + pow(x, -1); + d = x + pow(x, -1); + result += check_series(e, 0, d); + + e = x + pow(x, -1); + d = 2 + pow(x-1, 2) - pow(x-1, 3) + pow(x-1, 4) - pow(x-1, 5) + pow(x-1, 6) - pow(x-1, 7) + Order(pow(x-1, 8)); + result += check_series(e, 1, d); + + e = pow(x + pow(x, 3), -1); + d = pow(x, -1) - x + pow(x, 3) - pow(x, 5) + pow(x, 7) + Order(pow(x, 8)); + result += check_series(e, 0, d); + + e = pow(pow(x, 2) + pow(x, 4), -1); + d = pow(x, -2) - 1 + pow(x, 2) - pow(x, 4) + pow(x, 6) + Order(pow(x, 8)); + result += check_series(e, 0, d); + + e = pow(sin(x), -2); + d = pow(x, -2) + numeric(1,3) + pow(x, 2) / 15 + pow(x, 4) * 2/189 + pow(x, 6) / 675 + Order(pow(x, 8)); + result += check_series(e, 0, d); + + e = sin(x) / cos(x); + d = x + pow(x, 3) / 3 + pow(x, 5) * 2/15 + pow(x, 7) * 17/315 + Order(pow(x, 8)); + result += check_series(e, 0, d); + + e = cos(x) / sin(x); + d = pow(x, -1) - x / 3 - pow(x, 3) / 45 - pow(x, 5) * 2/945 - pow(x, 7) / 4725 + Order(pow(x, 8)); + result += check_series(e, 0, d); + + e = pow(numeric(2), x); + ex t = log(2) * x; + d = 1 + t + pow(t, 2) / 2 + pow(t, 3) / 6 + pow(t, 4) / 24 + pow(t, 5) / 120 + pow(t, 6) / 720 + pow(t, 7) / 5040 + Order(pow(x, 8)); + result += check_series(e, 0, d.expand()); + + e = pow(Pi, x); + t = log(Pi) * x; + d = 1 + t + pow(t, 2) / 2 + pow(t, 3) / 6 + pow(t, 4) / 24 + pow(t, 5) / 120 + pow(t, 6) / 720 + pow(t, 7) / 5040 + Order(pow(x, 8)); + result += check_series(e, 0, d.expand()); + + e = log(x); + d = e; + result += check_series(e, 0, d, 1); + result += check_series(e, 0, d, 2); + + e = pow(x, 8) * pow(pow(x,3)+ pow(x + pow(x,3), 2), -2); + d = pow(x, 4) - 2*pow(x, 5) + Order(pow(x, 6)); + result += check_series(e, 0, d, 6); + + e = cos(x) * pow(sin(x)*(pow(x, 5) + 4 * pow(x, 2)), -3); + d = pow(x, -9) / 64 - 3 * pow(x, -6) / 256 - pow(x, -5) / 960 + 535 * pow(x, -3) / 96768 + + pow(x, -2) / 1280 - pow(x, -1) / 14400 - numeric(283, 129024) - 2143 * x / 5322240 + + Order(pow(x, 2)); + result += check_series(e, 0, d, 2); + + e = sqrt(1+x*x) * sqrt(1+2*x*x); + d = 1 + Order(pow(x, 2)); + result += check_series(e, 0, d, 2); + + e = pow(x, 4) * sin(a) + pow(x, 2); + d = pow(x, 2) + Order(pow(x, 3)); + result += check_series(e, 0, d, 3); + + e = log(a*x + b*x*x*log(x)); + d = log(a*x) + b/a*log(x)*x - pow(b/a, 2)/2*pow(log(x)*x, 2) + Order(pow(x, 3)); + result += check_series(e, 0, d, 3); + + e = pow((x+a), b); + d = pow(a, b) + (pow(a, b)*b/a)*x + (pow(a, b)*b*b/a/a/2 - pow(a, b)*b/a/a/2)*pow(x, 2) + Order(pow(x, 3)); + result += check_series(e, 0, d, 3); + + return result; +} + +// Series addition +static unsigned exam_series2() +{ + unsigned result = 0; + ex e, d; + + e = pow(sin(x), -1).series(x==0, 8) + pow(sin(-x), -1).series(x==0, 12); + d = Order(pow(x, 8)); + result += check_series(e, 0, d); + + return result; +} + +// Series multiplication +static unsigned exam_series3() +{ + unsigned result = 0; + ex e, d; + + e = sin(x).series(x==0, 8) * pow(sin(x), -1).series(x==0, 12); + d = 1 + Order(pow(x, 7)); + result += check_series(e, 0, d); + + return result; +} + +// Series exponentiation +static unsigned exam_series4() +{ + unsigned result = 0; + ex e, d; + + e = pow((2*cos(x)).series(x==0, 5), 2).series(x==0, 5); + d = 4 - 4*pow(x, 2) + 4*pow(x, 4)/3 + Order(pow(x, 5)); + result += check_series(e, 0, d); + + e = pow(tgamma(x), 2).series(x==0, 2); + d = pow(x,-2) - 2*Euler/x + (pow(Pi,2)/6+2*pow(Euler,2)) + + x*(-4*pow(Euler, 3)/3 -pow(Pi,2)*Euler/3 - 2*zeta(3)/3) + Order(pow(x, 2)); + result += check_series(e, 0, d); + + return result; +} + +// Order term handling +static unsigned exam_series5() +{ + unsigned result = 0; + ex e, d; + + e = 1 + x + pow(x, 2) + pow(x, 3); + d = Order(1); + result += check_series(e, 0, d, 0); + d = 1 + Order(x); + result += check_series(e, 0, d, 1); + d = 1 + x + Order(pow(x, 2)); + result += check_series(e, 0, d, 2); + d = 1 + x + pow(x, 2) + Order(pow(x, 3)); + result += check_series(e, 0, d, 3); + d = 1 + x + pow(x, 2) + pow(x, 3); + result += check_series(e, 0, d, 4); + return result; +} + +// Series expansion of tgamma(-1) +static unsigned exam_series6() +{ + ex e = tgamma(2*x); + ex d = pow(x+1,-1)*numeric(1,4) + + pow(x+1,0)*(numeric(3,4) - + numeric(1,2)*Euler) + + pow(x+1,1)*(numeric(7,4) - + numeric(3,2)*Euler + + numeric(1,2)*pow(Euler,2) + + numeric(1,12)*pow(Pi,2)) + + pow(x+1,2)*(numeric(15,4) - + numeric(7,2)*Euler - + numeric(1,3)*pow(Euler,3) + + numeric(1,4)*pow(Pi,2) + + numeric(3,2)*pow(Euler,2) - + numeric(1,6)*pow(Pi,2)*Euler - + numeric(2,3)*zeta(3)) + + pow(x+1,3)*(numeric(31,4) - pow(Euler,3) - + numeric(15,2)*Euler + + numeric(1,6)*pow(Euler,4) + + numeric(7,2)*pow(Euler,2) + + numeric(7,12)*pow(Pi,2) - + numeric(1,2)*pow(Pi,2)*Euler - + numeric(2)*zeta(3) + + numeric(1,6)*pow(Euler,2)*pow(Pi,2) + + numeric(1,40)*pow(Pi,4) + + numeric(4,3)*zeta(3)*Euler) + + Order(pow(x+1,4)); + return check_series(e, -1, d, 4); +} + +// Series expansion of tan(x==Pi/2) +static unsigned exam_series7() +{ + ex e = tan(x*Pi/2); + ex d = pow(x-1,-1)/Pi*(-2) + pow(x-1,1)*Pi/6 + pow(x-1,3)*pow(Pi,3)/360 + +pow(x-1,5)*pow(Pi,5)/15120 + pow(x-1,7)*pow(Pi,7)/604800 + +Order(pow(x-1,9)); + return check_series(e,1,d,9); +} + +// Series expansion of log(sin(x==0)) +static unsigned exam_series8() +{ + ex e = log(sin(x)); + ex d = log(x) - pow(x,2)/6 - pow(x,4)/180 - pow(x,6)/2835 - pow(x,8)/37800 + Order(pow(x,9)); + return check_series(e,0,d,9); +} + +// Series expansion of Li2(sin(x==0)) +static unsigned exam_series9() +{ + ex e = Li2(sin(x)); + ex d = x + pow(x,2)/4 - pow(x,3)/18 - pow(x,4)/48 + - 13*pow(x,5)/1800 - pow(x,6)/360 - 23*pow(x,7)/21168 + + Order(pow(x,8)); + return check_series(e,0,d,8); +} + +// Series expansion of Li2((x==2)^2), caring about branch-cut +static unsigned exam_series10() +{ + using GiNaC::log; + + ex e = Li(2, pow(x,2)); + ex d = Li(2, 4) + (-log(3) + I*Pi*csgn(I-I*pow(x,2))) * (x-2) + + (numeric(-2,3) + log(3)/4 - I*Pi/4*csgn(I-I*pow(x,2))) * pow(x-2,2) + + (numeric(11,27) - log(3)/12 + I*Pi/12*csgn(I-I*pow(x,2))) * pow(x-2,3) + + (numeric(-155,648) + log(3)/32 - I*Pi/32*csgn(I-I*pow(x,2))) * pow(x-2,4) + + Order(pow(x-2,5)); + return check_series(e,2,d,5); +} + +// Series expansion of logarithms around branch points +static unsigned exam_series11() +{ + using GiNaC::log; + + unsigned result = 0; + ex e, d; + symbol a("a"); + + e = log(x); + d = log(x); + result += check_series(e,0,d,5); + + e = log(3/x); + d = log(3)-log(x); + result += check_series(e,0,d,5); + + e = log(3*pow(x,2)); + d = log(3)+2*log(x); + result += check_series(e,0,d,5); + + // These ones must not be expanded because it would result in a branch cut + // running in the wrong direction. (Other systems tend to get this wrong.) + e = log(-x); + d = e; + result += check_series(e,0,d,5); + + e = log(I*(x-123)); + d = e; + result += check_series(e,123,d,5); + + e = log(a*x); + d = e; // we don't know anything about a! + result += check_series(e,0,d,5); + + e = log((1-x)/x); + d = log(1-x) - (x-1) + pow(x-1,2)/2 - pow(x-1,3)/3 + pow(x-1,4)/4 + Order(pow(x-1,5)); + result += check_series(e,1,d,5); + + return result; +} + +// Series expansion of other functions around branch points +static unsigned exam_series12() +{ + using GiNaC::log; + + unsigned result = 0; + ex e, d; + + // NB: Mma and Maple give different results, but they agree if one + // takes into account that by assumption |x|<1. + e = atan(x); + d = (I*log(2)/2-I*log(1+I*x)/2) + (x-I)/4 + I*pow(x-I,2)/16 + Order(pow(x-I,3)); + result += check_series(e,I,d,3); + + // NB: here, at -I, Mathematica disagrees, but it is wrong -- they + // pick up a complex phase by incorrectly expanding logarithms. + e = atan(x); + d = (-I*log(2)/2+I*log(1-I*x)/2) + (x+I)/4 - I*pow(x+I,2)/16 + Order(pow(x+I,3)); + result += check_series(e,-I,d,3); + + // This is basically the same as above, the branch point is at +/-1: + e = atanh(x); + d = (-log(2)/2+log(x+1)/2) + (x+1)/4 + pow(x+1,2)/16 + Order(pow(x+1,3)); + result += check_series(e,-1,d,3); + + return result; +} + + +unsigned exam_pseries() +{ + unsigned result = 0; + + cout << "examining series expansion" << flush; + clog << "----------series expansion:" << endl; + + result += exam_series1(); cout << '.' << flush; + result += exam_series2(); cout << '.' << flush; + result += exam_series3(); cout << '.' << flush; + result += exam_series4(); cout << '.' << flush; + result += exam_series5(); cout << '.' << flush; + result += exam_series6(); cout << '.' << flush; + result += exam_series7(); cout << '.' << flush; + result += exam_series8(); cout << '.' << flush; + result += exam_series9(); cout << '.' << flush; + result += exam_series10(); cout << '.' << flush; + result += exam_series11(); cout << '.' << flush; + result += exam_series12(); cout << '.' << flush; + + if (!result) { + cout << " passed " << endl; + clog << "(no output)" << endl; + } else { + cout << " failed " << endl; + } + return result; +} diff --git a/ginac/Makefile.am b/ginac/Makefile.am index 0907823a..df3e8477 100644 --- a/ginac/Makefile.am +++ b/ginac/Makefile.am @@ -1,33 +1,24 @@ ## Process this file with automake to produce Makefile.in lib_LTLIBRARIES = libginac.la -libginac_la_SOURCES = add.cpp basic.cpp constant.cpp diff.cpp ex.cpp \ - expairseq.cpp exprseq.cpp fail.cpp function.cpp inifcns.cpp \ - inifcns_trans.cpp inifcns_zeta.cpp inifcns_gamma.cpp matrix.cpp mul.cpp \ - normal.cpp numeric.cpp operators.cpp power.cpp print.cpp printraw.cpp \ - printtree.cpp printcsrc.cpp relational.cpp symbol.cpp utils.cpp series.cpp \ - ncmul.cpp clifford.cpp structure.cpp color.cpp indexed.cpp idx.cpp \ - isospin.cpp exprseq_suppl.cpp lst.cpp lst_suppl.cpp simp_lor.cpp \ - coloridx.cpp lorentzidx.cpp debugmsg.h utils.h -libginac_la_LDFLAGS = -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \ - -release $(LT_RELEASE) +libginac_la_SOURCES = add.cpp archive.cpp basic.cpp clifford.cpp color.cpp \ + constant.cpp ex.cpp excompiler.cpp expair.cpp expairseq.cpp exprseq.cpp \ + fail.cpp function.cpp idx.cpp indexed.cpp inifcns.cpp inifcns_exp.cpp \ + inifcns_polylog.cpp inifcns_trig.cpp \ + integral.cpp lst.cpp matrix.cpp mul.cpp ncmul.cpp normal.cpp numeric.cpp \ + operators.cpp power.cpp registrar.cpp relational.cpp \ + pseries.cpp print.cpp symbol.cpp symmetry.cpp tensor.cpp \ + utils.cpp wildcard.cpp input_parser.yy input_lexer.ll \ + input_lexer.h tostring.h utils.h +libginac_la_LDFLAGS = -version-info $(LT_VERSION_INFO) -release $(LT_RELEASE) ginacincludedir = $(includedir)/ginac -ginacinclude_HEADERS = ginac.h add.h basic.h clifford.h color.h coloridx.h \ - constant.h ex.h expair.h expairseq.h exprseq.h fail.h flags.h function.h \ - idx.h indexed.h inifcns.h isospin.h lorentzidx.h lst.h matrix.h mul.h \ - ncmul.h normal.h numeric.h operators.h power.h relational.h series.h \ - simp_lor.h structure.h symbol.h tinfos.h assertion.h -EXTRA_DIST = container.pl function.pl structure.pl - -# Files which are generated by perl scripts -function.h function.cpp: function.pl - perl function.pl - -lst.h lst.cpp: container.pl - perl container.pl lst - -exprseq.h exprseq.cpp: container.pl - perl container.pl exprseq - -# Force build of headers before compilation -add.cpp: function.h lst.h exprseq.h +ginacinclude_HEADERS = ginac.h add.h archive.h assertion.h basic.h class_info.h \ + clifford.h color.h constant.h container.h ex.h excompiler.h expair.h expairseq.h \ + exprseq.h fail.h flags.h function.h hash_map.h idx.h indexed.h inifcns.h \ + inifcns_exp.h inifcns_polylog.h inifcns_trig.h \ + integral.h lst.h matrix.h mul.h ncmul.h normal.h numeric.h operators.h \ + power.h print.h pseries.h ptr.h registrar.h relational.h structure.h \ + symbol.h symmetry.h tensor.h version.h wildcard.h +AM_LFLAGS = -Pginac_yy -olex.yy.c +AM_YFLAGS = -p ginac_yy -d +EXTRA_DIST = input_parser.h version.h.in diff --git a/ginac/flags.h b/ginac/flags.h index 90f4e38d..e2651530 100644 --- a/ginac/flags.h +++ b/ginac/flags.h @@ -3,7 +3,7 @@ * Collection of all flags used through the GiNaC framework. */ /* - * GiNaC Copyright (C) 1999 Johannes Gutenberg University Mainz, Germany + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __GINAC_FLAGS_H__ @@ -25,92 +25,239 @@ namespace GiNaC { +/** Flags to control the behavior of expand(). */ class expand_options { public: - enum { expand_trigonometric = 0x0001 - }; + enum { + expand_indexed = 0x0001, ///< expands (a+b).i to a.i+b.i + expand_function_args = 0x0002 ///< expands the arguments of functions + }; }; -class status_flags { +/** Flags to control the behavior of has(). */ +class has_options { public: - enum { dynallocated = 0x0001, - evaluated = 0x0002, - expanded = 0x0004, - hash_calculated = 0x0008 - }; + enum { + algebraic = 0x0001, ///< enable algebraic matching + }; }; -class info_flags { +/** Flags to control the behavior of subs(). */ +class subs_options { public: - enum { - // answered by class numeric - numeric, - real, - rational, - integer, - positive, - negative, - nonnegative, - posint, - negint, - nonnegint, - even, - odd, - prime, - - // answered by class relation - relation, - relation_equal, - relation_not_equal, - relation_less, - relation_less_or_equal, - relation_greater, - relation_greater_or_equal, - - // answered by class symbol - symbol, - - // answered by class lst - list, - - // answered by class exprseq - exprseq, - - // answered by classes numeric, symbol, add, mul, power - polynomial, - integer_polynomial, - rational_polynomial, - rational_function, - - // answered by class ex - normal_form, - - // answered by class indexed - indexed, // class can carry indices - has_indices, // object has at least one index - - // answered by class idx - idx, - - // answered by class coloridx - coloridx, - - // answered by class lorentzidx - lorentzidx - }; + enum { + no_pattern = 0x0001, ///< disable pattern matching + subs_no_pattern = 0x0001, // for backwards compatibility + algebraic = 0x0002, ///< enable algebraic substitutions + subs_algebraic = 0x0002, // for backwards compatibility + pattern_is_product = 0x0004, ///< used internally by expairseq::subschildren() + pattern_is_not_product = 0x0008, ///< used internally by expairseq::subschildren() + no_index_renaming = 0x0010 + }; }; -class return_types { +/** Domain of an object */ +class domain { +public: + enum { + complex, + real + }; +}; + +/** Flags to control series expansion. */ +class series_options { +public: + enum { + /** Suppress branch cuts in series expansion. Branch cuts manifest + * themselves as step functions, if this option is not passed. If + * it is passed and expansion at a point on a cut is performed, then + * the analytic continuation of the function is expanded. */ + suppress_branchcut = 0x0001 + }; +}; + +/** Switch to control algorithm for determinant computation. */ +class determinant_algo { public: - enum { commutative, noncommutative, noncommutative_composite}; + enum { + /** Let the system choose. A heuristics is applied for automatic + * determination of a suitable algorithm. */ + automatic, + /** Gauss elimination. If \f$m_{i,j}^{(0)}\f$ are the entries of the + * original matrix, then the matrix is transformed into triangular + * form by applying the rules + * \f[ + * m_{i,j}^{(k+1)} = m_{i,j}^{(k)} - m_{i,k}^{(k)} m_{k,j}^{(k)} / m_{k,k}^{(k)} + * \f] + * The determinant is then just the product of diagonal elements. + * Choose this algorithm only for purely numerical matrices. */ + gauss, + /** Division-free elimination. This is a modification of Gauss + * elimination where the division by the pivot element is not + * carried out. If \f$m_{i,j}^{(0)}\f$ are the entries of the + * original matrix, then the matrix is transformed into triangular + * form by applying the rules + * \f[ + * m_{i,j}^{(k+1)} = m_{i,j}^{(k)} m_{k,k}^{(k)} - m_{i,k}^{(k)} m_{k,j}^{(k)} + * \f] + * The determinant can later be computed by inspecting the diagonal + * elements only. This algorithm is only there for the purpose of + * cross-checks. It is never fast. */ + divfree, + /** Laplace elimination. This is plain recursive elimination along + * minors although multiple minors are avoided by the algorithm. + * Although the algorithm is exponential in complexity it is + * frequently the fastest one when the matrix is populated by + * complicated symbolic expressions. */ + laplace, + /** Bareiss fraction-free elimination. This is a modification of + * Gauss elimination where the division by the pivot element is + * delayed until it can be carried out without computing + * GCDs. If \f$m_{i,j}^{(0)}\f$ are the entries of the original + * matrix, then the matrix is transformed into triangular form by + * applying the rules + * \f[ + * m_{i,j}^{(k+1)} = (m_{i,j}^{(k)} m_{k,k}^{(k)} - m_{i,k}^{(k)} m_{k,j}^{(k)}) / m_{k-1,k-1}^{(k-1)} + * \f] + * (We have set \f$m_{-1,-1}^{(-1)}=1\f$ in order to avoid a case + * distinction in above formula.) It can be shown that nothing more + * than polynomial long division is needed for carrying out the + * division. The determinant can then be read of from the lower + * right entry. This algorithm is rarely fast for computing + * determinants. */ + bareiss + }; }; -class csrc_types { +/** Switch to control algorithm for linear system solving. */ +class solve_algo { +public: + enum { + /** Let the system choose. A heuristics is applied for automatic + * determination of a suitable algorithm. */ + automatic, + /** Gauss elimination. If \f$m_{i,j}^{(0)}\f$ are the entries of the + * original matrix, then the matrix is transformed into triangular + * form by applying the rules + * \f[ + * m_{i,j}^{(k+1)} = m_{i,j}^{(k)} - m_{i,k}^{(k)} m_{k,j}^{(k)} / m_{k,k}^{(k)} + * \f] + * This algorithm is well-suited for numerical matrices but generally + * suffers from the expensive division (and computation of GCDs) at + * each step. */ + gauss, + /** Division-free elimination. This is a modification of Gauss + * elimination where the division by the pivot element is not + * carried out. If \f$m_{i,j}^{(0)}\f$ are the entries of the + * original matrix, then the matrix is transformed into triangular + * form by applying the rules + * \f[ + * m_{i,j}^{(k+1)} = m_{i,j}^{(k)} m_{k,k}^{(k)} - m_{i,k}^{(k)} m_{k,j}^{(k)} + * \f] + * This algorithm is only there for the purpose of cross-checks. + * It suffers from exponential intermediate expression swell. Use it + * only for small systems. */ + divfree, + /** Bareiss fraction-free elimination. This is a modification of + * Gauss elimination where the division by the pivot element is + * delayed until it can be carried out without computing + * GCDs. If \f$m_{i,j}^{(0)}\f$ are the entries of the original + * matrix, then the matrix is transformed into triangular form by + * applying the rules + * \f[ + * m_{i,j}^{(k+1)} = (m_{i,j}^{(k)} m_{k,k}^{(k)} - m_{i,k}^{(k)} m_{k,j}^{(k)}) / m_{k-1,k-1}^{(k-1)} + * \f] + * (We have set \f$m_{-1,-1}^{(-1)}=1\f$ in order to avoid a case + * distinction in above formula.) It can be shown that nothing more + * than polynomial long division is needed for carrying out the + * division. This is generally the fastest algorithm for solving + * linear systems. In contrast to division-free elimination it only + * has a linear expression swell. For two-dimensional systems, the + * two algorithms are equivalent, however. */ + bareiss + }; +}; + +/** Flags to store information about the state of an object. + * @see basic::flags */ +class status_flags { +public: + enum { + dynallocated = 0x0001, ///< heap-allocated (i.e. created by new if we want to be clever and bypass the stack, @see ex::construct_from_basic() ) + evaluated = 0x0002, ///< .eval() has already done its job + expanded = 0x0004, ///< .expand(0) has already done its job (other expand() options ignore this flag) + hash_calculated = 0x0008, ///< .calchash() has already done its job + not_shareable = 0x0010 ///< don't share instances of this object between different expressions unless explicitly asked to (used by ex::compare()) + }; +}; + +/** Possible attributes an object can have. */ +class info_flags { +public: + enum { + // answered by class numeric + numeric, + real, + rational, + integer, + crational, + cinteger, + positive, + negative, + nonnegative, + posint, + negint, + nonnegint, + even, + odd, + prime, + + // answered by class relation + relation, + relation_equal, + relation_not_equal, + relation_less, + relation_less_or_equal, + relation_greater, + relation_greater_or_equal, + + // answered by class symbol + symbol, + + // answered by class lst + list, + + // answered by class exprseq + exprseq, + + // answered by class function + function, + + // answered by classes numeric, symbol, add, mul, power + polynomial, + integer_polynomial, + cinteger_polynomial, + rational_polynomial, + crational_polynomial, + rational_function, + algebraic, + + // answered by class indexed + indexed, // class can carry indices + has_indices, // object has at least one index + + // answered by class idx + idx + }; +}; + +class return_types { public: enum { - ctype_float, - ctype_double, - ctype_cl_N + commutative, + noncommutative, + noncommutative_composite }; }; diff --git a/ginac/function.cpp b/ginac/function.cpp new file mode 100644 index 00000000..6a322db0 --- /dev/null +++ b/ginac/function.cpp @@ -0,0 +1,196 @@ +/** @file function.cpp + * + * Implementation of class of symbolic functions. */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "function.h" + +#include "inifcns.h" +#include "operators.h" +#include "power.h" +#include "utils.h" + +namespace GiNaC { + +GINAC_IMPLEMENT_REGISTERED_CLASS(function, exprseq) + +function::function(const archive_node& n, lst& sym_lst) : inherited(n, sym_lst) +{ +} + +void function::archive(archive_node& n) const +{ + inherited::archive(n); +} + +ex function::unarchive(const archive_node& n, lst& sym_lst) +{ + return (new function(n, sym_lst))->setflag(status_flags::dynallocated); +} + +function* function::duplicate() const +{ + return new function(*this); +} + +void function::accept(GiNaC::visitor& v) const +{ + if (visitor* p = dynamic_cast(&v)) { + p->visit(*this); + } else { + inherited::accept(v); + } +} + +int function::compare_same_type(const basic& other) const +{ + if (tinfo_key == other.tinfo()) { + return exprseq::compare_same_type(other); + } else { + return compare_pointers(tinfo_key, other.tinfo()); + } +} + +bool function::is_equal_same_type(const basic& other) const +{ + if (tinfo_key == other.tinfo()) { + return exprseq::is_equal_same_type(other); + } else { + return false; + } +} + +bool function::match_same_type(const basic & other) const +{ + return tinfo_key == other.tinfo(); +} + +unsigned function::return_type() const +{ +// GINAC_ASSERT(serialreturn_type(); + } +} + +tinfo_t function::return_type_tinfo() const +{ + //GINAC_ASSERT(serialreturn_type_tinfo(); + } +} + +ex function::conjugate() const +{ + return conjugate_function(*this); +} + +ex function::derivative(const symbol& s) const +{ + // Chain rule + ex arg_diff; + ex result; + size_t num = seq.size(); + for (size_t i=0; i& pdt = get_class_info().options.get_print_dispatch_table(); + unsigned id = c.get_class_info().options.get_id(); + if (id >= pdt.size() || !(pdt[id].is_valid())) { + if (is_a(c)) { + c.s << std::string(level, ' ') << class_name() + << " @" << this + << std::hex << ", hash=0x" << hashvalue << ", flags=0x" << flags << std::dec + << ", nops=" << nops() + << std::endl; + unsigned delta_indent = static_cast(c).delta_indent; + for (size_t i=0; i(c)) { + c.s << "\\mbox{" << class_name() << "}"; + inherited::do_print(c,level); + } else { + std::string classname(class_name()); + c.s << classname.erase(classname.find("_function",0),9); + inherited::do_print(c,level); + } + } else { + pdt[id](*this, c, level); + } +} + +} // namespace GiNaC diff --git a/ginac/function.h b/ginac/function.h new file mode 100644 index 00000000..7cacb58e --- /dev/null +++ b/ginac/function.h @@ -0,0 +1,193 @@ +/** @file function.h + * + * Interface to class of symbolic functions. */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __GINAC_FUNCTION_H__ +#define __GINAC_FUNCTION_H__ + +#include "exprseq.h" + +#include + +namespace GiNaC { + +class function : public exprseq +{ + GINAC_DECLARE_REGISTERED_CLASS_NO_CTORS(function, exprseq) +public: + virtual function* duplicate() const; + virtual void accept(GiNaC::visitor& v) const; + function(const exvector& v) : exprseq(v) { tinfo_key = &function::tinfo_static; } + function(std::auto_ptr vp) : exprseq(vp) { tinfo_key = &function::tinfo_static; } + function(tinfo_t ti, const exvector& vp) : exprseq(vp) { tinfo_key = ti; } + function(tinfo_t ti, std::auto_ptr vp) : exprseq(vp) { tinfo_key = ti; } + ex thiscontainer(const exvector& v) const { return function(v); } + ex thiscontainer(std::auto_ptr vp) const { return function(vp); } +protected: + virtual int compare_same_type(const basic& other) const; + virtual bool is_equal_same_type(const basic& other) const; + virtual bool match_same_type(const basic & other) const; + virtual unsigned return_type() const; + virtual tinfo_t return_type_tinfo() const; +public: + virtual ex conjugate() const; + virtual ex derivative(const symbol& s) const; + virtual ex pderivative(unsigned diff_param) const; + virtual ex expand(unsigned options) const; + virtual ex power_law(const ex& exp) const; +public: + virtual bool info(unsigned inf) const; + virtual void print(const print_context& c, unsigned level = 0) const; +public: + function() { } + function(tinfo_t ti) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1) : inherited(x1) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2) : inherited(x1, x2) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2, const ex& x3) : inherited(x1, x2, x3) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2, const ex& x3, const ex& x4) : inherited(x1, x2, x3, x4) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2, const ex& x3, const ex& x4, const ex& x5) : inherited(x1, x2, x3, x4, x5) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2, const ex& x3, const ex& x4, const ex& x5, const ex& x6) : inherited(x1, x2, x3, x4, x5, x6) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2, const ex& x3, const ex& x4, const ex& x5, const ex& x6, const ex& x7) : inherited(x1, x2, x3, x4, x5, x6, x7) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2, const ex& x3, const ex& x4, const ex& x5, const ex& x6, const ex& x7, const ex& x8) : inherited(x1, x2, x3, x4, x5, x6, x7, x8) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2, const ex& x3, const ex& x4, const ex& x5, const ex& x6, const ex& x7, const ex& x8, const ex& x9) : inherited(x1, x2, x3, x4, x5, x6, x7, x8, x9) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2, const ex& x3, const ex& x4, const ex& x5, const ex& x6, const ex& x7, const ex& x8, const ex& x9, const ex& x10) : inherited(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2, const ex& x3, const ex& x4, const ex& x5, const ex& x6, const ex& x7, const ex& x8, const ex& x9, const ex& x10, const ex& x11) : inherited(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) { tinfo_key = ti; } + function(tinfo_t ti, const ex& x1, const ex& x2, const ex& x3, const ex& x4, const ex& x5, const ex& x6, const ex& x7, const ex& x8, const ex& x9, const ex& x10, const ex& x11, const ex& x12) : inherited(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) { tinfo_key = ti; } +}; + +/** Macro for inclusion in the declaration of a GiNaC function. */ +#define GINAC_DECLARE_FUNCTION_NONSTD(classname) \ +public: \ + typedef function inherited; \ + static const tinfo_static_t tinfo_static; \ +private: \ + static GiNaC::registered_class_info reg_info; \ +public: \ + static GiNaC::registered_class_info& get_class_info_static() { return reg_info; } \ + virtual const GiNaC::registered_class_info& get_class_info() const { return classname::get_class_info_static(); } \ + virtual GiNaC::registered_class_info& get_class_info() { return classname::get_class_info_static(); } \ + virtual const char* class_name() const { return classname::get_class_info_static().options.get_name(); } \ + \ + classname(const GiNaC::archive_node& n, GiNaC::lst& sym_lst) : inherited(n, sym_lst) { } \ + virtual void archive(GiNaC::archive_node& n) const { inherited::archive(n); } \ + static GiNaC::ex unarchive(const GiNaC::archive_node& n, GiNaC::lst& sym_lst) { return (new classname(n, sym_lst))->setflag(status_flags::dynallocated); } \ + \ + static GiNaC::ex factory(const exvector& args) { return (new classname(args))->setflag(status_flags::dynallocated); } \ + \ + class visitor { \ + public: \ + virtual void visit(const classname&) = 0; \ + virtual ~visitor() {}; \ + }; \ + \ + virtual void accept(GiNaC::visitor& v) const \ + { \ + if (visitor* p = dynamic_cast(&v)) \ + p->visit(*this); \ + else \ + inherited::accept(v); \ + } \ + \ + virtual classname* duplicate() const { return new classname(*this); } \ +private: + +#define GINAC_DECLARE_FUNCTION(classname) \ +GINAC_DECLARE_FUNCTION_NONSTD(classname) \ +public: \ + classname(const exvector& v) : inherited(v) { tinfo_key = &classname::tinfo_static; } \ + classname(std::auto_ptr vp) : inherited(vp) { tinfo_key = &classname::tinfo_static; } \ + ex thiscontainer(const exvector& v) const { return classname(v); } \ + ex thiscontainer(std::auto_ptr vp) const { return classname(vp); } \ +private: + +#define GINAC_FUNCTION_CTOR_1P(classname) \ + public: \ + classname(const ex& x1) : inherited(&classname::tinfo_static, x1) { } + +#define GINAC_FUNCTION_CTOR_2P(classname) \ + public: \ + classname(const ex& x1, const ex& x2) : inherited(&classname::tinfo_static, x1, x2) { } \ + +#define GINAC_FUNCTION_CTOR_3P(classname) \ + public: \ + classname(const ex& x1, const ex& x2, const ex& x3) : inherited(&classname::tinfo_static, x1, x2, x3) { } \ + +/** Macro for inclusion in the declaration of a GiNaC function with one parameter. */ +#define GINAC_DECLARE_FUNCTION_1P(classname) \ + GINAC_DECLARE_FUNCTION(classname) \ + GINAC_FUNCTION_CTOR_1P(classname) \ + private: + +/** Macro for inclusion in the declaration of a GiNaC function with two parameters. */ +#define GINAC_DECLARE_FUNCTION_2P(classname) \ + GINAC_DECLARE_FUNCTION(classname) \ + GINAC_FUNCTION_CTOR_2P(classname) \ + private: + +/** Macro for inclusion in the declaration of a GiNaC function with two parameters. */ +#define GINAC_DECLARE_FUNCTION_3P(classname) \ + GINAC_DECLARE_FUNCTION(classname) \ + GINAC_FUNCTION_CTOR_3P(classname) \ + private: + +#define GINAC_FUNCTION_eval public: virtual ex eval(int level = 0) const; +#define GINAC_FUNCTION_evalf public: virtual ex evalf(int level = 0) const; +#define GINAC_FUNCTION_conjugate public: virtual ex conjugate() const; +#define GINAC_FUNCTION_power_law public: virtual ex power_law(const ex& exp) const; +#define GINAC_FUNCTION_series public: virtual ex series(const relational& r, int order, unsigned options = 0) const; +#define GINAC_FUNCTION_print_dflt protected: void do_print_dflt(const print_context& c, unsigned level) const; +#define GINAC_FUNCTION_print_csrc_float protected: void do_print_csrc_float(const print_context& c, unsigned level) const; +#define GINAC_FUNCTION_print_latex protected: void do_print_latex(const print_context& c, unsigned level) const; + +#define GINAC_FUNCTION_1P(functionname, class_methods) \ + class functionname##_function : public function \ + { \ + GINAC_DECLARE_FUNCTION_1P(functionname##_function) \ + class_methods \ + }; \ + template inline functionname##_function functionname(const T1& x1) { return functionname##_function(x1); } + +#define GINAC_FUNCTION_2P(functionname, class_methods) \ + class functionname##_function : public function \ + { \ + GINAC_DECLARE_FUNCTION_2P(functionname##_function) \ + class_methods \ + }; \ + template inline functionname##_function functionname(const T1& x1, const T2& x2) { return functionname##_function(x1, x2); } + +/** Macro for inclusion in the implementation of a GiNaC function. */ +#define GINAC_IMPLEMENT_FUNCTION(classname) \ + GiNaC::registered_class_info classname::reg_info = GiNaC::registered_class_info(GiNaC::registered_class_options(#classname, "function", &classname::tinfo_static, &classname::unarchive).func_factory(&classname::factory)); \ + const tinfo_static_t classname::tinfo_static = {}; + +/** Macro for inclusion in the implementation of a GiNaC function. + * Additional options can be specified. */ +#define GINAC_IMPLEMENT_FUNCTION_OPT(classname, options) \ + GiNaC::registered_class_info classname::reg_info = GiNaC::registered_class_info(GiNaC::registered_class_options(#classname, "function", &classname::tinfo_static, &classname::unarchive).func_factory(&classname::factory).options); \ + const tinfo_static_t classname::tinfo_static = {}; + +/** Exception class thrown by classes which provide their own series expansion + * to signal that ordinary Taylor expansion is safe. */ +class do_taylor {}; + +} // namespace GiNaC + +#endif // ifndef __GINAC_FUNCTION_H__ diff --git a/ginac/function.pl b/ginac/function.pl deleted file mode 100755 index 06bf2d67..00000000 --- a/ginac/function.pl +++ /dev/null @@ -1,657 +0,0 @@ -#!/usr/bin/perl -w - -$maxargs=10; - -sub generate_seq { - my ($seq_template,$n)=@_; - my ($res,$N); - - $res=''; - for ($N=1; $N<=$n; $N++) { - $res .= eval('"' . $seq_template . '"'); - if ($N!=$n) { - $res .= ', '; - } - } - return $res; -} - -sub generate { - my ($template,$seq_template1,$seq_template2)=@_; - my ($res,$N,$SEQ); - - $res=''; - for ($N=1; $N<=$maxargs; $N++) { - $SEQ1=generate_seq($seq_template1,$N); - $SEQ2=generate_seq($seq_template2,$N); - $res .= eval('"' . $template . '"'); - $SEQ1=''; # to avoid main::SEQ1 used only once warning - $SEQ2=''; # same as above - } - return $res; -} - -$declare_function_macro=generate( - <<'END_OF_DECLARE_FUNCTION_MACRO','GiNaC::ex const & p${N}','p${N}'); -#define DECLARE_FUNCTION_${N}P(NAME) \\ -extern unsigned function_index_##NAME; \\ -inline GiNaC::function NAME(${SEQ1}) { \\ - return GiNaC::function(function_index_##NAME, ${SEQ2}); \\ -} - -END_OF_DECLARE_FUNCTION_MACRO - -$typedef_eval_funcp=generate( -'typedef ex (* eval_funcp_${N})(${SEQ1});'."\n", -'ex const &',''); - -$typedef_evalf_funcp=generate( -'typedef ex (* evalf_funcp_${N})(${SEQ1});'."\n", -'ex const &',''); - -$typedef_diff_funcp=generate( -'typedef ex (* diff_funcp_${N})(${SEQ1}, unsigned);'."\n", -'ex const &',''); - -$typedef_series_funcp=generate( -'typedef ex (* series_funcp_${N})(${SEQ1}, symbol const &, ex const &, int);'."\n", -'ex const &',''); - -$constructors_interface=generate( -' function(unsigned ser, ${SEQ1});'."\n", -'ex const & param${N}',''); - -$register_new_interface=generate( -' static unsigned register_new(char const * nm, eval_funcp_${N} e,'."\n". -' evalf_funcp_${N} ef=0, diff_funcp_${N} d=0, series_funcp_${N} s=0);'. -"\n",'',''); - -$constructors_implementation=generate( - <<'END_OF_CONSTRUCTORS_IMPLEMENTATION','ex const & param${N}','param${N}'); -function::function(unsigned ser, ${SEQ1}) - : exprseq(${SEQ2}), serial(ser) -{ - debugmsg(\"function constructor from unsigned,${N}*ex\",LOGLEVEL_CONSTRUCT); - tinfo_key = TINFO_function; -} -END_OF_CONSTRUCTORS_IMPLEMENTATION - -$eval_switch_statement=generate( - <<'END_OF_EVAL_SWITCH_STATEMENT','eseq[${N}-1]',''); - case ${N}: - return ((eval_funcp_${N})(registered_functions()[serial].e))(${SEQ1}); - break; -END_OF_EVAL_SWITCH_STATEMENT - -$evalf_switch_statement=generate( - <<'END_OF_EVALF_SWITCH_STATEMENT','eseq[${N}-1]',''); - case ${N}: - return ((evalf_funcp_${N})(registered_functions()[serial].ef))(${SEQ1}); - break; -END_OF_EVALF_SWITCH_STATEMENT - -$diff_switch_statement=generate( - <<'END_OF_DIFF_SWITCH_STATEMENT','seq[${N}-1]',''); - case ${N}: - return ((diff_funcp_${N})(registered_functions()[serial].d))(${SEQ1},diff_param); - break; -END_OF_DIFF_SWITCH_STATEMENT - -$series_switch_statement=generate( - <<'END_OF_SERIES_SWITCH_STATEMENT','seq[${N}-1]',''); - case ${N}: - return ((series_funcp_${N})(registered_functions()[serial].s))(${SEQ1},s,point,order); - break; -END_OF_SERIES_SWITCH_STATEMENT - -$register_new_implementation=generate( - <<'END_OF_REGISTER_NEW_IMPLEMENTATION','',''); -unsigned function::register_new(char const * nm, eval_funcp_${N} e, - evalf_funcp_${N} ef, diff_funcp_${N} d, series_funcp_${N} s) -{ - registered_function_info rfi={nm,${N},0,eval_funcp(e), - evalf_funcp(ef),diff_funcp(d),series_funcp(s)}; - registered_functions().push_back(rfi); - return registered_functions().size()-1; -} -END_OF_REGISTER_NEW_IMPLEMENTATION - -$interface=< -#include -#include - -// the following lines have been generated for max. ${maxargs} parameters -$declare_function_macro -// end of generated lines - -#define REGISTER_FUNCTION(NAME,E,EF,D,S) \\ -unsigned function_index_##NAME=GiNaC::function::register_new(#NAME,E,EF,D,S); - -#define BEGIN_TYPECHECK \\ -bool automatic_typecheck=true; - -#define TYPECHECK(VAR,TYPE) \\ -if (!is_ex_exactly_of_type(VAR,TYPE)) { \\ - automatic_typecheck=false; \\ -} else - -#define TYPECHECK_INTEGER(VAR) \\ -if (!(VAR).info(GiNaC::info_flags::integer)) { \\ - automatic_typecheck=false; \\ -} else - -#define END_TYPECHECK(RV) \\ -{} \\ -if (!automatic_typecheck) { \\ - return RV.hold(); \\ -} - -namespace GiNaC { - -class function; - -typedef ex (* eval_funcp)(); -typedef ex (* evalf_funcp)(); -typedef ex (* diff_funcp)(); -typedef ex (* series_funcp)(); - -// the following lines have been generated for max. ${maxargs} parameters -$typedef_eval_funcp -$typedef_evalf_funcp -$typedef_diff_funcp -$typedef_series_funcp -// end of generated lines - -struct registered_function_info { - char const * name; - unsigned nparams; - unsigned options; - eval_funcp e; - evalf_funcp ef; - diff_funcp d; - series_funcp s; -}; - -/** The class function is used to implement builtin functions like sin, cos... - and user defined functions */ -class function : public exprseq -{ - friend void ginsh_get_ginac_functions(void); - -// member functions - - // default constructor, destructor, copy constructor assignment operator and helpers -public: - function(); - ~function(); - function(function const & other); - function const & operator=(function const & other); -protected: - void copy(function const & other); - void destroy(bool call_parent); - - // other constructors -public: - function(unsigned ser); - // the following lines have been generated for max. ${maxargs} parameters -$constructors_interface - // end of generated lines - function(unsigned ser, exprseq const & es); - function(unsigned ser, exvector const & v, bool discardable=0); - function(unsigned ser, exvector * vp); // vp will be deleted - - // functions overriding virtual functions from bases classes -public: - basic * duplicate() const; - void printraw(ostream & os) const; - void print(ostream & os, unsigned upper_precedence=0) const; - void printtree(ostream & os, unsigned indent) const; - void printcsrc(ostream & os, unsigned type, unsigned upper_precedence=0) const; - ex expand(unsigned options=0) const; - ex eval(int level=0) const; - ex evalf(int level=0) const; - ex diff(symbol const & s) const; - ex series(symbol const & s, ex const & point, int order) const; - ex thisexprseq(exvector const & v) const; - ex thisexprseq(exvector * vp) const; -protected: - int compare_same_type(basic const & other) const; - bool is_equal_same_type(basic const & other) const; - unsigned return_type(void) const; - unsigned return_type_tinfo(void) const; - - // new virtual functions which can be overridden by derived classes - // none - - // non-virtual functions in this class -protected: - ex pdiff(unsigned diff_param) const; // partial differentiation - static vector & registered_functions(void); -public: - // the following lines have been generated for max. ${maxargs} parameters -$register_new_interface - // end of generated lines - unsigned getserial(void) const {return serial;} - -// member variables - -protected: - unsigned serial; -}; - -// utility macros - -#define is_ex_the_function(OBJ, FUNCNAME) \\ - (is_ex_exactly_of_type(OBJ, function) && static_cast(OBJ.bp)->getserial() == function_index_##FUNCNAME) - -// global constants - -extern const function some_function; -extern type_info const & typeid_function; - -} // namespace GiNaC - -#endif // ndef __GINAC_FUNCTION_H__ - -END_OF_INTERFACE - -$implementation=< -#include - -#include "function.h" -#include "ex.h" -#include "debugmsg.h" - -namespace GiNaC { - -////////// -// default constructor, destructor, copy constructor assignment operator and helpers -////////// - -// public - -function::function() : serial(0) -{ - debugmsg("function default constructor",LOGLEVEL_CONSTRUCT); - tinfo_key = TINFO_function; -} - -function::~function() -{ - debugmsg("function destructor",LOGLEVEL_DESTRUCT); - destroy(0); -} - -function::function(function const & other) -{ - debugmsg("function copy constructor",LOGLEVEL_CONSTRUCT); - copy(other); -} - -function const & function::operator=(function const & other) -{ - debugmsg("function operator=",LOGLEVEL_ASSIGNMENT); - if (this != &other) { - destroy(1); - copy(other); - } - return *this; -} - -// protected - -void function::copy(function const & other) -{ - exprseq::copy(other); - serial=other.serial; -} - -void function::destroy(bool call_parent) -{ - if (call_parent) exprseq::destroy(call_parent); -} - -////////// -// other constructors -////////// - -// public - -function::function(unsigned ser) : serial(ser) -{ - debugmsg("function constructor from unsigned",LOGLEVEL_CONSTRUCT); - tinfo_key = TINFO_function; -} - -// the following lines have been generated for max. ${maxargs} parameters -$constructors_implementation -// end of generated lines - -function::function(unsigned ser, exprseq const & es) : exprseq(es), serial(ser) -{ - debugmsg("function constructor from unsigned,exprseq",LOGLEVEL_CONSTRUCT); - tinfo_key = TINFO_function; -} - -function::function(unsigned ser, exvector const & v, bool discardable) - : exprseq(v,discardable), serial(ser) -{ - debugmsg("function constructor from string,exvector,bool",LOGLEVEL_CONSTRUCT); - tinfo_key = TINFO_function; -} - -function::function(unsigned ser, exvector * vp) - : exprseq(vp), serial(ser) -{ - debugmsg("function constructor from unsigned,exvector *",LOGLEVEL_CONSTRUCT); - tinfo_key = TINFO_function; -} - -////////// -// functions overriding virtual functions from bases classes -////////// - -// public - -basic * function::duplicate() const -{ - debugmsg("function duplicate",LOGLEVEL_DUPLICATE); - return new function(*this); -} - -void function::printraw(ostream & os) const -{ - debugmsg("function printraw",LOGLEVEL_PRINT); - - GINAC_ASSERT(serialprint(os); - } - os << ")"; -} - -void function::print(ostream & os, unsigned upper_precedence) const -{ - debugmsg("function print",LOGLEVEL_PRINT); - - GINAC_ASSERT(serialbp->printcsrc(os, type, 0); - it++; - if (it != itend) - os << ","; - } - os << ")"; -} - -ex function::expand(unsigned options) const -{ - return this->setflag(status_flags::expanded); -} - -ex function::eval(int level) const -{ - GINAC_ASSERT(serial(const_cast(other)); - - if (serial!=o.serial) { - return serial < o.serial ? -1 : 1; - } - return exprseq::compare_same_type(o); -} - -bool function::is_equal_same_type(basic const & other) const -{ - GINAC_ASSERT(is_of_type(other, function)); - function const & o=static_cast(const_cast(other)); - - if (serial!=o.serial) return false; - return exprseq::is_equal_same_type(o); -} - -unsigned function::return_type(void) const -{ - if (seq.size()==0) { - return return_types::commutative; - } - return (*seq.begin()).return_type(); -} - -unsigned function::return_type_tinfo(void) const -{ - if (seq.size()==0) { - return tinfo_key; - } - return (*seq.begin()).return_type_tinfo(); -} - -////////// -// new virtual functions which can be overridden by derived classes -////////// - -// none - -////////// -// non-virtual functions in this class -////////// - -// protected - -ex function::pdiff(unsigned diff_param) const // partial differentiation -{ - GINAC_ASSERT(serial & function::registered_functions(void) -{ - static vector * rf=new vector; - return *rf; -} - -// public - -// the following lines have been generated for max. ${maxargs} parameters -$register_new_implementation -// end of generated lines - -////////// -// static member variables -////////// - -// none - -////////// -// global constants -////////// - -const function some_function; -type_info const & typeid_function=typeid(some_function); - -} // namespace GiNaC - -END_OF_IMPLEMENTATION - -print "Creating interface file function.h..."; -open OUT,">function.h" or die "cannot open function.h"; -print OUT $interface; -close OUT; -print "ok.\n"; - -print "Creating implementation file function.cpp..."; -open OUT,">function.cpp" or die "cannot open function.cpp"; -print OUT $implementation; -close OUT; -print "ok.\n"; - -print "done.\n"; diff --git a/ginac/ginac.h b/ginac/ginac.h index 23864bc2..c3deb21f 100644 --- a/ginac/ginac.h +++ b/ginac/ginac.h @@ -3,7 +3,7 @@ * This include file includes all other public GiNaC headers. */ /* - * GiNaC Copyright (C) 1999 Johannes Gutenberg University Mainz, Germany + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,49 +17,62 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __GINAC_H__ #define __GINAC_H__ -#include +#include "version.h" -#include -#include +#include "basic.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "ex.h" +#include "normal.h" +#include "archive.h" +#include "print.h" -#include -#include -#include -#include -#include +#include "constant.h" +#include "fail.h" +#include "integral.h" +#include "lst.h" +#include "matrix.h" +#include "numeric.h" +#include "power.h" +#include "relational.h" +#include "structure.h" +#include "symbol.h" +#include "pseries.h" +#include "wildcard.h" +#include "symmetry.h" -#include -#include -#include +#include "expair.h" +#include "expairseq.h" +#include "add.h" +#include "mul.h" -#include -#include +#include "exprseq.h" +#include "function.h" +#include "ncmul.h" -#ifndef GINAC_BASE_ONLY -#include -#include -#include -#include -#include -#include -#include -#endif // ndef GINAC_BASE_ONLY +#include "inifcns.h" +#include "inifcns_trig.h" +#include "inifcns_exp.h" +#include "inifcns_polylog.h" +#include "operators.h" +#include "hash_map.h" + +#include "idx.h" +#include "indexed.h" +#include "tensor.h" +#include "color.h" +#include "clifford.h" + +#include "excompiler.h" + +#ifdef __MAKECINT__ +#pragma link C++ nestedclass; +#pragma link C++ nestedtypedef; +#endif #endif // ndef __GINAC_H__ diff --git a/ginac/inifcns.cpp b/ginac/inifcns.cpp index 3f67d848..944ee5da 100644 --- a/ginac/inifcns.cpp +++ b/ginac/inifcns.cpp @@ -3,7 +3,7 @@ * Implementation of GiNaC's initially known functions. */ /* - * GiNaC Copyright (C) 1999 Johannes Gutenberg University Mainz, Germany + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,239 +17,644 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include -#include - #include "inifcns.h" + #include "ex.h" #include "constant.h" #include "lst.h" #include "matrix.h" #include "mul.h" -#include "ncmul.h" -#include "numeric.h" #include "power.h" +#include "operators.h" #include "relational.h" -#include "series.h" +#include "pseries.h" #include "symbol.h" +#include "symmetry.h" +#include "utils.h" + +#include +#include namespace GiNaC { ////////// -// dilogarithm +// absolute value ////////// -static ex Li2_eval(ex const & x) +GINAC_IMPLEMENT_FUNCTION_OPT(abs_function, + print_func(&abs_function::do_print_csrc_float). + print_func(&abs_function::do_print_latex)) + +ex abs_function::conjugate() const +{ + return *this; +} + +ex abs_function::eval(int level) const +{ + const ex& arg = seq[0]; + if (is_exactly_a(arg)) + return abs(ex_to(arg)); + else + return this->hold(); +} + +ex abs_function::evalf(int level) const +{ + const ex& arg = seq[0]; + if (is_exactly_a(arg)) { + return abs(ex_to(arg)); + } + + return this->hold(); +} + +ex abs_function::power_law(const ex& exp) const { - if (x.is_zero()) - return x; - if (x.is_equal(exONE())) - return power(Pi, 2) / 6; - if (x.is_equal(exMINUSONE())) - return -power(Pi, 2) / 12; - return Li2(x).hold(); + const ex& arg = seq[0]; + if (arg.is_equal(arg.conjugate()) && is_a(exp) && ex_to(exp).is_even()) + return power(arg, exp); + else + return power(abs(arg), exp).hold(); } -REGISTER_FUNCTION(Li2, Li2_eval, NULL, NULL, NULL); +void abs_function::do_print_csrc_float(const print_context& c, unsigned level) const +{ + c.s << "fabs("; seq[0].print(c); c.s << ")"; +} + +void abs_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "{|"; seq[0].print(c); c.s << "|}"; +} ////////// -// trilogarithm +// complex conjugate ////////// -static ex Li3_eval(ex const & x) +GINAC_IMPLEMENT_FUNCTION_OPT(conjugate_function, + print_func(&conjugate_function::do_print_latex)) + +ex conjugate_function::conjugate() const +{ + return seq[0]; +} + +ex conjugate_function::eval(int level) const { - if (x.is_zero()) - return x; - return Li3(x).hold(); + return seq[0].conjugate(); } -REGISTER_FUNCTION(Li3, Li3_eval, NULL, NULL, NULL); +ex conjugate_function::evalf(int level) const +{ + const ex& arg = seq[0]; + if (is_exactly_a(arg)) { + return ex_to(arg).conjugate(); + } + return this->hold(); +} + +void conjugate_function::do_print_latex(const print_context& c, unsigned level) const +{ + const ex& arg = seq[0]; + c.s << "\\bar{"; arg.print(c); c.s << "}"; +} + +////////// +// Complex sign +////////// + +GINAC_IMPLEMENT_FUNCTION(csgn_function) + +ex csgn_function::conjugate() const +{ + return *this; +} + +ex csgn_function::eval(int level) const +{ + const ex& arg = seq[0]; + + if (is_exactly_a(arg)) + return csgn(ex_to(arg)); + + else if (is_exactly_a(arg) && + is_exactly_a(arg.op(arg.nops()-1))) { + numeric oc = ex_to(arg.op(arg.nops()-1)); + if (oc.is_real()) { + if (oc > 0) + // csgn(42*x) -> csgn(x) + return csgn(arg/oc).hold(); + else + // csgn(-42*x) -> -csgn(x) + return -csgn(arg/oc).hold(); + } + if (oc.real().is_zero()) { + if (oc.imag() > 0) + // csgn(42*I*x) -> csgn(I*x) + return csgn(I*arg/oc).hold(); + else + // csgn(-42*I*x) -> -csgn(I*x) + return -csgn(I*arg/oc).hold(); + } + } + + return this->hold(); +} + +ex csgn_function::evalf(int level) const +{ + const ex& arg = seq[0]; + if (is_exactly_a(arg)) + return csgn(ex_to(arg)); + + return this->hold(); +} + +ex csgn_function::power_law(const ex& exp) const +{ + const ex& arg = seq[0]; + if (is_a(exp) && exp.info(info_flags::positive) && ex_to(exp).is_integer()) { + if (ex_to(exp).is_odd()) + return *this; + else + return power(*this, _ex2).hold(); + } else + return power(*this, exp).hold(); +} + +ex csgn_function::series(const relational& r, int order, unsigned options) const +{ + const ex& arg = seq[0]; + const ex arg_pt = arg.subs(r, subs_options::no_pattern); + if (arg_pt.info(info_flags::numeric) + && ex_to(arg_pt).real().is_zero() + && !(options & series_options::suppress_branchcut)) + throw (std::domain_error("csgn_series(): on imaginary axis")); + + epvector seq; + seq.push_back(expair(csgn(arg_pt), _ex0)); + return pseries(r, seq); +} + +////////// +// Step function +////////// + +GINAC_IMPLEMENT_FUNCTION(step_function) + +ex step_function::conjugate() const +{ + return *this; +} + +ex step_function::eval(int level) const +{ + const ex& arg = seq[0]; + + if (is_exactly_a(arg)) + return step(ex_to(arg)); + + else if (is_exactly_a(arg) && + is_exactly_a(arg.op(arg.nops()-1))) { + numeric oc = ex_to(arg.op(arg.nops()-1)); + if (oc.is_real()) { + if (oc > 0) + // step(42*x) -> step(x) + return step(arg/oc).hold(); + else + // step(-42*x) -> step(-x) + return step(-arg/oc).hold(); + } + if (oc.real().is_zero()) { + if (oc.imag() > 0) + // step(42*I*x) -> step(I*x) + return step(I*arg/oc).hold(); + else + // step(-42*I*x) -> step(-I*x) + return step(-I*arg/oc).hold(); + } + } + + return this->hold(); +} + +ex step_function::evalf(int level) const +{ + const ex& arg = seq[0]; + if (is_exactly_a(arg)) + return step(ex_to(arg)); + + return this->hold(); +} + +ex step_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& arg = seq[0]; + const ex arg_pt = arg.subs(rel, subs_options::no_pattern); + if (arg_pt.info(info_flags::numeric) + && ex_to(arg_pt).real().is_zero() + && !(options & series_options::suppress_branchcut)) + throw (std::domain_error("step_series(): on imaginary axis")); + + epvector seq; + seq.push_back(expair(step(arg_pt), _ex0)); + return pseries(rel,seq); +} ////////// // factorial ////////// -static ex factorial_evalf(ex const & x) +GINAC_IMPLEMENT_FUNCTION_OPT(factorial_function, + print_func(&factorial_function::do_print_dflt_latex). + print_func(&factorial_function::do_print_dflt_latex)) + +ex factorial_function::conjugate() const { - return factorial(x).hold(); + return *this; } -static ex factorial_eval(ex const & x) +ex factorial_function::eval(int level) const { - if (is_ex_exactly_of_type(x, numeric)) - return factorial(ex_to_numeric(x)); - else - return factorial(x).hold(); + const ex& x = seq[0]; + if (is_exactly_a(x)) + return factorial(ex_to(x)); + else + return this->hold(); } -REGISTER_FUNCTION(factorial, factorial_eval, factorial_evalf, NULL, NULL); +ex factorial_function::evalf(int level) const +{ + return this->hold(); +} + +void factorial_function::do_print_dflt_latex(const print_context& c, unsigned level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x) || is_exactly_a(x) || is_exactly_a(x)) { + x.print(c); c.s << "!"; + } else { + c.s << "("; x.print(c); c.s << ")!"; + } +} ////////// // binomial ////////// -static ex binomial_evalf(ex const & x, ex const & y) +GINAC_IMPLEMENT_FUNCTION(binomial_function) + +// At the moment the numeric evaluation of a binomail function always +// gives a real number, but if this would be implemented using the gamma +// function, also complex conjugation should be changed (or rather, deleted). +ex binomial_function::conjugate() const { - return binomial(x, y).hold(); + return *this; } -static ex binomial_eval(ex const & x, ex const &y) +ex binomial_function::sym(const ex& x, const numeric& y) const { - if (is_ex_exactly_of_type(x, numeric) && is_ex_exactly_of_type(y, numeric)) - return binomial(ex_to_numeric(x), ex_to_numeric(y)); - else - return binomial(x, y).hold(); + if (y.is_integer()) { + if (y.is_nonneg_integer()) { + const unsigned N = y.to_int(); + if (N == 0) return _ex0; + if (N == 1) return x; + ex t = x.expand(); + for (unsigned i = 2; i <= N; ++i) + t = (t * (x + i - y - 1)).expand() / i; + return t; + } else + return _ex0; + } + + return this->hold(); } -REGISTER_FUNCTION(binomial, binomial_eval, binomial_evalf, NULL, NULL); +ex binomial_function::eval(int level) const +{ + const ex& x = seq[0]; + const ex& y = seq[1]; + if (is_exactly_a(y)) { + if (is_exactly_a(x) && ex_to(x).is_integer()) + return binomial(ex_to(x), ex_to(y)); + else + return sym(x, ex_to(y)); + } else + return this->hold(); +} + +ex binomial_function::evalf(int level) const +{ + return this->hold(); +} ////////// // Order term function (for truncated power series) ////////// -static ex Order_eval(ex const & x) +GINAC_IMPLEMENT_FUNCTION_OPT(Order_function, + print_func(&Order_function::do_print_latex)) + +ex Order_function::conjugate() const +{ + return *this; +} + +ex Order_function::derivative(const symbol& s) const +{ + return Order(seq[0].diff(s)); +} + +ex Order_function::eval(int level) const { - if (is_ex_exactly_of_type(x, numeric)) { + const ex& x = op(0); + if (is_exactly_a(x)) { + // O(c) -> O(1) or 0 + if (!x.is_zero()) + return Order(_ex1).hold(); + else + return _ex0; + } else if (is_exactly_a(x)) { + const mul& m = ex_to(x); + // O(c*expr) -> O(expr) + if (is_exactly_a(m.op(m.nops() - 1))) + return Order(x / m.op(m.nops() - 1)).hold(); + } + return this->hold(); +} + +ex Order_function::series(const relational& r, int order, unsigned options) const +{ + const ex& x = op(0); + // Just wrap the function into a pseries object + epvector new_seq; + GINAC_ASSERT(is_a(r.lhs())); + const symbol& s = ex_to(r.lhs()); + new_seq.push_back(expair(Order(_ex1), numeric(std::min(x.ldegree(s), order)))); + return pseries(r, new_seq); +} - // O(c)=O(1) - return Order(exONE()).hold(); +void Order_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\mathcal{O}"; + inherited::do_print(c,level); +} - } else if (is_ex_exactly_of_type(x, mul)) { +////////// +// Abstract derivative of functions +////////// - mul *m = static_cast(x.bp); - if (is_ex_exactly_of_type(m->op(m->nops() - 1), numeric)) { +GINAC_IMPLEMENT_FUNCTION_OPT(function_derivative_function, + print_func(&function_derivative_function::do_print_dflt). + print_func(&function_derivative_function::do_print_tree)) - // O(c*expr)=O(expr) - return Order(x / m->op(m->nops() - 1)).hold(); +/** Implementation of ex::diff() for derivatives. It applies the chain rule. + * @see ex::diff */ +ex function_derivative_function::derivative(const symbol& s) const +{ + GINAC_ASSERT(seq[0].size() == 2); + GINAC_ASSERT(is_a(seq[0])); + GINAC_ASSERT(is_a(seq[1])); + + const ex& func = seq[1]; + ex result; + for (size_t i=0; i(seq[0]); + params.append(i); + params.sort(); + result += arg_diff * function_derivative(params, func); } } - return Order(x).hold(); + return result; } -static ex Order_series(ex const & x, symbol const & s, ex const & point, int order) +ex function_derivative_function::eval(int level) const { - // Just wrap the function into a series object - epvector new_seq; - new_seq.push_back(expair(Order(exONE()), numeric(min(x.ldegree(s), order)))); - return series(s, point, new_seq); -} - -REGISTER_FUNCTION(Order, Order_eval, NULL, NULL, Order_series); - -/** linear solve. */ -ex lsolve(ex const &eqns, ex const &symbols) -{ - // solve a system of linear equations - if (eqns.info(info_flags::relation_equal)) { - if (!symbols.info(info_flags::symbol)) { - throw(std::invalid_argument("lsolve: 2nd argument must be a symbol")); - } - ex sol=lsolve(lst(eqns),lst(symbols)); - - GINAC_ASSERT(sol.nops()==1); - GINAC_ASSERT(is_ex_exactly_of_type(sol.op(0),relational)); - - return sol.op(0).op(1); // return rhs of first solution - } - - // syntax checks - if (!eqns.info(info_flags::list)) { - throw(std::invalid_argument("lsolve: 1st argument must be a list")); - } - for (int i=0; i 1) { + // first evaluate children, then we will end up here again + return function_derivative_function(evalchildren(level)); + } + + const ex& params = seq[0]; + // No parameters specified? Then return the function itself + if (params.nops() == 0) { + return seq[1]; + } + + if (params.nops() == 1) { + return ex_to(seq[1]).pderivative((ex_to(params.op(0))).to_int()); + } + + return this->hold(); +} + +void function_derivative_function::do_print_dflt(const print_context& c, unsigned level) const +{ + c.s << "D["; + const lst& params = ex_to(seq[0]); + lst::const_iterator i = params.begin(), end = params.end(); + --end; + while (i != end) { + c.s << *i++ << ","; + } + c.s << *i << "](" << seq[1] << ")"; +} + +void function_derivative_function::do_print_tree(const print_tree& c, unsigned level) const +{ + c.s << std::string(level, ' ') << class_name() << " " + << ex_to(seq[1]).class_name() << " @" << this + << std::hex << ", hash=0x" << hashvalue << ", flags=0x" << flags << std::dec + << ", nops=" << nops() << std::endl; + seq[0].print(c, level + c.delta_indent); + for (size_t i=0; i(sol.op(0))); + + return sol.op(0).op(1); // return rhs of first solution + } + + // syntax checks + if (!eqns.info(info_flags::list)) { + throw(std::invalid_argument("lsolve(): 1st argument must be a list")); + } + for (size_t i=0; i(symbols.op(c)),1); + linpart -= co*symbols.op(c); + sys(r,c) = co; + } + linpart = linpart.expand(); + rhs(r,0) = -linpart; + } + + // test if system is linear and fill vars matrix + for (size_t i=0; i(f_in)) { + f = f_in.lhs()-f_in.rhs(); + } else { + f = f_in; + } + const ex fx_[2] = { f.subs(x==xx[0]).evalf(), + f.subs(x==xx[1]).evalf() }; + if (!is_a(fx_[0]) || !is_a(fx_[1])) { + throw std::runtime_error("fsolve(): function does not evaluate numerically"); + } + numeric fx[2] = { ex_to(fx_[0]), + ex_to(fx_[1]) }; + if (!fx[0].is_real() || !fx[1].is_real()) { + throw std::runtime_error("fsolve(): function evaluates to complex values at interval boundaries"); + } + if (fx[0]*fx[1]>=0) { + throw std::runtime_error("fsolve(): function does not change sign at interval boundaries"); + } + + // The Newton-Raphson method has quadratic convergence! Simply put, it + // replaces x with x-f(x)/f'(x) at each step. -f/f' is the delta: + const ex ff = normal(-f/f.diff(x)); + int side = 0; // Start at left interval limit. + numeric xxprev; + numeric fxprev; + do { + xxprev = xx[side]; + fxprev = fx[side]; + xx[side] += ex_to(ff.subs(x==xx[side]).evalf()); + fx[side] = ex_to(f.subs(x==xx[side]).evalf()); + if ((side==0 && xx[0]xxprev) || xx[0]>xx[1]) { + // Oops, Newton-Raphson method shot out of the interval. + // Restore, and try again with the other side instead! + xx[side] = xxprev; + fx[side] = fxprev; + side = !side; + xxprev = xx[side]; + fxprev = fx[side]; + xx[side] += ex_to(ff.subs(x==xx[side]).evalf()); + fx[side] = ex_to(f.subs(x==xx[side]).evalf()); + } + if ((fx[side]<0 && fx[!side]<0) || (fx[side]>0 && fx[!side]>0)) { + // Oops, the root isn't bracketed any more. + // Restore, and perform a bisection! + xx[side] = xxprev; + fx[side] = fxprev; + + // Ah, the bisection! Bisections converge linearly. Unfortunately, + // they occur pretty often when Newton-Raphson arrives at an x too + // close to the result on one side of the interval and + // f(x-f(x)/f'(x)) turns out to have the same sign as f(x) due to + // precision errors! Recall that this function does not have a + // precision goal as one of its arguments but instead relies on + // x converging to a fixed point. We speed up the (safe but slow) + // bisection method by mixing in a dash of the (unsafer but faster) + // secant method: Instead of splitting the interval at the + // arithmetic mean (bisection), we split it nearer to the root as + // determined by the secant between the values xx[0] and xx[1]. + // Don't set the secant_weight to one because that could disturb + // the convergence in some corner cases! + static const double secant_weight = 0.984375; // == 63/64 < 1 + numeric xxmid = (1-secant_weight)*0.5*(xx[0]+xx[1]) + + secant_weight*(xx[0]+fx[0]*(xx[0]-xx[1])/(fx[1]-fx[0])); + numeric fxmid = ex_to(f.subs(x==xxmid).evalf()); + if (fxmid.is_zero()) { + // Luck strikes... + return xxmid; + } + if ((fxmid<0 && fx[side]>0) || (fxmid>0 && fx[side]<0)) { + side = !side; + } + xxprev = xx[side]; + fxprev = fx[side]; + xx[side] = xxmid; + fx[side] = fxmid; + } + } while (xxprev!=xx[side]); + return xxprev; +} } // namespace GiNaC diff --git a/ginac/inifcns.h b/ginac/inifcns.h index ee737818..1f6761b7 100644 --- a/ginac/inifcns.h +++ b/ginac/inifcns.h @@ -3,7 +3,7 @@ * Interface to GiNaC's initially known functions. */ /* - * GiNaC Copyright (C) 1999 Johannes Gutenberg University Mainz, Germany + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,96 +17,134 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __GINAC_INIFCNS_H__ #define __GINAC_INIFCNS_H__ -#include -#include +#include "numeric.h" +#include "function.h" +#include "ex.h" namespace GiNaC { -/** Sine. */ -DECLARE_FUNCTION_1P(sin) - -/** Cosine. */ -DECLARE_FUNCTION_1P(cos) - -/** Tangent. */ -DECLARE_FUNCTION_1P(tan) - -/** Exponential function. */ -DECLARE_FUNCTION_1P(exp) - -/** Natural logarithm. */ -DECLARE_FUNCTION_1P(log) - -/** Inverse sine (arc sine). */ -DECLARE_FUNCTION_1P(asin) - -/** Inverse cosine (arc cosine). */ -DECLARE_FUNCTION_1P(acos) - -/** Inverse tangent (arc tangent). */ -DECLARE_FUNCTION_1P(atan) - -/** Inverse tangent with two arguments. */ -DECLARE_FUNCTION_2P(atan2) - -/** Hyperbolic Sine. */ -DECLARE_FUNCTION_1P(sinh) - -/** Hyperbolic Cosine. */ -DECLARE_FUNCTION_1P(cosh) - -/** Hyperbolic Tangent. */ -DECLARE_FUNCTION_1P(tanh) - -/** Inverse hyperbolic Sine (area hyperbolic sine). */ -DECLARE_FUNCTION_1P(asinh) - -/** Inverse hyperbolic Cosine (area hyperbolic cosine). */ -DECLARE_FUNCTION_1P(acosh) - -/** Inverse hyperbolic Tangent (area hyperbolic tangent). */ -DECLARE_FUNCTION_1P(atanh) - -/** Dilogarithm. */ -DECLARE_FUNCTION_1P(Li2) - -/** Trilogarithm. */ -DECLARE_FUNCTION_1P(Li3) - -/** Riemann's Zeta-function. */ -DECLARE_FUNCTION_1P(zeta) -//DECLARE_FUNCTION_2P(zeta) +/** Absolute value. */ +class abs_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(abs_function) +public: + virtual ex conjugate() const; + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex power_law(const ex& exp) const; +protected: + void do_print_csrc_float(const print_context& c, unsigned level) const; + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline abs_function abs(const T1& x1) { return abs_function(x1); } +inline abs_function abs(double x1) { return abs_function(x1); } +inline abs_function abs(float x1) { return abs_function(x1); } + +/** Complex conjugate. */ +GINAC_FUNCTION_1P(conjugate, + GINAC_FUNCTION_conjugate + GINAC_FUNCTION_eval + GINAC_FUNCTION_evalf + GINAC_FUNCTION_print_latex) + +/** Complex sign. */ +GINAC_FUNCTION_1P(csgn, + GINAC_FUNCTION_conjugate + GINAC_FUNCTION_eval + GINAC_FUNCTION_evalf + GINAC_FUNCTION_power_law + GINAC_FUNCTION_series) + +/** Step function. */ +class step_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(step_function) +public: + virtual ex conjugate() const; + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +}; -/** Gamma-function. */ -DECLARE_FUNCTION_1P(gamma) +template inline step_function step(const T1& x1) { return step_function(x1); } -/** Psi-function (aka polygamma-function). */ -//DECLARE_FUNCTION_1P(psi) -DECLARE_FUNCTION_2P(psi) - /** Factorial function. */ -DECLARE_FUNCTION_1P(factorial) - -/** Binomial function. */ -DECLARE_FUNCTION_2P(binomial) +class factorial_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(factorial_function) +public: + virtual ex conjugate() const; + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; +protected: + void do_print_dflt_latex(const print_context& c, unsigned level) const; +}; -/** Order term function (for truncated power series). */ -DECLARE_FUNCTION_1P(Order) +template inline factorial_function factorial(const T1& x1) { return factorial_function(x1); } -ex lsolve(ex const &eqns, ex const &symbols); +/** Binomial function. */ +class binomial_function : public function +{ + GINAC_DECLARE_FUNCTION_2P(binomial_function) +public: + virtual ex conjugate() const; + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; +protected: + ex sym(const ex& x, const numeric& y) const; +}; -ex ncpower(ex const &basis, unsigned exponent); +template inline binomial_function binomial(const T1& x1, const T2& x2) { return binomial_function(x1, x2); } -inline bool is_order_function(ex const & e) +/** Order term function (for truncated power series). */ +class Order_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(Order_function) +public: + virtual ex conjugate() const; + virtual ex derivative(const symbol& s) const; + virtual ex eval(int level = 0) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline Order_function Order(const T1& x1) { return Order_function(x1); } + +/** Abstract derivative of functions. */ +class function_derivative_function : public function { - return is_ex_the_function(e, Order); -} + GINAC_DECLARE_FUNCTION_2P(function_derivative_function) +public: + virtual ex derivative(const symbol& s) const; + virtual ex eval(int level = 0) const; +protected: + void do_print_dflt(const print_context& c, unsigned level) const; + void do_print_tree(const print_tree& c, unsigned level) const; +}; + +template +inline function_derivative_function function_derivative(const T1& x1, const T2& x2) { return function_derivative_function(x1, x2); } + +ex lsolve(const ex& eqns, const ex& symbols, unsigned options = solve_algo::automatic); + +/** Find a real root of real-valued function f(x) numerically within a given + * interval. The function must change sign across interval. Uses Newton- + * Raphson method combined with bisection in order to guarantee convergence. + * + * @param f Function f(x) + * @param x Symbol f(x) + * @param x1 lower interval limit + * @param x2 upper interval limit + * @exception runtime_error (if interval is invalid). */ +const numeric fsolve(const ex& f, const symbol& x, const numeric& x1, const numeric& x2); } // namespace GiNaC diff --git a/ginac/inifcns_exp.cpp b/ginac/inifcns_exp.cpp new file mode 100644 index 00000000..a464400c --- /dev/null +++ b/ginac/inifcns_exp.cpp @@ -0,0 +1,903 @@ +/** @file inifcns_exp.cpp + * + * Implementation of TODO + * functions. */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "inifcns_exp.h" + +#include +#include + +#include "inifcns.h" +#include "inifcns_polylog.h" +#include "ex.h" +#include "constant.h" +#include "numeric.h" +#include "power.h" +#include "operators.h" +#include "relational.h" +#include "symbol.h" +#include "symmetry.h" +#include "pseries.h" +#include "utils.h" + +namespace GiNaC { + +GINAC_IMPLEMENT_FUNCTION_OPT(eta_function, + print_func(&eta_function::do_print_latex)) + +ex eta_function::conjugate() const +{ + return -eta(seq[0], seq[1]); +} + +ex eta_function::eval(int level) const +{ + // Canonicalize argument order according to the symmetry properties + exvector v = seq; + int sig = canonicalize(v.begin(), sy_symm(0,1)); + if (sig != INT_MAX) { + // Something has changed while sorting arguments, more evaluations later + if (sig == 0) { + return _ex0; + } + return ex(sig) * thiscontainer(v); + } + + const ex& x = seq[0]; + const ex& y = seq[1]; + // trivial: eta(x,c) -> 0 if c is real and positive + if (x.info(info_flags::positive) || y.info(info_flags::positive)) + return _ex0; + + if (x.info(info_flags::numeric) && y.info(info_flags::numeric)) { + // don't call eta_evalf here because it would call Pi.evalf()! + const numeric nx = ex_to(x); + const numeric ny = ex_to(y); + const numeric nxy = ex_to(x*y); + int cut = 0; + if (nx.is_real() && nx.is_negative()) + cut -= 4; + if (ny.is_real() && ny.is_negative()) + cut -= 4; + if (nxy.is_real() && nxy.is_negative()) + cut += 4; + return (I/4)*Pi*((csgn(-imag(nx))+1)*(csgn(-imag(ny))+1)*(csgn(imag(nxy))+1)- + (csgn(imag(nx))+1)*(csgn(imag(ny))+1)*(csgn(-imag(nxy))+1)+cut); + } + + return this->hold(); +} + +ex eta_function::evalf(int level) const +{ + const ex& x = seq[0]; + const ex& y = seq[1]; + // It seems like we basically have to replicate the eval function here, + // since the expression might not be fully evaluated yet. + if (x.info(info_flags::positive) || y.info(info_flags::positive)) + return _ex0; + + if (x.info(info_flags::numeric) && y.info(info_flags::numeric)) { + const numeric nx = ex_to(x); + const numeric ny = ex_to(y); + const numeric nxy = ex_to(x*y); + int cut = 0; + if (nx.is_real() && nx.is_negative()) + cut -= 4; + if (ny.is_real() && ny.is_negative()) + cut -= 4; + if (nxy.is_real() && nxy.is_negative()) + cut += 4; + return GiNaC::evalf(I/4*Pi)*((csgn(-imag(nx))+1)*(csgn(-imag(ny))+1)*(csgn(imag(nxy))+1)- + (csgn(imag(nx))+1)*(csgn(imag(ny))+1)*(csgn(-imag(nxy))+1)+cut); + } + + return this->hold(); +} + +ex eta_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& x = seq[0]; + const ex& y = seq[1]; + const ex x_pt = x.subs(rel, subs_options::no_pattern); + const ex y_pt = y.subs(rel, subs_options::no_pattern); + if ((x_pt.info(info_flags::numeric) && x_pt.info(info_flags::negative)) || + (y_pt.info(info_flags::numeric) && y_pt.info(info_flags::negative)) || + ((x_pt*y_pt).info(info_flags::numeric) && (x_pt*y_pt).info(info_flags::negative))) + throw (std::domain_error("eta_series(): on discontinuity")); + epvector seq; + seq.push_back(expair(eta(x_pt,y_pt), _ex0)); + return pseries(rel, seq); +} + +void eta_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\eta"; + inherited::do_print(c,level); +} + +////////// +// exponential function +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(exp_function, + print_func(&exp_function::do_print_csrc_float). + print_func(&exp_function::do_print_latex)) + +ex exp_function::eval(int level) const +{ + const ex& x = seq[0]; + + // exp(0) -> 1 + if (x.is_zero()) { + return _ex1; + } + + // exp(n*Pi*I/2) -> {+1|+I|-1|-I} + const ex TwoExOverPiI=(_ex2*x)/(Pi*I); + if (TwoExOverPiI.info(info_flags::integer)) { + const numeric z = mod(ex_to(TwoExOverPiI),*_num4_p); + if (z.is_equal(*_num0_p)) + return _ex1; + if (z.is_equal(*_num1_p)) + return ex(I); + if (z.is_equal(*_num2_p)) + return _ex_1; + if (z.is_equal(*_num3_p)) + return ex(-I); + } + + // exp(log(x)) -> x + if (is_exactly_a(x)) + return x.op(0); + + // exp(float) -> float + if (x.info(info_flags::numeric) && !x.info(info_flags::crational)) + return exp(ex_to(x)); + + return this->hold(); +} + +ex exp_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) + return exp(ex_to(x)); + + return this->hold(); +} + +ex exp_function::pderivative(unsigned deriv_param) const +{ + GINAC_ASSERT(deriv_param==0); + + // d/dx exp(x) -> exp(x) + return *this; +} + +void exp_function::do_print_csrc_float(const print_context& c, unsigned level) const +{ + c.s << "exp("; seq[0].print(c); c.s << ")"; +} + +void exp_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\exp"; + inherited::do_print(c,level); +} + +////////// +// natural logarithm +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(log_function, + print_func(&log_function::do_print_csrc_float). + print_func(&log_function::do_print_latex)) + +ex log_function::eval(int level) const +{ + const ex& x = seq[0]; + if (x.info(info_flags::numeric)) { + if (x.is_zero()) // log(0) -> infinity + throw(pole_error("log_eval(): log(0)",0)); + if (x.info(info_flags::rational) && x.info(info_flags::negative)) + return (log(-x)+I*Pi); + if (x.is_equal(_ex1)) // log(1) -> 0 + return _ex0; + if (x.is_equal(I)) // log(I) -> Pi*I/2 + return (Pi*I*_ex1_2); + if (x.is_equal(-I)) // log(-I) -> -Pi*I/2 + return (Pi*I*_ex_1_2); + + // log(float) -> float + if (!x.info(info_flags::crational)) + return log(ex_to(x)); + } + + // log(exp(t)) -> t (if -Pi < t.imag() <= Pi): + if (is_exactly_a(x)) { + const ex& t = x.op(0); + if (t.info(info_flags::real)) { + return t; + } + } + + return this->hold(); +} + +ex log_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) + return log(ex_to(x)); + + return this->hold(); +} + +ex log_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + GINAC_ASSERT(deriv_param==0); + + // d/dx log(x) -> 1/x + return power::power(x, _ex_1); +} + +ex log_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& arg = seq[0]; + + GINAC_ASSERT(is_a(rel.lhs())); + ex arg_pt; + bool must_expand_arg = false; + // maybe substitution of rel into arg fails because of a pole + try { + arg_pt = arg.subs(rel, subs_options::no_pattern); + } catch (pole_error) { + must_expand_arg = true; + } + // or we are at the branch point anyways + if (arg_pt.is_zero()) + must_expand_arg = true; + + if (must_expand_arg) { + // method: + // This is the branch point: Series expand the argument first, then + // trivially factorize it to isolate that part which has constant + // leading coefficient in this fashion: + // x^n + x^(n+1) +...+ Order(x^(n+m)) -> x^n * (1 + x +...+ Order(x^m)). + // Return a plain n*log(x) for the x^n part and series expand the + // other part. Add them together and reexpand again in order to have + // one unnested pseries object. All this also works for negative n. + pseries argser; // series expansion of log's argument + unsigned extra_ord = 0; // extra expansion order + do { + // oops, the argument expanded to a pure Order(x^something)... + argser = ex_to(arg.series(rel, order+extra_ord, options)); + ++extra_ord; + } while (!argser.is_terminating() && argser.nops()==1); + + const symbol &s = ex_to(rel.lhs()); + const ex &point = rel.rhs(); + const int n = argser.ldegree(s); + epvector seq; + // construct what we carelessly called the n*log(x) term above + const ex coeff = argser.coeff(s, n); + // expand the log, but only if coeff is real and > 0, since otherwise + // it would make the branch cut run into the wrong direction + if (coeff.info(info_flags::positive)) + seq.push_back(expair(n*log(s-point)+log(coeff), _ex0)); + else + seq.push_back(expair(log(coeff*pow(s-point, n)), _ex0)); + + if (!argser.is_terminating() || argser.nops()!=1) { + // in this case n more (or less) terms are needed + // (sadly, to generate them, we have to start from the beginning) + if (n == 0 && coeff == 1) { + epvector epv; + ex acc = (new pseries(rel, epv))->setflag(status_flags::dynallocated); + epv.reserve(2); + epv.push_back(expair(-1, _ex0)); + epv.push_back(expair(Order(_ex1), order)); + ex rest = pseries(rel, epv).add_series(argser); + for (int i = order-1; i>0; --i) { + epvector cterm; + cterm.reserve(1); + cterm.push_back(expair(i%2 ? _ex1/i : _ex_1/i, _ex0)); + acc = pseries(rel, cterm).add_series(ex_to(acc)); + acc = (ex_to(rest)).mul_series(ex_to(acc)); + } + return acc; + } + const ex newarg = ex_to((arg/coeff).series(rel, order+n, options)).shift_exponents(-n).convert_to_poly(true); + return pseries(rel, seq).add_series(ex_to(log(newarg).series(rel, order, options))); + } else // it was a monomial + return pseries(rel, seq); + } + if (!(options & series_options::suppress_branchcut) && + arg_pt.info(info_flags::negative)) { + // method: + // This is the branch cut: assemble the primitive series manually and + // then add the corresponding complex step function. + const symbol &s = ex_to(rel.lhs()); + const ex &point = rel.rhs(); + const symbol foo; + const ex replarg = log(arg).series(s==foo, order).subs(foo==point, subs_options::no_pattern); + epvector seq; + seq.push_back(expair(-I*csgn(arg*I)*Pi, _ex0)); + seq.push_back(expair(Order(_ex1), order)); + return (replarg - I*Pi + pseries(rel, seq)).series(rel, order); + } + return basic::series(rel, order, options); +} + +void log_function::do_print_csrc_float(const print_context& c, unsigned level) const +{ + c.s << "log("; seq[0].print(c); c.s << ")"; +} + +void log_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\log"; + inherited::do_print(c,level); +} + +////////// +// Logarithm of Gamma function +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(lgamma_function, + print_func(&lgamma_function::do_print_latex)) + +/** Evaluation of lgamma(x), the natural logarithm of the Gamma function. + * Knows about integer arguments and that's it. Somebody ought to provide + * some good numerical evaluation some day... + * + * @exception GiNaC::pole_error("lgamma_eval(): logarithmic pole",0) */ +ex lgamma_function::eval(int level) const +{ + const ex& x = seq[0]; + if (x.info(info_flags::numeric)) { + // trap integer arguments: + if (x.info(info_flags::integer)) { + // lgamma(n) -> log((n-1)!) for postitive n + if (x.info(info_flags::posint)) + return log(factorial(x + _ex_1)); + else + throw (pole_error("lgamma_eval(): logarithmic pole",0)); + } + // lgamma_evalf should be called here once it becomes available + } + + return this->hold(); +} + +ex lgamma_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) { + try { + return lgamma(ex_to(x)); + } catch (const dunno& e) { } + } + + return this->hold(); +} + +ex lgamma_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + GINAC_ASSERT(deriv_param==0); + + // d/dx lgamma(x) -> psi(x) + return psi(x); +} + +ex lgamma_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& arg = seq[0]; + // method: + // Taylor series where there is no pole falls back to psi function + // evaluation. + // On a pole at -m we could use the recurrence relation + // lgamma(x) == lgamma(x+1)-log(x) + // from which follows + // series(lgamma(x),x==-m,order) == + // series(lgamma(x+m+1)-log(x)...-log(x+m)),x==-m,order); + const ex arg_pt = arg.subs(rel, subs_options::no_pattern); + if (!arg_pt.info(info_flags::integer) || arg_pt.info(info_flags::positive)) + return basic::series(rel, order, options); + // if we got here we have to care for a simple pole of tgamma(-m): + numeric m = -ex_to(arg_pt); + ex recur; + for (numeric p = 0; p<=m; ++p) + recur += log(arg+p); + return (lgamma(arg+m+_ex1)-recur).series(rel, order, options); +} + +void lgamma_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\log \\Gamma"; + inherited::do_print(c,level); +} + +////////// +// true Gamma function +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(tgamma_function, + print_func(&tgamma_function::do_print_latex)) + +/** Evaluation of tgamma(x), the true Gamma function. Knows about integer + * arguments, half-integer arguments and that's it. Somebody ought to provide + * some good numerical evaluation some day... + * + * @exception pole_error("tgamma_eval(): simple pole",0) */ +ex tgamma_function::eval(int level) const +{ + const ex& x = seq[0]; + if (x.info(info_flags::numeric)) { + // trap integer arguments: + const numeric two_x = (*_num2_p)*ex_to(x); + if (two_x.is_even()) { + // tgamma(n) -> (n-1)! for postitive n + if (two_x.is_positive()) { + return factorial(ex_to(x).sub(*_num1_p)); + } else { + throw (pole_error("tgamma_eval(): simple pole",1)); + } + } + // trap half integer arguments: + if (two_x.is_integer()) { + // trap positive x==(n+1/2) + // tgamma(n+1/2) -> Pi^(1/2)*(1*3*..*(2*n-1))/(2^n) + if (two_x.is_positive()) { + const numeric n = ex_to(x).sub(*_num1_2_p); + return (doublefactorial(n.mul(*_num2_p).sub(*_num1_p)).div(pow(*_num2_p,n))) * sqrt(Pi); + } else { + // trap negative x==(-n+1/2) + // tgamma(-n+1/2) -> Pi^(1/2)*(-2)^n/(1*3*..*(2*n-1)) + const numeric n = abs(ex_to(x).sub(*_num1_2_p)); + return (pow(*_num_2_p, n).div(doublefactorial(n.mul(*_num2_p).sub(*_num1_p))))*sqrt(Pi); + } + } + // tgamma_evalf should be called here once it becomes available + } + + return this->hold(); +} + +ex tgamma_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) { + try { + return tgamma(ex_to(x)); + } catch (const dunno &e) { } + } + + return this->hold(); +} + +ex tgamma_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + GINAC_ASSERT(deriv_param==0); + + // d/dx tgamma(x) -> psi(x)*tgamma(x) + return psi(x)*tgamma(x); +} + +ex tgamma_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& arg = seq[0]; + // method: + // Taylor series where there is no pole falls back to psi function + // evaluation. + // On a pole at -m use the recurrence relation + // tgamma(x) == tgamma(x+1) / x + // from which follows + // series(tgamma(x),x==-m,order) == + // series(tgamma(x+m+1)/(x*(x+1)*...*(x+m)),x==-m,order); + const ex arg_pt = arg.subs(rel, subs_options::no_pattern); + if (!arg_pt.info(info_flags::integer) || arg_pt.info(info_flags::positive)) { + return basic::series(rel, order, options); + } + // if we got here we have to care for a simple pole at -m: + const numeric m = -ex_to(arg_pt); + ex ser_denom = _ex1; + for (numeric p; p<=m; ++p) + ser_denom *= arg+p; + return (tgamma(arg+m+_ex1)/ser_denom).series(rel, order, options); +} + +void tgamma_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\Gamma"; + inherited::do_print(c,level); +} + +////////// +// beta-function +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(beta_function, + print_func(&beta_function::do_print_latex)) + +ex beta_function::eval(int level) const +{ + // Canonicalize argument order according to the symmetry properties + exvector v = seq; + int sig = canonicalize(v.begin(), sy_symm(0,1)); + if (sig != INT_MAX) { + // Something has changed while sorting arguments, more evaluations later + if (sig == 0) { + return _ex0; + } + return ex(sig) * thiscontainer(v); + } + + const ex& x = seq[0]; + const ex& y = seq[1]; + if (x.is_equal(_ex1)) + return 1/y; + if (y.is_equal(_ex1)) + return 1/x; + if (x.info(info_flags::numeric) && y.info(info_flags::numeric)) { + // treat all problematic x and y that may not be passed into tgamma, + // because they would throw there although beta(x,y) is well-defined + // using the formula beta(x,y) == (-1)^y * beta(1-x-y, y) + const numeric &nx = ex_to(x); + const numeric &ny = ex_to(y); + if (nx.is_real() && nx.is_integer() && + ny.is_real() && ny.is_integer()) { + if (nx.is_negative()) { + if (nx<=-ny) + return pow(*_num_1_p, ny)*beta(1-x-y, y); + else + throw (pole_error("beta_eval(): simple pole",1)); + } + if (ny.is_negative()) { + if (ny<=-nx) + return pow(*_num_1_p, nx)*beta(1-y-x, x); + else + throw (pole_error("beta_eval(): simple pole",1)); + } + return tgamma(x)*tgamma(y)/tgamma(x+y); + } + // no problem in numerator, but denominator has pole: + if ((nx+ny).is_real() && + (nx+ny).is_integer() && + !(nx+ny).is_positive()) + return _ex0; + // beta_evalf should be called here once it becomes available + } + + return this->hold(); +} + +ex beta_function::evalf(int level) const +{ + const ex& x = seq[0]; + const ex& y = seq[1]; + if (is_exactly_a(x) && is_exactly_a(y)) { + try { + return tgamma(ex_to(x))*tgamma(ex_to(y))/tgamma(ex_to(x+y)); + } catch (const dunno &e) { } + } + + return this->hold(); +} + +ex beta_function::pderivative(unsigned deriv_param) const + +{ + const ex& x = seq[0]; + const ex& y = seq[1]; + GINAC_ASSERT(deriv_param<2); + ex retval; + + // d/dx beta(x,y) -> (psi(x)-psi(x+y)) * beta(x,y) + if (deriv_param==0) + retval = (psi(x)-psi(x+y))*beta(x,y); + // d/dy beta(x,y) -> (psi(y)-psi(x+y)) * beta(x,y) + if (deriv_param==1) + retval = (psi(y)-psi(x+y))*beta(x,y); + return retval; +} + +ex beta_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& arg1 = seq[0]; + const ex& arg2 = seq[1]; + // method: + // Taylor series where there is no pole of one of the tgamma functions + // falls back to beta function evaluation. Otherwise, fall back to + // tgamma series directly. + const ex arg1_pt = arg1.subs(rel, subs_options::no_pattern); + const ex arg2_pt = arg2.subs(rel, subs_options::no_pattern); + GINAC_ASSERT(is_a(rel.lhs())); + const symbol &s = ex_to(rel.lhs()); + ex arg1_ser, arg2_ser, arg1arg2_ser; + if ((!arg1_pt.info(info_flags::integer) || arg1_pt.info(info_flags::positive)) && + (!arg2_pt.info(info_flags::integer) || arg2_pt.info(info_flags::positive))) + return basic::series(rel, order, options); + // trap the case where arg1 is on a pole: + if (arg1.info(info_flags::integer) && !arg1.info(info_flags::positive)) + arg1_ser = tgamma(arg1+s); + else + arg1_ser = tgamma(arg1); + // trap the case where arg2 is on a pole: + if (arg2.info(info_flags::integer) && !arg2.info(info_flags::positive)) + arg2_ser = tgamma(arg2+s); + else + arg2_ser = tgamma(arg2); + // trap the case where arg1+arg2 is on a pole: + if ((arg1+arg2).info(info_flags::integer) && !(arg1+arg2).info(info_flags::positive)) + arg1arg2_ser = tgamma(arg2+arg1+s); + else + arg1arg2_ser = tgamma(arg2+arg1); + // compose the result (expanding all the terms): + return (arg1_ser*arg2_ser/arg1arg2_ser).series(rel, order, options).expand(); +} + +void beta_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\mbox{B}"; + inherited::do_print(c,level); +} + +////////// +// Psi-function (aka digamma-function) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(psi1_function, + print_func(&psi1_function::do_print_latex)) + +/** Evaluation of digamma-function psi(x). + * Somebody ought to provide some good numerical evaluation some day... */ +ex psi1_function::eval(int level) const +{ + const ex& x = seq[0]; + if (x.info(info_flags::numeric)) { + const numeric &nx = ex_to(x); + if (nx.is_integer()) { + // integer case + if (nx.is_positive()) { + // psi(n) -> 1 + 1/2 +...+ 1/(n-1) - Euler + numeric rat = 0; + for (numeric i(nx+(*_num_1_p)); i>0; --i) + rat += i.inverse(); + return rat-Euler; + } else { + // for non-positive integers there is a pole: + throw (pole_error("psi_eval(): simple pole",1)); + } + } + if (((*_num2_p)*nx).is_integer()) { + // half integer case + if (nx.is_positive()) { + // psi((2m+1)/2) -> 2/(2m+1) + 2/2m +...+ 2/1 - Euler - 2log(2) + numeric rat = 0; + for (numeric i = (nx+(*_num_1_p))*(*_num2_p); i>0; i-=(*_num2_p)) + rat += (*_num2_p)*i.inverse(); + return rat-Euler-_ex2*log(_ex2); + } else { + // use the recurrence relation + // psi(-m-1/2) == psi(-m-1/2+1) - 1 / (-m-1/2) + // to relate psi(-m-1/2) to psi(1/2): + // psi(-m-1/2) == psi(1/2) + r + // where r == ((-1/2)^(-1) + ... + (-m-1/2)^(-1)) + numeric recur = 0; + for (numeric p = nx; p<0; ++p) + recur -= pow(p, *_num_1_p); + return recur+psi(_ex1_2); + } + } + // psi1_evalf should be called here once it becomes available + } + + return this->hold(); +} + +ex psi1_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) { + try { + return psi(ex_to(x)); + } catch (const dunno &e) { } + } + + return this->hold(); +} + +ex psi1_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + GINAC_ASSERT(deriv_param==0); + + // d/dx psi(x) -> psi(1,x) + return psi(_ex1, x); +} + +ex psi1_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& arg = seq[0]; + // method: + // Taylor series where there is no pole falls back to polygamma function + // evaluation. + // On a pole at -m use the recurrence relation + // psi(x) == psi(x+1) - 1/z + // from which follows + // series(psi(x),x==-m,order) == + // series(psi(x+m+1) - 1/x - 1/(x+1) - 1/(x+m)),x==-m,order); + const ex arg_pt = arg.subs(rel, subs_options::no_pattern); + if (!arg_pt.info(info_flags::integer) || arg_pt.info(info_flags::positive)) + return basic::series(rel, order, options); + // if we got here we have to care for a simple pole at -m: + const numeric m = -ex_to(arg_pt); + ex recur; + for (numeric p; p<=m; ++p) + recur += power::power(arg+p,_ex_1); + return (psi(arg+m+_ex1)-recur).series(rel, order, options); +} + +void psi1_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\psi"; + inherited::do_print(c,level); +} + +////////// +// Psi-functions (aka polygamma-functions) psi(0,x)==psi(x) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(psi2_function, + print_func(&psi2_function::do_print_latex)) + +/** Evaluation of polygamma-function psi(n,x). + * Somebody ought to provide some good numerical evaluation some day... */ +ex psi2_function::eval(int eval) const +{ + const ex& n = seq[0]; + const ex& x = seq[1]; + // psi(0,x) -> psi(x) + if (n.is_zero()) + return psi(x); + // psi(-1,x) -> log(tgamma(x)) + if (n.is_equal(_ex_1)) + return log(tgamma(x)); + if (n.info(info_flags::numeric) && n.info(info_flags::posint) && + x.info(info_flags::numeric)) { + const numeric &nn = ex_to(n); + const numeric &nx = ex_to(x); + if (nx.is_integer()) { + // integer case + if (nx.is_equal(*_num1_p)) + // use psi(n,1) == (-)^(n+1) * n! * zeta(n+1) + return pow(*_num_1_p,nn+(*_num1_p))*factorial(nn)*zeta_function(nn+(*_num1_p)); + if (nx.is_positive()) { + // use the recurrence relation + // psi(n,m) == psi(n,m+1) - (-)^n * n! / m^(n+1) + // to relate psi(n,m) to psi(n,1): + // psi(n,m) == psi(n,1) + r + // where r == (-)^n * n! * (1^(-n-1) + ... + (m-1)^(-n-1)) + numeric recur = 0; + for (numeric p = 1; phold(); +} + +ex psi2_function::evalf(int eval) const +{ + const ex& n = seq[0]; + const ex& x = seq[1]; + if (is_exactly_a(n) && is_exactly_a(x)) { + try { + return psi(ex_to(n),ex_to(x)); + } catch (const dunno &e) { } + } + + return this->hold(); +} + +ex psi2_function::pderivative(unsigned deriv_param) const +{ + const ex& n = seq[0]; + const ex& x = seq[1]; + GINAC_ASSERT(deriv_param<2); + + if (deriv_param==0) { + // d/dn psi(n,x) + throw(std::logic_error("cannot diff psi(n,x) with respect to n")); + } + // d/dx psi(n,x) -> psi(n+1,x) + return psi(n+_ex1, x); +} + +ex psi2_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& n = seq[0]; + const ex& arg = seq[1]; + // method: + // Taylor series where there is no pole falls back to polygamma function + // evaluation. + // On a pole at -m use the recurrence relation + // psi(n,x) == psi(n,x+1) - (-)^n * n! / x^(n+1) + // from which follows + // series(psi(x),x==-m,order) == + // series(psi(x+m+1) - (-1)^n * n! * ((x)^(-n-1) + (x+1)^(-n-1) + ... + // ... + (x+m)^(-n-1))),x==-m,order); + const ex arg_pt = arg.subs(rel, subs_options::no_pattern); + if (!arg_pt.info(info_flags::integer) || arg_pt.info(info_flags::positive)) + return basic::series(rel, order, options); + // if we got here we have to care for a pole of order n+1 at -m: + const numeric m = -ex_to(arg_pt); + ex recur; + for (numeric p; p<=m; ++p) + recur += power(arg+p,-n+_ex_1); + recur *= factorial(n)*power(_ex_1,n); + return (psi(n, arg+m+_ex1)-recur).series(rel, order, options); +} + +void psi2_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\psi"; + inherited::do_print(c,level); +} + +} // namespace GiNaC diff --git a/ginac/inifcns_exp.h b/ginac/inifcns_exp.h new file mode 100644 index 00000000..b52646d8 --- /dev/null +++ b/ginac/inifcns_exp.h @@ -0,0 +1,164 @@ +/** @file inifcns_exp.h + * + * Interface to GiNaC's TODO */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __GINAC_INIFCNS_EXP_H__ +#define __GINAC_INIFCNS_EXP_H__ + +#include "numeric.h" +#include "function.h" +#include "ex.h" + +namespace GiNaC { + +/** Eta function: log(a*b) == log(a) + log(b) + eta(a, b). */ +////////// +// Eta function: eta(x,y) == log(x*y) - log(x) - log(y). +// This function is closely related to the unwinding number K, sometimes found +// in modern literature: K(z) == (z-log(exp(z)))/(2*Pi*I). +////////// +class eta_function : public function +{ + GINAC_DECLARE_FUNCTION_2P(eta_function) +public: + virtual ex conjugate() const; + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline eta_function eta(const T1& x1, const T2& x2) { return eta_function(x1, x2); } + +/** Exponential function. */ +class exp_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(exp_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; +protected: + void do_print_csrc_float(const print_context& c, unsigned level) const; + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline exp_function exp(const T1& x1) { return exp_function(x1); } +inline exp_function exp(double x1) { return exp_function(x1); } +inline exp_function exp(float x1) { return exp_function(x1); } + +/** Natural logarithm. */ +class log_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(log_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +protected: + void do_print_csrc_float(const print_context& c, unsigned level) const; + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline log_function log(const T1& x1) { return log_function(x1); } +inline log_function log(double x1) { return log_function(x1); } +inline log_function log(float x1) { return log_function(x1); } + +/** Log-Gamma-function. */ +class lgamma_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(lgamma_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline lgamma_function lgamma(const T1& x1) { return lgamma_function(x1); } + +/** Gamma-function. */ +class tgamma_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(tgamma_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline tgamma_function tgamma(const T1& x1) { return tgamma_function(x1); } + +/** Beta-function. */ +class beta_function : public function +{ + GINAC_DECLARE_FUNCTION_2P(beta_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline beta_function beta(const T1& x1, const T2& x2) { return beta_function(x1, x2); } + +/** Psi-function (aka digamma-function). */ +class psi1_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(psi1_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline psi1_function psi(const T1& x1) { return psi1_function(x1); } + +/** Derivatives of Psi-function (aka polygamma-functions). */ +class psi2_function : public function +{ + GINAC_DECLARE_FUNCTION_2P(psi2_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline psi2_function psi(const T1& x1, const T2& x2) { return psi2_function(x1, x2); } + +} // namespace GiNaC + +#endif // ndef __GINAC_INIFCNS_EXP_H__ diff --git a/ginac/inifcns_gamma.cpp b/ginac/inifcns_gamma.cpp deleted file mode 100644 index bb3a74e0..00000000 --- a/ginac/inifcns_gamma.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/** @file inifcns_gamma.cpp - * - * Implementation of Gamma-function, Polygamma-functions, and some related - * stuff. */ - -/* - * GiNaC Copyright (C) 1999 Johannes Gutenberg University Mainz, Germany - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include -#include - -#include "inifcns.h" -#include "ex.h" -#include "constant.h" -#include "numeric.h" -#include "power.h" -#include "symbol.h" - -namespace GiNaC { - -////////// -// Gamma-function -////////// - -/** Evaluation of gamma(x). Knows about integer arguments, half-integer - * arguments and that's it. Somebody ought to provide some good numerical - * evaluation some day... - * - * @exception fail_numeric("complex_infinity") or something similar... */ -static ex gamma_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // trap integer arguments: - if ( x.info(info_flags::integer) ) { - // gamma(n+1) -> n! for postitive n - if ( x.info(info_flags::posint) ) { - return factorial(ex_to_numeric(x).sub(numONE())); - } else { - return numZERO(); // Infinity. Throw? What? - } - } - // trap half integer arguments: - if ( (x*2).info(info_flags::integer) ) { - // trap positive x=(n+1/2) - // gamma(n+1/2) -> Pi^(1/2)*(1*3*..*(2*n-1))/(2^n) - if ( (x*2).info(info_flags::posint) ) { - numeric n = ex_to_numeric(x).sub(numHALF()); - numeric coefficient = doublefactorial(n.mul(numTWO()).sub(numONE())); - coefficient = coefficient.div(numTWO().power(n)); - return coefficient * pow(Pi,numHALF()); - } else { - // trap negative x=(-n+1/2) - // gamma(-n+1/2) -> Pi^(1/2)*(-2)^n/(1*3*..*(2*n-1)) - numeric n = abs(ex_to_numeric(x).sub(numHALF())); - numeric coefficient = numeric(-2).power(n); - coefficient = coefficient.div(doublefactorial(n.mul(numTWO()).sub(numONE())));; - return coefficient*sqrt(Pi); - } - } - } - return gamma(x).hold(); -} - -static ex gamma_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(gamma(x)) - - return gamma(ex_to_numeric(x)); -} - -static ex gamma_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return psi(exZERO(),x)*gamma(x); // diff(log(gamma(x)),x)==psi(0,x) -} - -static ex gamma_series(ex const & x, symbol const & s, ex const & point, int order) -{ - // FIXME: Only handle one special case for now... - if (x.is_equal(s) && point.is_zero()) { - ex e = 1 / s - EulerGamma + s * (pow(Pi, 2) / 12 + pow(EulerGamma, 2) / 2) + Order(pow(s, 2)); - return e.series(s, point, order); - } else - throw(std::logic_error("don't know the series expansion of this particular gamma function")); -} - -REGISTER_FUNCTION(gamma, gamma_eval, gamma_evalf, gamma_diff, gamma_series); - -////////// -// Psi-function (aka polygamma-function) -////////// - -/** Evaluation of polygamma-function psi(n,x). - * Somebody ought to provide some good numerical evaluation some day... */ -static ex psi_eval(ex const & n, ex const & x) -{ - if (n.info(info_flags::numeric) && x.info(info_flags::numeric)) { - // do some stuff... - } - return psi(n, x).hold(); -} - -static ex psi_evalf(ex const & n, ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(n,numeric) - TYPECHECK(x,numeric) - END_TYPECHECK(psi(n,x)) - - return psi(ex_to_numeric(n), ex_to_numeric(x)); -} - -static ex psi_diff(ex const & n, ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return psi(n+1, x); -} - -static ex psi_series(ex const & n, ex const & x, symbol const & s, ex const & point, int order) -{ - throw(std::logic_error("Nobody told me how to series expand the psi function. :-(")); -} - -REGISTER_FUNCTION(psi, psi_eval, psi_evalf, psi_diff, psi_series); - -} // namespace GiNaC diff --git a/ginac/inifcns_polylog.cpp b/ginac/inifcns_polylog.cpp new file mode 100644 index 00000000..1fe9cd2f --- /dev/null +++ b/ginac/inifcns_polylog.cpp @@ -0,0 +1,3969 @@ +/** @file inifcns_polylog.cpp + * + * Implementation of some special functions that have a representation as nested sums. TODO + * + * The functions are: + * classical polylogarithm Li(n,x) + * multiple polylogarithm Li(lst(m_1,...,m_k),lst(x_1,...,x_k)) + * G(lst(a_1,...,a_k),y) or G(lst(a_1,...,a_k),lst(s_1,...,s_k),y) + * Nielsen's generalized polylogarithm S(n,p,x) + * harmonic polylogarithm H(m,x) or H(lst(m_1,...,m_k),x) + * multiple zeta value zeta(m) or zeta(lst(m_1,...,m_k)) + * alternating Euler sum zeta(m,s) or zeta(lst(m_1,...,m_k),lst(s_1,...,s_k)) + * + * Some remarks: + * + * - All formulae used can be looked up in the following publications: + * [Kol] Nielsen's Generalized Polylogarithms, K.S.Kolbig, SIAM J.Math.Anal. 17 (1986), pp. 1232-1258. + * [Cra] Fast Evaluation of Multiple Zeta Sums, R.E.Crandall, Math.Comp. 67 (1998), pp. 1163-1172. + * [ReV] Harmonic Polylogarithms, E.Remiddi, J.A.M.Vermaseren, Int.J.Mod.Phys. A15 (2000), pp. 725-754 + * [BBB] Special Values of Multiple Polylogarithms, J.Borwein, D.Bradley, D.Broadhurst, P.Lisonek, Trans.Amer.Math.Soc. 353/3 (2001), pp. 907-941 + * [VSW] Numerical evaluation of multiple polylogarithms, J.Vollinga, S.Weinzierl, hep-ph/0410259 + * + * - The order of parameters and arguments of Li and zeta is defined according to the nested sums + * representation. The parameters for H are understood as in [ReV]. They can be in expanded --- only + * 0, 1 and -1 --- or in compactified --- a string with zeros in front of 1 or -1 is written as a single + * number --- notation. + * + * - All functions can be nummerically evaluated with arguments in the whole complex plane. The parameters + * for Li, zeta and S must be positive integers. If you want to have an alternating Euler sum, you have + * to give the signs of the parameters as a second argument s to zeta(m,s) containing 1 and -1. + * + * - The calculation of classical polylogarithms is speeded up by using Bernoulli numbers and + * look-up tables. S uses look-up tables as well. The zeta function applies the algorithms in + * [Cra] and [BBB] for speed up. Multiple polylogarithms use Hoelder convolution [BBB]. + * + * - The functions have no means to do a series expansion into nested sums. To do this, you have to convert + * these functions into the appropriate objects from the nestedsums library, do the expansion and convert + * the result back. + * + * - Numerical testing of this implementation has been performed by doing a comparison of results + * between this software and the commercial M.......... 4.1. Multiple zeta values have been checked + * by means of evaluations into simple zeta values. Harmonic polylogarithms have been checked by + * comparison to S(n,p,x) for corresponding parameter combinations and by continuity checks + * around |x|=1 along with comparisons to corresponding zeta functions. Multiple polylogarithms were + * checked against H and zeta and by means of shuffle and quasi-shuffle relations. + * + */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "inifcns_polylog.h" + +#include +#include +#include +#include + + +#include "inifcns.h" +#include "inifcns_exp.h" +#include "add.h" +#include "constant.h" +#include "lst.h" +#include "mul.h" +#include "numeric.h" +#include "operators.h" +#include "power.h" +#include "pseries.h" +#include "relational.h" +#include "symbol.h" +#include "utils.h" +#include "wildcard.h" + + +namespace GiNaC { + + +////////////////////////////////////////////////////////////////////// +// +// Classical polylogarithm Li(n,x) +// +// helper functions +// +////////////////////////////////////////////////////////////////////// + + +// anonymous namespace for helper functions +namespace { + + +// lookup table for factors built from Bernoulli numbers +// see fill_Xn() +std::vector > Xn; +// initial size of Xn that should suffice for 32bit machines (must be even) +const int xninitsizestep = 26; +int xninitsize = xninitsizestep; +int xnsize = 0; + + +// This function calculates the X_n. The X_n are needed for speed up of classical polylogarithms. +// With these numbers the polylogs can be calculated as follows: +// Li_p (x) = \sum_{n=0}^\infty X_{p-2}(n) u^{n+1}/(n+1)! with u = -log(1-x) +// X_0(n) = B_n (Bernoulli numbers) +// X_p(n) = \sum_{k=0}^n binomial(n,k) B_{n-k} / (k+1) * X_{p-1}(k) +// The calculation of Xn depends on X0 and X{n-1}. +// X_0 is special, it holds only the non-zero Bernoulli numbers with index 2 or greater. +// This results in a slightly more complicated algorithm for the X_n. +// The first index in Xn corresponds to the index of the polylog minus 2. +// The second index in Xn corresponds to the index from the actual sum. +void fill_Xn(int n) +{ + if (n>1) { + // calculate X_2 and higher (corresponding to Li_4 and higher) + std::vector buf(xninitsize); + std::vector::iterator it = buf.begin(); + cln::cl_N result; + *it = -(cln::expt(cln::cl_I(2),n+1) - 1) / cln::expt(cln::cl_I(2),n+1); // i == 1 + it++; + for (int i=2; i<=xninitsize; i++) { + if (i&1) { + result = 0; // k == 0 + } else { + result = Xn[0][i/2-1]; // k == 0 + } + for (int k=1; k 1)) ) { + result = result + cln::binomial(i,k) * Xn[0][(i-k)/2-1] * Xn[n-1][k-1] / (k+1); + } + } + result = result - cln::binomial(i,i-1) * Xn[n-1][i-2] / 2 / i; // k == i-1 + result = result + Xn[n-1][i-1] / (i+1); // k == i + + *it = result; + it++; + } + Xn.push_back(buf); + } else if (n==1) { + // special case to handle the X_0 correct + std::vector buf(xninitsize); + std::vector::iterator it = buf.begin(); + cln::cl_N result; + *it = cln::cl_I(-3)/cln::cl_I(4); // i == 1 + it++; + *it = cln::cl_I(17)/cln::cl_I(36); // i == 2 + it++; + for (int i=3; i<=xninitsize; i++) { + if (i & 1) { + result = -Xn[0][(i-3)/2]/2; + *it = (cln::binomial(i,1)/cln::cl_I(2) + cln::binomial(i,i-1)/cln::cl_I(i))*result; + it++; + } else { + result = Xn[0][i/2-1] + Xn[0][i/2-1]/(i+1); + for (int k=1; k buf(xninitsize/2); + std::vector::iterator it = buf.begin(); + for (int i=1; i<=xninitsize/2; i++) { + *it = bernoulli(i*2).to_cl_N(); + it++; + } + Xn.push_back(buf); + } + + xnsize++; +} + +// doubles the number of entries in each Xn[] +void double_Xn() +{ + const int pos0 = xninitsize / 2; + // X_0 + for (int i=1; i<=xninitsizestep/2; ++i) { + Xn[0].push_back(bernoulli((i+pos0)*2).to_cl_N()); + } + if (Xn.size() > 1) { + int xend = xninitsize + xninitsizestep; + cln::cl_N result; + // X_1 + for (int i=xninitsize+1; i<=xend; ++i) { + if (i & 1) { + result = -Xn[0][(i-3)/2]/2; + Xn[1].push_back((cln::binomial(i,1)/cln::cl_I(2) + cln::binomial(i,i-1)/cln::cl_I(i))*result); + } else { + result = Xn[0][i/2-1] + Xn[0][i/2-1]/(i+1); + for (int k=1; k 1)) ) { + result = result + cln::binomial(i,k) * Xn[0][(i-k)/2-1] * Xn[n-1][k-1] / (k+1); + } + } + result = result - cln::binomial(i,i-1) * Xn[n-1][i-2] / 2 / i; // k == i-1 + result = result + Xn[n-1][i-1] / (i+1); // k == i + Xn[n].push_back(result); + } + } + } + xninitsize += xninitsizestep; +} + + +// calculates Li(2,x) without Xn +cln::cl_N Li2_do_sum(const cln::cl_N& x) +{ + cln::cl_N res = x; + cln::cl_N resbuf; + cln::cl_N num = x * cln::cl_float(1, cln::float_format(Digits)); + cln::cl_I den = 1; // n^2 = 1 + unsigned i = 3; + do { + resbuf = res; + num = num * x; + den = den + i; // n^2 = 4, 9, 16, ... + i += 2; + res = res + num / den; + } while (res != resbuf); + return res; +} + + +// calculates Li(2,x) with Xn +cln::cl_N Li2_do_sum_Xn(const cln::cl_N& x) +{ + std::vector::const_iterator it = Xn[0].begin(); + std::vector::const_iterator xend = Xn[0].end(); + cln::cl_N u = -cln::log(1-x); + cln::cl_N factor = u * cln::cl_float(1, cln::float_format(Digits)); + cln::cl_N uu = cln::square(u); + cln::cl_N res = u - uu/4; + cln::cl_N resbuf; + unsigned i = 1; + do { + resbuf = res; + factor = factor * uu / (2*i * (2*i+1)); + res = res + (*it) * factor; + i++; + if (++it == xend) { + double_Xn(); + it = Xn[0].begin() + (i-1); + xend = Xn[0].end(); + } + } while (res != resbuf); + return res; +} + + +// calculates Li(n,x), n>2 without Xn +cln::cl_N Lin_do_sum(int n, const cln::cl_N& x) +{ + cln::cl_N factor = x * cln::cl_float(1, cln::float_format(Digits)); + cln::cl_N res = x; + cln::cl_N resbuf; + int i=2; + do { + resbuf = res; + factor = factor * x; + res = res + factor / cln::expt(cln::cl_I(i),n); + i++; + } while (res != resbuf); + return res; +} + + +// calculates Li(n,x), n>2 with Xn +cln::cl_N Lin_do_sum_Xn(int n, const cln::cl_N& x) +{ + std::vector::const_iterator it = Xn[n-2].begin(); + std::vector::const_iterator xend = Xn[n-2].end(); + cln::cl_N u = -cln::log(1-x); + cln::cl_N factor = u * cln::cl_float(1, cln::float_format(Digits)); + cln::cl_N res = u; + cln::cl_N resbuf; + unsigned i=2; + do { + resbuf = res; + factor = factor * u / i; + res = res + (*it) * factor; + i++; + if (++it == xend) { + double_Xn(); + it = Xn[n-2].begin() + (i-2); + xend = Xn[n-2].end(); + } + } while (res != resbuf); + return res; +} + + +// forward declaration needed by function Li_projection and C below +numeric S_num(int n, int p, const numeric& x); + + +// helper function for classical polylog Li +cln::cl_N Li_projection(int n, const cln::cl_N& x, const cln::float_format_t& prec) +{ + // treat n=2 as special case + if (n == 2) { + // check if precalculated X0 exists + if (xnsize == 0) { + fill_Xn(0); + } + + if (cln::realpart(x) < 0.5) { + // choose the faster algorithm + // the switching point was empirically determined. the optimal point + // depends on hardware, Digits, ... so an approx value is okay. + // it solves also the problem with precision due to the u=-log(1-x) transformation + if (cln::abs(cln::realpart(x)) < 0.25) { + + return Li2_do_sum(x); + } else { + return Li2_do_sum_Xn(x); + } + } else { + // choose the faster algorithm + if (cln::abs(cln::realpart(x)) > 0.75) { + return -Li2_do_sum(1-x) - cln::log(x) * cln::log(1-x) + cln::zeta(2); + } else { + return -Li2_do_sum_Xn(1-x) - cln::log(x) * cln::log(1-x) + cln::zeta(2); + } + } + } else { + // check if precalculated Xn exist + if (n > xnsize+1) { + for (int i=xnsize; i=12 the "normal" summation always wins against the method with Xn + if ((cln::abs(cln::realpart(x)) < 0.3) || (n >= 12)) { + return Lin_do_sum(n, x); + } else { + return Lin_do_sum_Xn(n, x); + } + } else { + cln::cl_N result = -cln::expt(cln::log(x), n-1) * cln::log(1-x) / cln::factorial(n-1); + for (int j=0; j(x).to_cl_N(); + cln::cl_N result = -cln::expt(cln::log(x_), n-1) * cln::log(1-x_) / cln::factorial(n-1); + for (int j=0; j(cln::realpart(value))); + else if (!x.imag().is_rational()) + prec = cln::float_format(cln::the(cln::imagpart(value))); + + // [Kol] (5.15) + if (cln::abs(value) > 1) { + cln::cl_N result = -cln::expt(cln::log(-value),n) / cln::factorial(n); + // check if argument is complex. if it is real, the new polylog has to be conjugated. + if (cln::zerop(cln::imagpart(value))) { + if (n & 1) { + result = result + cln::conjugate(Li_projection(n, cln::recip(value), prec)); + } + else { + result = result - cln::conjugate(Li_projection(n, cln::recip(value), prec)); + } + } + else { + if (n & 1) { + result = result + Li_projection(n, cln::recip(value), prec); + } + else { + result = result - Li_projection(n, cln::recip(value), prec); + } + } + cln::cl_N add; + for (int j=0; j& s, const std::vector& x) +{ + const int j = s.size(); + + std::vector t(j); + cln::cl_F one = cln::cl_float(1, cln::float_format(Digits)); + + cln::cl_N t0buf; + int q = 0; + do { + t0buf = t[0]; + // do it once ... + q++; + t[j-1] = t[j-1] + cln::expt(x[j-1], q) / cln::expt(cln::cl_I(q),s[j-1]) * one; + for (int k=j-2; k>=0; k--) { + t[k] = t[k] + t[k+1] * cln::expt(x[k], q+j-1-k) / cln::expt(cln::cl_I(q+j-1-k), s[k]); + } + // ... and do it again (to avoid premature drop out due to special arguments) + q++; + t[j-1] = t[j-1] + cln::expt(x[j-1], q) / cln::expt(cln::cl_I(q),s[j-1]) * one; + for (int k=j-2; k>=0; k--) { + t[k] = t[k] + t[k+1] * cln::expt(x[k], q+j-1-k) / cln::expt(cln::cl_I(q+j-1-k), s[k]); + } + } while (t[0] != t0buf); + + return t[0]; +} + + +// converts parameter types and calls multipleLi_do_sum (convenience function for G_numeric) +cln::cl_N mLi_do_summation(const lst& m, const lst& x) +{ + std::vector m_int; + std::vector x_cln; + for (lst::const_iterator itm = m.begin(), itx = x.begin(); itm != m.end(); ++itm, ++itx) { + m_int.push_back(ex_to(*itm).to_int()); + x_cln.push_back(ex_to(*itx).to_cl_N()); + } + return multipleLi_do_sum(m_int, x_cln); +} + + +// forward declaration for Li_eval() +lst convert_parameter_Li_to_H(const lst& m, const lst& x, ex& pf); + + +// holding dummy-symbols for the G/Li transformations +std::vector gsyms; + + +// type used by the transformation functions for G +typedef std::vector Gparameter; + + +// G_eval1-function for G transformations +ex G_eval1(int a, int scale) +{ + if (a != 0) { + const ex& scs = gsyms[std::abs(scale)]; + const ex& as = gsyms[std::abs(a)]; + if (as != scs) { + return -log(1 - scs/as); + } else { + return -zeta(1); + } + } else { + return log(gsyms[std::abs(scale)]); + } +} + + +// G_eval-function for G transformations +ex G_eval(const Gparameter& a, int scale) +{ + // check for properties of G + ex sc = gsyms[std::abs(scale)]; + lst newa; + bool all_zero = true; + bool all_ones = true; + int count_ones = 0; + for (Gparameter::const_iterator it = a.begin(); it != a.end(); ++it) { + if (*it != 0) { + const ex sym = gsyms[std::abs(*it)]; + newa.append(sym); + all_zero = false; + if (sym != sc) { + all_ones = false; + } + if (all_ones) { + ++count_ones; + } + } else { + all_ones = false; + } + } + + // care about divergent G: shuffle to separate divergencies that will be canceled + // later on in the transformation + if (newa.nops() > 1 && newa.op(0) == sc && !all_ones && a.front()!=0) { + // do shuffle + Gparameter short_a; + Gparameter::const_iterator it = a.begin(); + ++it; + for (; it != a.end(); ++it) { + short_a.push_back(*it); + } + ex result = G_eval1(a.front(), scale) * G_eval(short_a, scale); + it = short_a.begin(); + for (int i=1; i G({1};y)^k / k! + if (all_ones && a.size() > 1) { + return pow(G_eval1(a.front(),scale), count_ones) / factorial(count_ones); + } + + // G({0,...,0};y) -> log(y)^k / k! + if (all_zero) { + return pow(log(gsyms[std::abs(scale)]), a.size()) / factorial(a.size()); + } + + // no special cases anymore -> convert it into Li + lst m; + lst x; + ex argbuf = gsyms[std::abs(scale)]; + ex mval = _ex1; + for (Gparameter::const_iterator it=a.begin(); it!=a.end(); ++it) { + if (*it != 0) { + const ex& sym = gsyms[std::abs(*it)]; + x.append(argbuf / sym); + m.append(mval); + mval = _ex1; + argbuf = sym; + } else { + ++mval; + } + } + return pow(-1, x.nops()) * Li(m, x); +} + + +// converts data for G: pending_integrals -> a +Gparameter convert_pending_integrals_G(const Gparameter& pending_integrals) +{ + GINAC_ASSERT(pending_integrals.size() != 1); + + if (pending_integrals.size() > 0) { + // get rid of the first element, which would stand for the new upper limit + Gparameter new_a(pending_integrals.begin()+1, pending_integrals.end()); + return new_a; + } else { + // just return empty parameter list + Gparameter new_a; + return new_a; + } +} + + +// check the parameters a and scale for G and return information about convergence, depth, etc. +// convergent : true if G(a,scale) is convergent +// depth : depth of G(a,scale) +// trailing_zeros : number of trailing zeros of a +// min_it : iterator of a pointing on the smallest element in a +Gparameter::const_iterator check_parameter_G(const Gparameter& a, int scale, + bool& convergent, int& depth, int& trailing_zeros, Gparameter::const_iterator& min_it) +{ + convergent = true; + depth = 0; + trailing_zeros = 0; + min_it = a.end(); + Gparameter::const_iterator lastnonzero = a.end(); + for (Gparameter::const_iterator it = a.begin(); it != a.end(); ++it) { + if (std::abs(*it) > 0) { + ++depth; + trailing_zeros = 0; + lastnonzero = it; + if (std::abs(*it) < scale) { + convergent = false; + if ((min_it == a.end()) || (std::abs(*it) < std::abs(*min_it))) { + min_it = it; + } + } + } else { + ++trailing_zeros; + } + } + return ++lastnonzero; +} + + +// add scale to pending_integrals if pending_integrals is empty +Gparameter prepare_pending_integrals(const Gparameter& pending_integrals, int scale) +{ + GINAC_ASSERT(pending_integrals.size() != 1); + + if (pending_integrals.size() > 0) { + return pending_integrals; + } else { + Gparameter new_pending_integrals; + new_pending_integrals.push_back(scale); + return new_pending_integrals; + } +} + + +// handles trailing zeroes for an otherwise convergent integral +ex trailing_zeros_G(const Gparameter& a, int scale) +{ + bool convergent; + int depth, trailing_zeros; + Gparameter::const_iterator last, dummyit; + last = check_parameter_G(a, scale, convergent, depth, trailing_zeros, dummyit); + + GINAC_ASSERT(convergent); + + if ((trailing_zeros > 0) && (depth > 0)) { + ex result; + Gparameter new_a(a.begin(), a.end()-1); + result += G_eval1(0, scale) * trailing_zeros_G(new_a, scale); + for (Gparameter::const_iterator it = a.begin(); it != last; ++it) { + Gparameter new_a(a.begin(), it); + new_a.push_back(0); + new_a.insert(new_a.end(), it, a.end()-1); + result -= trailing_zeros_G(new_a, scale); + } + + return result / trailing_zeros; + } else { + return G_eval(a, scale); + } +} + + +// G transformation [VSW] (57),(58) +ex depth_one_trafo_G(const Gparameter& pending_integrals, const Gparameter& a, int scale) +{ + // pendint = ( y1, b1, ..., br ) + // a = ( 0, ..., 0, amin ) + // scale = y2 + // + // int_0^y1 ds1/(s1-b1) ... int dsr/(sr-br) G(0, ..., 0, sr; y2) + // where sr replaces amin + + GINAC_ASSERT(a.back() != 0); + GINAC_ASSERT(a.size() > 0); + + ex result; + Gparameter new_pending_integrals = prepare_pending_integrals(pending_integrals, std::abs(a.back())); + const int psize = pending_integrals.size(); + + // length == 1 + // G(sr_{+-}; y2 ) = G(y2_{-+}; sr) - G(0; sr) + ln(-y2_{-+}) + + if (a.size() == 1) { + + // ln(-y2_{-+}) + result += log(gsyms[ex_to(scale).to_int()]); + if (a.back() > 0) { + new_pending_integrals.push_back(-scale); + result += I*Pi; + } else { + new_pending_integrals.push_back(scale); + result -= I*Pi; + } + if (psize) { + result *= trailing_zeros_G(convert_pending_integrals_G(pending_integrals), pending_integrals.front()); + } + + // G(y2_{-+}; sr) + result += trailing_zeros_G(convert_pending_integrals_G(new_pending_integrals), new_pending_integrals.front()); + + // G(0; sr) + new_pending_integrals.back() = 0; + result -= trailing_zeros_G(convert_pending_integrals_G(new_pending_integrals), new_pending_integrals.front()); + + return result; + } + + // length > 1 + // G_m(sr_{+-}; y2) = -zeta_m + int_0^y2 dt/t G_{m-1}( (1/y2)_{+-}; 1/t ) + // - int_0^sr dt/t G_{m-1}( (1/y2)_{+-}; 1/t ) + + //term zeta_m + result -= zeta(a.size()); + if (psize) { + result *= trailing_zeros_G(convert_pending_integrals_G(pending_integrals), pending_integrals.front()); + } + + // term int_0^sr dt/t G_{m-1}( (1/y2)_{+-}; 1/t ) + // = int_0^sr dt/t G_{m-1}( t_{+-}; y2 ) + Gparameter new_a(a.begin()+1, a.end()); + new_pending_integrals.push_back(0); + result -= depth_one_trafo_G(new_pending_integrals, new_a, scale); + + // term int_0^y2 dt/t G_{m-1}( (1/y2)_{+-}; 1/t ) + // = int_0^y2 dt/t G_{m-1}( t_{+-}; y2 ) + Gparameter new_pending_integrals_2; + new_pending_integrals_2.push_back(scale); + new_pending_integrals_2.push_back(0); + if (psize) { + result += trailing_zeros_G(convert_pending_integrals_G(pending_integrals), pending_integrals.front()) + * depth_one_trafo_G(new_pending_integrals_2, new_a, scale); + } else { + result += depth_one_trafo_G(new_pending_integrals_2, new_a, scale); + } + + return result; +} + + +// forward declaration +ex shuffle_G(const Gparameter & a0, const Gparameter & a1, const Gparameter & a2, + const Gparameter& pendint, const Gparameter& a_old, int scale); + + +// G transformation [VSW] +ex G_transform(const Gparameter& pendint, const Gparameter& a, int scale) +{ + // main recursion routine + // + // pendint = ( y1, b1, ..., br ) + // a = ( a1, ..., amin, ..., aw ) + // scale = y2 + // + // int_0^y1 ds1/(s1-b1) ... int dsr/(sr-br) G(a1,...,sr,...,aw,y2) + // where sr replaces amin + + // find smallest alpha, determine depth and trailing zeros, and check for convergence + bool convergent; + int depth, trailing_zeros; + Gparameter::const_iterator min_it; + Gparameter::const_iterator firstzero = + check_parameter_G(a, scale, convergent, depth, trailing_zeros, min_it); + int min_it_pos = min_it - a.begin(); + + // special case: all a's are zero + if (depth == 0) { + ex result; + + if (a.size() == 0) { + result = 1; + } else { + result = G_eval(a, scale); + } + if (pendint.size() > 0) { + result *= trailing_zeros_G(convert_pending_integrals_G(pendint), pendint.front()); + } + return result; + } + + // handle trailing zeros + if (trailing_zeros > 0) { + ex result; + Gparameter new_a(a.begin(), a.end()-1); + result += G_eval1(0, scale) * G_transform(pendint, new_a, scale); + for (Gparameter::const_iterator it = a.begin(); it != firstzero; ++it) { + Gparameter new_a(a.begin(), it); + new_a.push_back(0); + new_a.insert(new_a.end(), it, a.end()-1); + result -= G_transform(pendint, new_a, scale); + } + return result / trailing_zeros; + } + + // convergence case + if (convergent) { + if (pendint.size() > 0) { + return G_eval(convert_pending_integrals_G(pendint), pendint.front()) * G_eval(a, scale); + } else { + return G_eval(a, scale); + } + } + + // call basic transformation for depth equal one + if (depth == 1) { + return depth_one_trafo_G(pendint, a, scale); + } + + // do recursion + // int_0^y1 ds1/(s1-b1) ... int dsr/(sr-br) G(a1,...,sr,...,aw,y2) + // = int_0^y1 ds1/(s1-b1) ... int dsr/(sr-br) G(a1,...,0,...,aw,y2) + // + int_0^y1 ds1/(s1-b1) ... int dsr/(sr-br) int_0^{sr} ds_{r+1} d/ds_{r+1} G(a1,...,s_{r+1},...,aw,y2) + + // smallest element in last place + if (min_it + 1 == a.end()) { + do { --min_it; } while (*min_it == 0); + Gparameter empty; + Gparameter a1(a.begin(),min_it+1); + Gparameter a2(min_it+1,a.end()); + + ex result = G_transform(pendint,a2,scale)*G_transform(empty,a1,scale); + + result -= shuffle_G(empty,a1,a2,pendint,a,scale); + return result; + } + + Gparameter empty; + Gparameter::iterator changeit; + + // first term G(a_1,..,0,...,a_w;a_0) + Gparameter new_pendint = prepare_pending_integrals(pendint, a[min_it_pos]); + Gparameter new_a = a; + new_a[min_it_pos] = 0; + ex result = G_transform(empty, new_a, scale); + if (pendint.size() > 0) { + result *= trailing_zeros_G(convert_pending_integrals_G(pendint), pendint.front()); + } + + // other terms + changeit = new_a.begin() + min_it_pos; + changeit = new_a.erase(changeit); + if (changeit != new_a.begin()) { + // smallest in the middle + new_pendint.push_back(*changeit); + result -= trailing_zeros_G(convert_pending_integrals_G(new_pendint), new_pendint.front()) + * G_transform(empty, new_a, scale); + int buffer = *changeit; + *changeit = *min_it; + result += G_transform(new_pendint, new_a, scale); + *changeit = buffer; + new_pendint.pop_back(); + --changeit; + new_pendint.push_back(*changeit); + result += trailing_zeros_G(convert_pending_integrals_G(new_pendint), new_pendint.front()) + * G_transform(empty, new_a, scale); + *changeit = *min_it; + result -= G_transform(new_pendint, new_a, scale); + } else { + // smallest at the front + new_pendint.push_back(scale); + result += trailing_zeros_G(convert_pending_integrals_G(new_pendint), new_pendint.front()) + * G_transform(empty, new_a, scale); + new_pendint.back() = *changeit; + result -= trailing_zeros_G(convert_pending_integrals_G(new_pendint), new_pendint.front()) + * G_transform(empty, new_a, scale); + *changeit = *min_it; + result += G_transform(new_pendint, new_a, scale); + } + return result; +} + + +// shuffles the two parameter list a1 and a2 and calls G_transform for every term except +// for the one that is equal to a_old +ex shuffle_G(const Gparameter & a0, const Gparameter & a1, const Gparameter & a2, + const Gparameter& pendint, const Gparameter& a_old, int scale) +{ + if (a1.size()==0 && a2.size()==0) { + // veto the one configuration we don't want + if ( a0 == a_old ) return 0; + + return G_transform(pendint,a0,scale); + } + + if (a2.size()==0) { + Gparameter empty; + Gparameter aa0 = a0; + aa0.insert(aa0.end(),a1.begin(),a1.end()); + return shuffle_G(aa0,empty,empty,pendint,a_old,scale); + } + + if (a1.size()==0) { + Gparameter empty; + Gparameter aa0 = a0; + aa0.insert(aa0.end(),a2.begin(),a2.end()); + return shuffle_G(aa0,empty,empty,pendint,a_old,scale); + } + + Gparameter a1_removed(a1.begin()+1,a1.end()); + Gparameter a2_removed(a2.begin()+1,a2.end()); + + Gparameter a01 = a0; + Gparameter a02 = a0; + + a01.push_back( a1[0] ); + a02.push_back( a2[0] ); + + return shuffle_G(a01,a1_removed,a2,pendint,a_old,scale) + + shuffle_G(a02,a1,a2_removed,pendint,a_old,scale); +} + + +// handles the transformations and the numerical evaluation of G +// the parameter x, s and y must only contain numerics +ex G_numeric(const lst& x, const lst& s, const ex& y) +{ + // check for convergence and necessary accelerations + bool need_trafo = false; + bool need_hoelder = false; + int depth = 0; + for (lst::const_iterator it = x.begin(); it != x.end(); ++it) { + if (!(*it).is_zero()) { + ++depth; + if (abs(*it) - y < -pow(10,-Digits+2)) { + need_trafo = true; + break; + } + if (abs((abs(*it) - y)/y) < 0.01) { + need_hoelder = true; + } + } + } + if (x.op(x.nops()-1).is_zero()) { + need_trafo = true; + } + if (depth == 1 && !need_trafo) { + return -Li(x.nops(), y / x.op(x.nops()-1)).evalf(); + } + + // convergence transformation + if (need_trafo) { + + // sort (|x|<->position) to determine indices + std::multimap sortmap; + int size = 0; + for (int i=0; i(abs(x[i]), i)); + ++size; + } + } + // include upper limit (scale) + sortmap.insert(std::pair(abs(y), x.nops())); + + // generate missing dummy-symbols + int i = 1; + gsyms.clear(); + gsyms.push_back(symbol("GSYMS_ERROR")); + ex lastentry; + for (std::multimap::const_iterator it = sortmap.begin(); it != sortmap.end(); ++it) { + if (it != sortmap.begin()) { + if (it->second < x.nops()) { + if (x[it->second] == lastentry) { + gsyms.push_back(gsyms.back()); + continue; + } + } else { + if (y == lastentry) { + gsyms.push_back(gsyms.back()); + continue; + } + } + } + std::ostringstream os; + os << "a" << i; + gsyms.push_back(symbol(os.str())); + ++i; + if (it->second < x.nops()) { + lastentry = x[it->second]; + } else { + lastentry = y; + } + } + + // fill position data according to sorted indices and prepare substitution list + Gparameter a(x.nops()); + lst subslst; + int pos = 1; + int scale; + for (std::multimap::const_iterator it = sortmap.begin(); it != sortmap.end(); ++it) { + if (it->second < x.nops()) { + if (s[it->second] > 0) { + a[it->second] = pos; + } else { + a[it->second] = -pos; + } + subslst.append(gsyms[pos] == x[it->second]); + } else { + scale = pos; + subslst.append(gsyms[pos] == y); + } + ++pos; + } + + // do transformation + Gparameter pendint; + ex result = G_transform(pendint, a, scale); + // replace dummy symbols with their values + result = result.eval().expand(); + result = result.subs(subslst).evalf(); + + return result; + } + + // do acceleration transformation (hoelder convolution [BBB]) + if (need_hoelder) { + + ex result; + const int size = x.nops(); + lst newx; + for (lst::const_iterator it = x.begin(); it != x.end(); ++it) { + newx.append(*it / y); + } + + for (int r=0; r<=size; ++r) { + ex buffer = pow(-1, r); + ex p = 2; + bool adjustp; + do { + adjustp = false; + for (lst::const_iterator it = newx.begin(); it != newx.end(); ++it) { + if (*it == 1/p) { + p += (3-p)/2; + adjustp = true; + continue; + } + } + } while (adjustp); + ex q = p / (p-1); + lst qlstx; + lst qlsts; + for (int j=r; j>=1; --j) { + qlstx.append(1-newx.op(j-1)); + if (newx.op(j-1).info(info_flags::real) && newx.op(j-1) > 1 && newx.op(j-1) <= 2) { + qlsts.append( s.op(j-1)); + } else { + qlsts.append( -s.op(j-1)); + } + } + if (qlstx.nops() > 0) { + buffer *= G_numeric(qlstx, qlsts, 1/q); + } + lst plstx; + lst plsts; + for (int j=r+1; j<=size; ++j) { + plstx.append(newx.op(j-1)); + plsts.append(s.op(j-1)); + } + if (plstx.nops() > 0) { + buffer *= G_numeric(plstx, plsts, 1/p); + } + result += buffer; + } + return result; + } + + // do summation + lst newx; + lst m; + int mcount = 1; + ex sign = 1; + ex factor = y; + for (lst::const_iterator it = x.begin(); it != x.end(); ++it) { + if ((*it).is_zero()) { + ++mcount; + } else { + newx.append(factor / (*it)); + factor = *it; + m.append(mcount); + mcount = 1; + sign = -sign; + } + } + + return sign * numeric(mLi_do_summation(m, newx)); +} + + +ex mLi_numeric(const lst& m, const lst& x) +{ + // let G_numeric do the transformation + lst newx; + lst s; + ex factor = 1; + for (lst::const_iterator itm = m.begin(), itx = x.begin(); itm != m.end(); ++itm, ++itx) { + for (int i = 1; i < *itm; ++i) { + newx.append(0); + s.append(1); + } + newx.append(factor / *itx); + factor /= *itx; + s.append(1); + } + return pow(-1, m.nops()) * G_numeric(newx, s, _ex1); +} + + +} // end of anonymous namespace + + +////////////////////////////////////////////////////////////////////// +// +// Generalized multiple polylogarithm G(x, y) and G(x, s, y) +// +// GiNaC function +// +////////////////////////////////////////////////////////////////////// + +GINAC_IMPLEMENT_FUNCTION(G_function) + +ex G_function::eval(int level) const +{ + if (seq.size() == 2) { + const ex& x_ = seq[0]; + const ex& y = seq[1]; + // G2 + //TODO eval to MZV or H or S or Lin + if (!y.info(info_flags::positive)) { + return G(x_, y).hold(); + } + lst x = is_a(x_) ? ex_to(x_) : lst(x_); + if (x.nops() == 0) { + return _ex1; + } + if (x.op(0) == y) { + return G(x_, y).hold(); + } + lst s; + bool all_zero = true; + bool crational = true; + for (lst::const_iterator it = x.begin(); it != x.end(); ++it) { + if (!(*it).info(info_flags::numeric)) { + return G(x_, y).hold(); + } + if (!(*it).info(info_flags::crational)) { + crational = false; + } + if (*it != _ex0) { + all_zero = false; + } + s.append(+1); + } + if (all_zero) { + return pow(log(y), x.nops()) / factorial(x.nops()); + } + if (!y.info(info_flags::crational)) { + crational = false; + } + if (crational) { + return G(x_, y).hold(); + } + return G_numeric(x, s, y); + } else { + // G3 + const ex& x_ = seq[0]; + const ex& s_ = seq[1]; + const ex& y = seq[2]; + if (!y.info(info_flags::positive)) { + return G(x_, s_, y).hold(); + } + lst x = is_a(x_) ? ex_to(x_) : lst(x_); + lst s = is_a(s_) ? ex_to(s_) : lst(s_); + if (x.nops() != s.nops()) { + return G(x_, s_, y).hold(); + } + if (x.nops() == 0) { + return _ex1; + } + if (x.op(0) == y) { + return G(x_, s_, y).hold(); + } + lst sn; + bool all_zero = true; + bool crational = true; + for (lst::const_iterator itx = x.begin(), its = s.begin(); itx != x.end(); ++itx, ++its) { + if (!(*itx).info(info_flags::numeric)) { + return G(x_, s_, y).hold(); + } + if (!(*its).info(info_flags::real)) { + return G(x_, s_, y).hold(); + } + if (!(*itx).info(info_flags::crational)) { + crational = false; + } + if (*itx != _ex0) { + all_zero = false; + } + if (*its >= 0) { + sn.append(+1); + } else { + sn.append(-1); + } + } + if (all_zero) { + return pow(log(y), x.nops()) / factorial(x.nops()); + } + if (!y.info(info_flags::crational)) { + crational = false; + } + if (crational) { + return G(x_, s_, y).hold(); + } + return G_numeric(x, sn, y); + } +} + +ex G_function::evalf(int level) const +{ + if (seq.size() == 2) { + // G2 + const ex& x_ = seq[0]; + const ex& y = seq[1]; + if (!y.info(info_flags::positive)) { + return G(x_, y).hold(); + } + lst x = is_a(x_) ? ex_to(x_) : lst(x_); + if (x.nops() == 0) { + return _ex1; + } + if (x.op(0) == y) { + return G(x_, y).hold(); + } + lst s; + bool all_zero = true; + for (lst::const_iterator it = x.begin(); it != x.end(); ++it) { + if (!(*it).info(info_flags::numeric)) { + return G(x_, y).hold(); + } + if (*it != _ex0) { + all_zero = false; + } + s.append(+1); + } + if (all_zero) { + return pow(log(y), x.nops()) / factorial(x.nops()); + } + return G_numeric(x, s, y); + } else { + // G3 + const ex& x_ = seq[0]; + const ex& s_ = seq[1]; + const ex& y = seq[2]; + if (!y.info(info_flags::positive)) { + return G(x_, s_, y).hold(); + } + lst x = is_a(x_) ? ex_to(x_) : lst(x_); + lst s = is_a(s_) ? ex_to(s_) : lst(s_); + if (x.nops() != s.nops()) { + return G(x_, s_, y).hold(); + } + if (x.nops() == 0) { + return _ex1; + } + if (x.op(0) == y) { + return G(x_, s_, y).hold(); + } + lst sn; + bool all_zero = true; + for (lst::const_iterator itx = x.begin(), its = s.begin(); itx != x.end(); ++itx, ++its) { + if (!(*itx).info(info_flags::numeric)) { + return G(x_, y).hold(); + } + if (!(*its).info(info_flags::real)) { + return G(x_, y).hold(); + } + if (*itx != _ex0) { + all_zero = false; + } + if (*its >= 0) { + sn.append(+1); + } else { + sn.append(-1); + } + } + if (all_zero) { + return pow(log(y), x.nops()) / factorial(x.nops()); + } + return G_numeric(x, sn, y); + } +} + +////////////////////////////////////////////////////////////////////// +// +// Classical polylogarithm and multiple polylogarithm Li(m,x) +// +// GiNaC function +// +////////////////////////////////////////////////////////////////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(Li_function, + print_func(&Li_function::do_print_latex)) + +ex Li_function::eval(int level) const +{ + const ex& m_ = seq[0]; + const ex& x_ = seq[1]; + if (is_a(m_)) { + if (is_a(x_)) { + // multiple polylogs + const lst& m = ex_to(m_); + const lst& x = ex_to(x_); + if (m.nops() != x.nops()) { + return this->hold(); + } + if (x.nops() == 0) { + return _ex1; + } + bool is_H = true; + bool is_zeta = true; + bool do_evalf = true; + bool crational = true; + for (lst::const_iterator itm = m.begin(), itx = x.begin(); itm != m.end(); ++itm, ++itx) { + if (!(*itm).info(info_flags::posint)) { + return this->hold(); + } + if ((*itx != _ex1) && (*itx != _ex_1)) { + if (itx != x.begin()) { + is_H = false; + } + is_zeta = false; + } + if (*itx == _ex0) { + return _ex0; + } + if (!(*itx).info(info_flags::numeric)) { + do_evalf = false; + } + if (!(*itx).info(info_flags::crational)) { + crational = false; + } + } + if (is_zeta) { + return zeta(m_,x_); + } + if (is_H) { + ex prefactor; + lst newm = convert_parameter_Li_to_H(m, x, prefactor); + return prefactor * H(newm, x[0]); + } + if (do_evalf && !crational) { + return mLi_numeric(m,x); + } + } + return this->hold(); + } else if (is_a(x_)) { + return this->hold(); + } + + // classical polylogs + if (x_ == _ex0) { + return _ex0; + } + if (x_ == _ex1) { + return zeta(m_); + } + if (x_ == _ex_1) { + return (pow(2,1-m_)-1) * zeta(m_); + } + if (m_ == _ex1) { + return -log(1-x_); + } + if (m_ == _ex2) { + if (x_.is_equal(I)) { + return GiNaC::power(Pi,_ex2)/_ex_48 + Catalan*I; + } + if (x_.is_equal(-I)) { + return GiNaC::power(Pi,_ex2)/_ex_48 - Catalan*I; + } + } + if (m_.info(info_flags::posint) && x_.info(info_flags::numeric) && !x_.info(info_flags::crational)) { + return Lin_numeric(ex_to(m_).to_int(), ex_to(x_)); + } + + return this->hold(); +} + +ex Li_function::evalf(int level) const +{ + const ex& m_ = seq[0]; + const ex& x_ = seq[1]; + // classical polylogs + if (m_.info(info_flags::posint)) { + if (x_.info(info_flags::numeric)) { + return Lin_numeric(ex_to(m_).to_int(), ex_to(x_)); + } else { + // try to numerically evaluate second argument + ex x_val = x_.evalf(); + if (x_val.info(info_flags::numeric)) { + return Lin_numeric(ex_to(m_).to_int(), ex_to(x_val)); + } + } + } + // multiple polylogs + if (is_a(m_) && is_a(x_)) { + + const lst& m = ex_to(m_); + const lst& x = ex_to(x_); + if (m.nops() != x.nops()) { + return this->hold(); + } + if (x.nops() == 0) { + return _ex1; + } + if ((m.op(0) == _ex1) && (x.op(0) == _ex1)) { + return Li(m_,x_).hold(); + } + + for (lst::const_iterator itm = m.begin(), itx = x.begin(); itm != m.end(); ++itm, ++itx) { + if (!(*itm).info(info_flags::posint)) { + return this->hold(); + } + if (!(*itx).info(info_flags::numeric)) { + return this->hold(); + } + if (*itx == _ex0) { + return _ex0; + } + } + + return mLi_numeric(m, x); + } + + return this->hold(); +} + +ex Li_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& m = seq[0]; + const ex& x = seq[1]; + if (is_a(m) || is_a(x)) { + // multiple polylog + epvector seq; + seq.push_back(expair(Li(m, x), 0)); + return pseries(rel, seq); + } + + // classical polylog + const ex x_pt = x.subs(rel, subs_options::no_pattern); + if (m.info(info_flags::numeric) && x_pt.info(info_flags::numeric)) { + // First special case: x==0 (derivatives have poles) + if (x_pt.is_zero()) { + const symbol s; + ex ser; + // manually construct the primitive expansion + for (int i=1; i=1 (branch cut) + if (m.is_equal(_ex2)) { + // second special case: x==1 (branch point) + if (x_pt.is_equal(_ex1)) { + // method: + // construct series manually in a dummy symbol s + const symbol s; + ex ser = zeta(_ex2); + // manually construct the primitive expansion + for (int i=1; i=1 (branch cut) + if (!(options & series_options::suppress_branchcut) && + ex_to(x_pt).is_real() && ex_to(x_pt)>1) { + // method: + // This is the branch cut: assemble the primitive series manually + // and then add the corresponding complex step function. + const symbol &s = ex_to(rel.lhs()); + const ex point = rel.rhs(); + const symbol foo; + epvector seq; + // zeroth order term: + seq.push_back(expair(Li2(x_pt), _ex0)); + // compute the intermediate terms: + ex replarg = GiNaC::series(Li2(x), s==foo, order); + for (size_t i=1; i 1) { + throw std::runtime_error("don't know how to derivate multiple polylogarithm!"); + } + ex m; + if (is_a(m_)) { + m = m_.op(0); + } else { + m = m_; + } + ex x; + if (is_a(x_)) { + x = x_.op(0); + } else { + x = x_; + } + if (m > 0) { + return Li(m-1, x) / x; + } else { + return 1/(1-x); + } +} + +void Li_function::do_print_latex(const print_context& c, unsigned level) const +{ + const ex& m_ = seq[0]; + const ex& x_ = seq[1]; + lst m; + if (is_a(m_)) { + m = ex_to(m_); + } else { + m = lst(m_); + } + lst x; + if (is_a(x_)) { + x = ex_to(x_); + } else { + x = lst(x_); + } + c.s << "\\mbox{Li}_{"; + lst::const_iterator itm = m.begin(); + (*itm).print(c); + itm++; + for (; itm != m.end(); itm++) { + c.s << ","; + (*itm).print(c); + } + c.s << "}("; + lst::const_iterator itx = x.begin(); + (*itx).print(c); + itx++; + for (; itx != x.end(); itx++) { + c.s << ","; + (*itx).print(c); + } + c.s << ")"; +} + +////////////////////////////////////////////////////////////////////// +// +// Nielsen's generalized polylogarithm S(n,p,x) +// +// helper functions +// +////////////////////////////////////////////////////////////////////// + + +// anonymous namespace for helper functions +namespace { + + +// lookup table for special Euler-Zagier-Sums (used for S_n,p(x)) +// see fill_Yn() +std::vector > Yn; +int ynsize = 0; // number of Yn[] +int ynlength = 100; // initial length of all Yn[i] + + +// This function calculates the Y_n. The Y_n are needed for the evaluation of S_{n,p}(x). +// The Y_n are basically Euler-Zagier sums with all m_i=1. They are subsums in the Z-sum +// representing S_{n,p}(x). +// The first index in Y_n corresponds to the parameter p minus one, i.e. the depth of the +// equivalent Z-sum. +// The second index in Y_n corresponds to the running index of the outermost sum in the full Z-sum +// representing S_{n,p}(x). +// The calculation of Y_n uses the values from Y_{n-1}. +void fill_Yn(int n, const cln::float_format_t& prec) +{ + const int initsize = ynlength; + //const int initsize = initsize_Yn; + cln::cl_N one = cln::cl_float(1, prec); + + if (n) { + std::vector buf(initsize); + std::vector::iterator it = buf.begin(); + std::vector::iterator itprev = Yn[n-1].begin(); + *it = (*itprev) / cln::cl_N(n+1) * one; + it++; + itprev++; + // sums with an index smaller than the depth are zero and need not to be calculated. + // calculation starts with depth, which is n+2) + for (int i=n+2; i<=initsize+n; i++) { + *it = *(it-1) + (*itprev) / cln::cl_N(i) * one; + it++; + itprev++; + } + Yn.push_back(buf); + } else { + std::vector buf(initsize); + std::vector::iterator it = buf.begin(); + *it = 1 * one; + it++; + for (int i=2; i<=initsize; i++) { + *it = *(it-1) + 1 / cln::cl_N(i) * one; + it++; + } + Yn.push_back(buf); + } + ynsize++; +} + + +// make Yn longer ... +void make_Yn_longer(int newsize, const cln::float_format_t& prec) +{ + + cln::cl_N one = cln::cl_float(1, prec); + + Yn[0].resize(newsize); + std::vector::iterator it = Yn[0].begin(); + it += ynlength; + for (int i=ynlength+1; i<=newsize; i++) { + *it = *(it-1) + 1 / cln::cl_N(i) * one; + it++; + } + + for (int n=1; n::iterator it = Yn[n].begin(); + std::vector::iterator itprev = Yn[n-1].begin(); + it += ynlength; + itprev += ynlength; + for (int i=ynlength+n+1; i<=newsize+n; i++) { + *it = *(it-1) + (*itprev) / cln::cl_N(i) * one; + it++; + itprev++; + } + } + + ynlength = newsize; +} + + +// helper function for S(n,p,x) +// [Kol] (7.2) +cln::cl_N C(int n, int p) +{ + cln::cl_N result; + + for (int k=0; k ynsize+1) { + for (int i=ynsize; i= ynlength) { + // make Yn longer + make_Yn_longer(ynlength*2, prec); + } + res = res + factor / cln::expt(cln::cl_I(i),n+1) * Yn[p-2][i-p]; // should we check it? or rely on magic number? ... + //res = res + factor / cln::expt(cln::cl_I(i),n+1) * (*it); // should we check it? or rely on magic number? ... + factor = factor * xf; + i++; + } while (res != resbuf); + + return res; +} + + +// helper function for S(n,p,x) +cln::cl_N S_projection(int n, int p, const cln::cl_N& x, const cln::float_format_t& prec) +{ + // [Kol] (5.3) + if (cln::abs(cln::realpart(x)) > cln::cl_F("0.5")) { + + cln::cl_N result = cln::expt(cln::cl_I(-1),p) * cln::expt(cln::log(x),n) + * cln::expt(cln::log(1-x),p) / cln::factorial(n) / cln::factorial(p); + + for (int s=0; s(cln::realpart(value))); + else if (!x.imag().is_rational()) + prec = cln::float_format(cln::the(cln::imagpart(value))); + + // [Kol] (5.3) + if ((cln::realpart(value) < -0.5) || (n == 0)) { + + cln::cl_N result = cln::expt(cln::cl_I(-1),p) * cln::expt(cln::log(value),n) + * cln::expt(cln::log(1-value),p) / cln::factorial(n) / cln::factorial(p); + + for (int s=0; s 1) { + + cln::cl_N result; + + for (int s=0; s(&S_function::do_print_latex)) + +ex S_function::eval(int level) const +{ + const ex& n = seq[0]; + const ex& p = seq[1]; + const ex& x = seq[2]; + if (n.info(info_flags::posint) && p.info(info_flags::posint)) { + if (x == 0) { + return _ex0; + } + if (x == 1) { + lst m(n+1); + for (int i=ex_to(p).to_int()-1; i>0; i--) { + m.append(1); + } + return zeta(m); + } + if (p == 1) { + return Li(n+1, x); + } + if (x.info(info_flags::numeric) && (!x.info(info_flags::crational))) { + return S_num(ex_to(n).to_int(), ex_to(p).to_int(), ex_to(x)); + } + } + if (n.is_zero()) { + // [Kol] (5.3) + return pow(-log(1-x), p) / factorial(p); + } + return this->hold(); +} + +ex S_function::evalf(int level) const +{ + const ex& n = seq[0]; + const ex& p = seq[1]; + const ex& x = seq[2]; + if (n.info(info_flags::posint) && p.info(info_flags::posint)) { + if (is_a(x)) { + return S_num(ex_to(n).to_int(), ex_to(p).to_int(), ex_to(x)); + } else { + ex x_val = x.evalf(); + if (is_a(x_val)) { + return S_num(ex_to(n).to_int(), ex_to(p).to_int(), ex_to(x_val)); + } + } + } + return this->hold(); +} + +ex S_function::pderivative(unsigned deriv_param) const +{ + const ex& n = seq[0]; + const ex& p = seq[1]; + const ex& x = seq[2]; + GINAC_ASSERT(deriv_param < 3); + if (deriv_param < 2) { + return _ex0; + } + if (n > 0) { + return S(n-1, p, x) / x; + } else { + return S(n, p-1, x) / (1-x); + } +} + +ex S_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& n = seq[0]; + const ex& p = seq[1]; + const ex& x = seq[2]; + if (p == _ex1) { + return Li(n+1, x).series(rel, order, options); + } + + const ex x_pt = x.subs(rel, subs_options::no_pattern); + if (n.info(info_flags::posint) && p.info(info_flags::posint) && x_pt.info(info_flags::numeric)) { + // First special case: x==0 (derivatives have poles) + if (x_pt.is_zero()) { + const symbol s; + ex ser; + // manually construct the primitive expansion + // subsum = Euler-Zagier-Sum is needed + // dirty hack (slow ...) calculation of subsum: + std::vector presubsum, subsum; + subsum.push_back(0); + for (int i=1; i=1 (branch cut) + throw std::runtime_error("S_series: don't know how to do the series expansion at this point!"); + } + // all other cases should be safe, by now: + return basic::series(rel, order, options); +} + +void S_function::do_print_latex(const print_context& c, unsigned level) const +{ + const ex& n = seq[0]; + const ex& p = seq[1]; + const ex& x = seq[2]; + c.s << "\\mbox{S}_{"; + n.print(c); + c.s << ","; + p.print(c); + c.s << "}("; + x.print(c); + c.s << ")"; +} + +////////////////////////////////////////////////////////////////////// +// +// Harmonic polylogarithm H(m,x) +// +// helper functions +// +////////////////////////////////////////////////////////////////////// + + +// anonymous namespace for helper functions +namespace { + + +// regulates the pole (used by 1/x-transformation) +symbol H_polesign("IMSIGN"); + + +// convert parameters from H to Li representation +// parameters are expected to be in expanded form, i.e. only 0, 1 and -1 +// returns true if some parameters are negative +bool convert_parameter_H_to_Li(const lst& l, lst& m, lst& s, ex& pf) +{ + // expand parameter list + lst mexp; + for (lst::const_iterator it = l.begin(); it != l.end(); it++) { + if (*it > 1) { + for (ex count=*it-1; count > 0; count--) { + mexp.append(0); + } + mexp.append(1); + } else if (*it < -1) { + for (ex count=*it+1; count < 0; count++) { + mexp.append(0); + } + mexp.append(-1); + } else { + mexp.append(*it); + } + } + + ex signum = 1; + pf = 1; + bool has_negative_parameters = false; + ex acc = 1; + for (lst::const_iterator it = mexp.begin(); it != mexp.end(); it++) { + if (*it == 0) { + acc++; + continue; + } + if (*it > 0) { + m.append((*it+acc-1) * signum); + } else { + m.append((*it-acc+1) * signum); + } + acc = 1; + signum = *it; + pf *= *it; + if (pf < 0) { + has_negative_parameters = true; + } + } + if (has_negative_parameters) { + for (int i=0; i(e) || is_a(e)) { + return e.map(*this); + } + if (is_exactly_a(e)) { + lst parameter; + if (is_a(e.op(0))) { + parameter = ex_to(e.op(0)); + } else { + parameter = lst(e.op(0)); + } + ex arg = e.op(1); + + lst m; + lst s; + ex pf; + if (convert_parameter_H_to_Li(parameter, m, s, pf)) { + s.let_op(0) = s.op(0) * arg; + return pf * Li(m, s).hold(); + } else { + for (int i=0; i(e) || is_a(e)) { + return e.map(*this); + } + if (is_exactly_a(e)) { + lst parameter; + if (is_a(e.op(0))) { + parameter = ex_to(e.op(0)); + } else { + parameter = lst(e.op(0)); + } + + lst m; + lst s; + ex pf; + if (convert_parameter_H_to_Li(parameter, m, s, pf)) { + return pf * zeta(m, s); + } else { + return zeta(m); + } + } + return e; + } +}; + + +// remove trailing zeros from H-parameters +struct map_trafo_H_reduce_trailing_zeros : public map_function +{ + ex operator()(const ex& e) + { + if (is_a(e) || is_a(e)) { + return e.map(*this); + } + if (is_exactly_a(e)) { + lst parameter; + if (is_a(e.op(0))) { + parameter = ex_to(e.op(0)); + } else { + parameter = lst(e.op(0)); + } + ex arg = e.op(1); + if (parameter.op(parameter.nops()-1) == 0) { + + // + if (parameter.nops() == 1) { + return log(arg); + } + + // + lst::const_iterator it = parameter.begin(); + while ((it != parameter.end()) && (*it == 0)) { + it++; + } + if (it == parameter.end()) { + return pow(log(arg),parameter.nops()) / factorial(parameter.nops()); + } + + // + parameter.remove_last(); + int lastentry = parameter.nops(); + while ((lastentry > 0) && (parameter[lastentry-1] == 0)) { + lastentry--; + } + + // + ex result = log(arg) * H(parameter,arg).hold(); + ex acc = 0; + for (ex i=0; i 0) { + parameter[i]++; + result -= (acc + parameter[i]-1) * H(parameter, arg).hold(); + parameter[i]--; + acc = 0; + } else if (parameter[i] < 0) { + parameter[i]--; + result -= (acc + abs(parameter[i]+1)) * H(parameter, arg).hold(); + parameter[i]++; + acc = 0; + } else { + acc++; + } + } + + if (lastentry < parameter.nops()) { + result = result / (parameter.nops()-lastentry+1); + return result.map(*this); + } else { + return result; + } + } + } + return e; + } +}; + + +// returns an expression with zeta functions corresponding to the parameter list for H +ex convert_H_to_zeta(const lst& m) +{ + symbol xtemp("xtemp"); + map_trafo_H_reduce_trailing_zeros filter; + map_trafo_H_convert_to_zeta filter2; + return filter2(filter(H(m, xtemp).hold())).subs(xtemp == 1); +} + + +// convert signs form Li to H representation +lst convert_parameter_Li_to_H(const lst& m, const lst& x, ex& pf) +{ + lst res; + lst::const_iterator itm = m.begin(); + lst::const_iterator itx = ++x.begin(); + int signum = 1; + pf = _ex1; + res.append(*itm); + itm++; + while (itx != x.end()) { + signum *= (*itx > 0) ? 1 : -1; + pf *= signum; + res.append((*itm) * signum); + itm++; + itx++; + } + return res; +} + + +// multiplies an one-dimensional H with another H +// [ReV] (18) +ex trafo_H_mult(const ex& h1, const ex& h2) +{ + ex res; + ex hshort; + lst hlong; + ex h1nops = h1.op(0).nops(); + ex h2nops = h2.op(0).nops(); + if (h1nops > 1) { + hshort = h2.op(0).op(0); + hlong = ex_to(h1.op(0)); + } else { + hshort = h1.op(0).op(0); + if (h2nops > 1) { + hlong = ex_to(h2.op(0)); + } else { + hlong = h2.op(0).op(0); + } + } + for (int i=0; i<=hlong.nops(); i++) { + lst newparameter; + int j=0; + for (; j(e)) { + return e.map(*this); + } + + if (is_a(e)) { + + ex result = 1; + ex firstH; + lst Hlst; + for (int pos=0; pos(e.op(pos)) && is_exactly_a(e.op(pos).op(0))) { + for (ex i=0; i(e.op(pos))) { + if (e.op(pos).op(0).nops() > 1) { + firstH = e.op(pos); + } else { + Hlst.append(e.op(pos)); + } + continue; + } + result *= e.op(pos); + } + if (firstH == 0) { + if (Hlst.nops() > 0) { + firstH = Hlst[Hlst.nops()-1]; + Hlst.remove_last(); + } else { + return e; + } + } + + if (Hlst.nops() > 0) { + ex buffer = trafo_H_mult(firstH, Hlst.op(0)); + result *= buffer; + for (int i=1; i(e)) { + h = e; + } else { + for (int i=0; i(e.op(i))) { + h = e.op(i); + } + } + } + if (h != 0) { + lst newparameter = ex_to(h.op(0)); + newparameter.prepend(0); + ex addzeta = convert_H_to_zeta(newparameter); + return e.subs(h == (addzeta-H(newparameter, h.op(1)).hold())).expand(); + } else { + return e * (-H(lst(0),1/arg).hold()); + } +} + + +// do integration [ReV] (49) +// put parameter 1 in front of existing parameters +ex trafo_H_prepend_one(const ex& e, const ex& arg) +{ + ex h; + std::string name; + if (is_exactly_a(e)) { + h = e; + } else { + for (int i=0; i(e.op(i))) { + h = e.op(i); + } + } + } + if (h != 0) { + lst newparameter = ex_to(h.op(0)); + newparameter.prepend(1); + return e.subs(h == H(newparameter, h.op(1)).hold()); + } else { + return e * H(lst(1),1-arg).hold(); + } +} + + +// do integration [ReV] (55) +// put parameter -1 in front of existing parameters +ex trafo_H_1tx_prepend_minusone(const ex& e, const ex& arg) +{ + ex h; + std::string name; + if (is_exactly_a(e)) { + h = e; + } else { + for (int i=0; i(e.op(i))) { + h = e.op(i); + } + } + } + if (h != 0) { + lst newparameter = ex_to(h.op(0)); + newparameter.prepend(-1); + ex addzeta = convert_H_to_zeta(newparameter); + return e.subs(h == (addzeta-H(newparameter, h.op(1)).hold())).expand(); + } else { + ex addzeta = convert_H_to_zeta(lst(-1)); + return (e * (addzeta - H(lst(-1),1/arg).hold())).expand(); + } +} + + +// do integration [ReV] (55) +// put parameter -1 in front of existing parameters +ex trafo_H_1mxt1px_prepend_minusone(const ex& e, const ex& arg) +{ + ex h; + std::string name; + if (is_exactly_a(e)) { + h = e; + } else { + for (int i=0; i(e.op(i))) { + h = e.op(i); + } + } + } + if (h != 0) { + lst newparameter = ex_to(h.op(0)); + newparameter.prepend(-1); + return e.subs(h == H(newparameter, h.op(1)).hold()).expand(); + } else { + return (e * H(lst(-1),(1-arg)/(1+arg)).hold()).expand(); + } +} + + +// do integration [ReV] (55) +// put parameter 1 in front of existing parameters +ex trafo_H_1mxt1px_prepend_one(const ex& e, const ex& arg) +{ + ex h; + std::string name; + if (is_exactly_a(e)) { + h = e; + } else { + for (int i=0; i(e.op(i))) { + h = e.op(i); + } + } + } + if (h != 0) { + lst newparameter = ex_to(h.op(0)); + newparameter.prepend(1); + return e.subs(h == H(newparameter, h.op(1)).hold()).expand(); + } else { + return (e * H(lst(1),(1-arg)/(1+arg)).hold()).expand(); + } +} + + +// do x -> 1-x transformation +struct map_trafo_H_1mx : public map_function +{ + ex operator()(const ex& e) + { + if (is_a(e) || is_a(e)) { + return e.map(*this); + } + + if (is_exactly_a(e)) { + + lst parameter = ex_to(e.op(0)); + ex arg = e.op(1); + + // special cases if all parameters are either 0, 1 or -1 + bool allthesame = true; + if (parameter.op(0) == 0) { + for (int i=1; i0; i--) { + newparameter.append(0); + } + return pow(-1, parameter.nops()) * H(newparameter, 1-arg).hold(); + } + } else if (parameter.op(0) == -1) { + throw std::runtime_error("map_trafo_H_1mx: cannot handle weights equal -1!"); + } else { + for (int i=1; i0; i--) { + newparameter.append(1); + } + return pow(-1, parameter.nops()) * H(newparameter, 1-arg).hold(); + } + } + + lst newparameter = parameter; + newparameter.remove_first(); + + if (parameter.op(0) == 0) { + + // leading zero + ex res = convert_H_to_zeta(parameter); + //ex res = convert_from_RV(parameter, 1).subs(H(wild(1),wild(2))==zeta(wild(1))); + map_trafo_H_1mx recursion; + ex buffer = recursion(H(newparameter, arg).hold()); + if (is_a(buffer)) { + for (int i=0; i 1/x transformation +struct map_trafo_H_1overx : public map_function +{ + ex operator()(const ex& e) + { + if (is_a(e) || is_a(e)) { + return e.map(*this); + } + + if (is_exactly_a(e)) { + + lst parameter = ex_to(e.op(0)); + ex arg = e.op(1); + + // special cases if all parameters are either 0, 1 or -1 + bool allthesame = true; + if (parameter.op(0) == 0) { + for (int i=1; i(buffer)) { + for (int i=0; i(buffer)) { + for (int i=0; i (1-x)/(1+x) transformation +struct map_trafo_H_1mxt1px : public map_function +{ + ex operator()(const ex& e) + { + if (is_a(e) || is_a(e)) { + return e.map(*this); + } + + if (is_exactly_a(e)) { + + lst parameter = ex_to(e.op(0)); + ex arg = e.op(1); + + // special cases if all parameters are either 0, 1 or -1 + bool allthesame = true; + if (parameter.op(0) == 0) { + for (int i=1; i(buffer)) { + for (int i=0; i(buffer)) { + for (int i=0; i& m, const cln::cl_N& x) +{ + const int j = m.size(); + + std::vector t(j); + + cln::cl_F one = cln::cl_float(1, cln::float_format(Digits)); + cln::cl_N factor = cln::expt(x, j) * one; + cln::cl_N t0buf; + int q = 0; + do { + t0buf = t[0]; + q++; + t[j-1] = t[j-1] + 1 / cln::expt(cln::cl_I(q),m[j-1]); + for (int k=j-2; k>=1; k--) { + t[k] = t[k] + t[k+1] / cln::expt(cln::cl_I(q+j-1-k), m[k]); + } + t[0] = t[0] + t[1] * factor / cln::expt(cln::cl_I(q+j-1), m[0]); + factor = factor * x; + } while (t[0] != t0buf); + + return t[0]; +} + + +} // end of anonymous namespace + + +////////////////////////////////////////////////////////////////////// +// +// Harmonic polylogarithm H(m,x) +// +// GiNaC function +// +////////////////////////////////////////////////////////////////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(H_function, + print_func(&H_function::do_print_latex)) + +ex H_function::eval(int level) const +{ + if (!do_eval) return this->hold(); + + const ex& m_ = seq[0]; + const ex& x = seq[1]; + lst m; + if (is_a(m_)) { + m = ex_to(m_); + } else { + m = lst(m_); + } + if (m.nops() == 0) { + return _ex1; + } + ex pos1; + ex pos2; + ex n; + ex p; + int step = 0; + if (*m.begin() > _ex1) { + step++; + pos1 = _ex0; + pos2 = _ex1; + n = *m.begin()-1; + p = _ex1; + } else if (*m.begin() < _ex_1) { + step++; + pos1 = _ex0; + pos2 = _ex_1; + n = -*m.begin()-1; + p = _ex1; + } else if (*m.begin() == _ex0) { + pos1 = _ex0; + n = _ex1; + } else { + pos1 = *m.begin(); + p = _ex1; + } + for (lst::const_iterator it = ++m.begin(); it != m.end(); it++) { + if ((*it).info(info_flags::integer)) { + if (step == 0) { + if (*it > _ex1) { + if (pos1 == _ex0) { + step = 1; + pos2 = _ex1; + n += *it-1; + p = _ex1; + } else { + step = 2; + } + } else if (*it < _ex_1) { + if (pos1 == _ex0) { + step = 1; + pos2 = _ex_1; + n += -*it-1; + p = _ex1; + } else { + step = 2; + } + } else { + if (*it != pos1) { + step = 1; + pos2 = *it; + } + if (*it == _ex0) { + n++; + } else { + p++; + } + } + } else if (step == 1) { + if (*it != pos2) { + step = 2; + } else { + if (*it == _ex0) { + n++; + } else { + p++; + } + } + } + } else { + // if some m_i is not an integer + return this->hold(); + } + } + if ((x == _ex1) && (*(--m.end()) != _ex0)) { + return convert_H_to_zeta(m); + } + if (step == 0) { + if (pos1 == _ex0) { + // all zero + if (x == _ex0) { + return this->hold(); + } + return pow(log(x), m.nops()) / factorial(m.nops()); + } else { + // all (minus) one + return pow(-pos1*log(1-pos1*x), m.nops()) / factorial(m.nops()); + } + } else if ((step == 1) && (pos1 == _ex0)){ + // convertible to S + if (pos2 == _ex1) { + return S(n, p, x); + } else { + return pow(-1, p) * S(n, p, -x); + } + } + if (x == _ex0) { + return _ex0; + } + if (x.info(info_flags::numeric) && (!x.info(info_flags::crational))) { + return evalf(level); + } + return this->hold(); +} + +ex H_function::evalf(int level) const +{ + const ex& x1 = seq[0]; + const ex& x2 = seq[1]; + if (is_a(x1)) { + + cln::cl_N x; + if (is_a(x2)) { + x = ex_to(x2).to_cl_N(); + } else { + ex x2_val = x2.evalf(); + if (is_a(x2_val)) { + x = ex_to(x2_val).to_cl_N(); + } + } + + for (int i=0; ihold(); + } + } + if (x1.nops() < 1) { + return this->hold(); + } + + const lst& morg = ex_to(x1); + // remove trailing zeros ... + if (*(--morg.end()) == 0) { + symbol xtemp("xtemp"); + map_trafo_H_reduce_trailing_zeros filter; + return filter(H(x1, xtemp).hold()).subs(xtemp==x2).evalf(); + } + // ... and expand parameter notation + bool has_minus_one = false; + lst m; + for (lst::const_iterator it = morg.begin(); it != morg.end(); it++) { + if (*it > 1) { + for (ex count=*it-1; count > 0; count--) { + m.append(0); + } + m.append(1); + } else if (*it <= -1) { + for (ex count=*it+1; count < 0; count++) { + m.append(0); + } + m.append(-1); + has_minus_one = true; + } else { + m.append(*it); + } + } + + // do summation + if (cln::abs(x) < 0.95) { + + lst m_lst; + lst s_lst; + ex pf; + if (convert_parameter_H_to_Li(m, m_lst, s_lst, pf)) { + // negative parameters -> s_lst is filled + std::vector m_int; + std::vector x_cln; + for (lst::const_iterator it_int = m_lst.begin(), it_cln = s_lst.begin(); + it_int != m_lst.end(); it_int++, it_cln++) { + m_int.push_back(ex_to(*it_int).to_int()); + x_cln.push_back(ex_to(*it_cln).to_cl_N()); + } + x_cln.front() = x_cln.front() * x; + return pf * numeric(multipleLi_do_sum(m_int, x_cln)); + } else { + // only positive parameters + //TODO + if (m_lst.nops() == 1) { + return Li(m_lst.op(0), x2).evalf(); + } + std::vector m_int; + for (lst::const_iterator it = m_lst.begin(); it != m_lst.end(); it++) { + m_int.push_back(ex_to(*it).to_int()); + } + return numeric(H_do_sum(m_int, x)); + } + } + + symbol xtemp("xtemp"); + ex res = 1; + + // ensure that the realpart of the argument is positive + if (cln::realpart(x) < 0) { + x = -x; + for (int i=0; i 1/x + if (cln::abs(x) >= 2.0) { + map_trafo_H_1overx trafo; + res *= trafo(H(m, xtemp)); + if (cln::imagpart(x) <= 0) { + res = res.subs(H_polesign == -I*Pi); + } else { + res = res.subs(H_polesign == I*Pi); + } + return res.subs(xtemp == numeric(x)).evalf(); + } + + // check transformations for 0.95 <= |x| < 2.0 + + // |(1-x)/(1+x)| < 0.9 -> circular area with center=9,53+0i and radius=9.47 + if (cln::abs(x-9.53) <= 9.47) { + // x -> (1-x)/(1+x) + map_trafo_H_1mxt1px trafo; + res *= trafo(H(m, xtemp)); + } else { + // x -> 1-x + if (has_minus_one) { + map_trafo_H_convert_to_Li filter; + return filter(H(m, numeric(x)).hold()).evalf(); + } + map_trafo_H_1mx trafo; + res *= trafo(H(m, xtemp)); + } + + return res.subs(xtemp == numeric(x)).evalf(); + } + + return this->hold(); +} + +ex H_function::pderivative(unsigned deriv_param) const +{ + const ex& m_ = seq[0]; + const ex& x = seq[1]; + GINAC_ASSERT(deriv_param < 2); + if (deriv_param == 0) { + return _ex0; + } + lst m; + if (is_a(m_)) { + m = ex_to(m_); + } else { + m = lst(m_); + } + ex mb = *m.begin(); + if (mb > _ex1) { + m[0]--; + return H(m, x) / x; + } + if (mb < _ex_1) { + m[0]++; + return H(m, x) / x; + } + m.remove_first(); + if (mb == _ex1) { + return 1/(1-x) * H(m, x); + } else if (mb == _ex_1) { + return 1/(1+x) * H(m, x); + } else { + return H(m, x) / x; + } +} + +bool H_function::do_eval = true; + +ex H_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& m = seq[0]; + const ex& x = seq[1]; + epvector seq; + seq.push_back(expair(H(m, x), 0)); + return pseries(rel, seq); +} + +void H_function::do_print_latex(const print_context& c, unsigned level) const +{ + const ex& m_ = seq[0]; + const ex& x = seq[1]; + lst m; + if (is_a(m_)) { + m = ex_to(m_); + } else { + m = lst(m_); + } + c.s << "\\mbox{H}_{"; + lst::const_iterator itm = m.begin(); + (*itm).print(c); + itm++; + for (; itm != m.end(); itm++) { + c.s << ","; + (*itm).print(c); + } + c.s << "}("; + x.print(c); + c.s << ")"; +} + +// takes a parameter list for H and returns an expression with corresponding multiple polylogarithms +ex convert_H_to_Li(const ex& m, const ex& x) +{ + map_trafo_H_reduce_trailing_zeros filter; + map_trafo_H_convert_to_Li filter2; + if (is_a(m)) { + return filter2(filter(H(m, x).hold())); + } else { + return filter2(filter(H(lst(m), x).hold())); + } +} + +////////////////////////////////////////////////////////////////////// +// +// Multiple zeta values zeta(x) and zeta(x,s) +// +// helper functions +// +////////////////////////////////////////////////////////////////////// + + +// anonymous namespace for helper functions +namespace { + + +// parameters and data for [Cra] algorithm +const cln::cl_N lambda = cln::cl_N("319/320"); +int L1; +int L2; +std::vector > f_kj; +std::vector crB; +std::vector > crG; +std::vector crX; + + +void halfcyclic_convolute(const std::vector& a, const std::vector& b, std::vector& c) +{ + const int size = a.size(); + for (int n=0; n& s) +{ + const int k = s.size(); + + crX.clear(); + crG.clear(); + crB.clear(); + + for (int i=0; i<=L2; i++) { + crB.push_back(bernoulli(i).to_cl_N() / cln::factorial(i)); + } + + int Sm = 0; + int Smp1 = 0; + for (int m=0; m crGbuf; + Sm = Sm + s[m]; + Smp1 = Sm + s[m+1]; + for (int i=0; i<=L2; i++) { + crGbuf.push_back(cln::factorial(i + Sm - m - 2) / cln::factorial(i + Smp1 - m - 2)); + } + crG.push_back(crGbuf); + } + + crX = crB; + + for (int m=0; m Xbuf; + for (int i=0; i<=L2; i++) { + Xbuf.push_back(crX[i] * crG[m][i]); + } + halfcyclic_convolute(Xbuf, crB, crX); + } +} + + +// [Cra] section 4 +cln::cl_N crandall_Y_loop(const cln::cl_N& Sqk) +{ + cln::cl_F one = cln::cl_float(1, cln::float_format(Digits)); + cln::cl_N factor = cln::expt(lambda, Sqk); + cln::cl_N res = factor / Sqk * crX[0] * one; + cln::cl_N resbuf; + int N = 0; + do { + resbuf = res; + factor = factor * lambda; + N++; + res = res + crX[N] * factor / (N+Sqk); + } while ((res != resbuf) || cln::zerop(crX[N])); + return res; +} + + +// [Cra] section 4 +void calc_f(int maxr) +{ + f_kj.clear(); + f_kj.resize(L1); + + cln::cl_N t0, t1, t2, t3, t4; + int i, j, k; + std::vector >::iterator it = f_kj.begin(); + cln::cl_F one = cln::cl_float(1, cln::float_format(Digits)); + + t0 = cln::exp(-lambda); + t2 = 1; + for (k=1; k<=L1; k++) { + t1 = k * lambda; + t2 = t0 * t2; + for (j=1; j<=maxr; j++) { + t3 = 1; + t4 = 1; + for (i=2; i<=j; i++) { + t4 = t4 * (j-i+1); + t3 = t1 * t3 + t4; + } + (*it).push_back(t2 * t3 * cln::expt(cln::cl_I(k),-j) * one); + } + it++; + } +} + + +// [Cra] (3.1) +cln::cl_N crandall_Z(const std::vector& s) +{ + const int j = s.size(); + + if (j == 1) { + cln::cl_N t0; + cln::cl_N t0buf; + int q = 0; + do { + t0buf = t0; + q++; + t0 = t0 + f_kj[q+j-2][s[0]-1]; + } while (t0 != t0buf); + + return t0 / cln::factorial(s[0]-1); + } + + std::vector t(j); + + cln::cl_N t0buf; + int q = 0; + do { + t0buf = t[0]; + q++; + t[j-1] = t[j-1] + 1 / cln::expt(cln::cl_I(q),s[j-1]); + for (int k=j-2; k>=1; k--) { + t[k] = t[k] + t[k+1] / cln::expt(cln::cl_I(q+j-1-k), s[k]); + } + t[0] = t[0] + t[1] * f_kj[q+j-2][s[0]-1]; + } while (t[0] != t0buf); + + return t[0] / cln::factorial(s[0]-1); +} + + +// [Cra] (2.4) +cln::cl_N zeta_do_sum_Crandall(const std::vector& s) +{ + std::vector r = s; + const int j = r.size(); + + // decide on maximal size of f_kj for crandall_Z + if (Digits < 50) { + L1 = 150; + } else { + L1 = Digits * 3 + j*2; + } + + // decide on maximal size of crX for crandall_Y + if (Digits < 38) { + L2 = 63; + } else if (Digits < 86) { + L2 = 127; + } else if (Digits < 192) { + L2 = 255; + } else if (Digits < 394) { + L2 = 511; + } else if (Digits < 808) { + L2 = 1023; + } else { + L2 = 2047; + } + + cln::cl_N res; + + int maxr = 0; + int S = 0; + for (int i=0; i maxr) { + maxr = r[i]; + } + } + + calc_f(maxr); + + const cln::cl_N r0factorial = cln::factorial(r[0]-1); + + std::vector rz; + int skp1buf; + int Srun = S; + for (int k=r.size()-1; k>0; k--) { + + rz.insert(rz.begin(), r.back()); + skp1buf = rz.front(); + Srun -= skp1buf; + r.pop_back(); + + initcX(r); + + for (int q=0; q& r) +{ + const int j = r.size(); + + // buffer for subsums + std::vector t(j); + cln::cl_F one = cln::cl_float(1, cln::float_format(Digits)); + + cln::cl_N t0buf; + int q = 0; + do { + t0buf = t[0]; + q++; + t[j-1] = t[j-1] + one / cln::expt(cln::cl_I(q),r[j-1]); + for (int k=j-2; k>=0; k--) { + t[k] = t[k] + one * t[k+1] / cln::expt(cln::cl_I(q+j-1-k), r[k]); + } + } while (t[0] != t0buf); + + return t[0]; +} + + +// does Hoelder convolution. see [BBB] (7.0) +cln::cl_N zeta_do_Hoelder_convolution(const std::vector& m_, const std::vector& s_) +{ + // prepare parameters + // holds Li arguments in [BBB] notation + std::vector s = s_; + std::vector m_p = m_; + std::vector m_q; + // holds Li arguments in nested sums notation + std::vector s_p(s.size(), cln::cl_N(1)); + s_p[0] = s_p[0] * cln::cl_N("1/2"); + // convert notations + int sig = 1; + for (int i=0; i s_q; + cln::cl_N signum = 1; + + // first term + cln::cl_N res = multipleLi_do_sum(m_p, s_p); + + // middle terms + do { + + // change parameters + if (s.front() > 0) { + if (m_p.front() == 1) { + m_p.erase(m_p.begin()); + s_p.erase(s_p.begin()); + if (s_p.size() > 0) { + s_p.front() = s_p.front() * cln::cl_N("1/2"); + } + s.erase(s.begin()); + m_q.front()++; + } else { + m_p.front()--; + m_q.insert(m_q.begin(), 1); + if (s_q.size() > 0) { + s_q.front() = s_q.front() * 2; + } + s_q.insert(s_q.begin(), cln::cl_N("1/2")); + } + } else { + if (m_p.front() == 1) { + m_p.erase(m_p.begin()); + cln::cl_N spbuf = s_p.front(); + s_p.erase(s_p.begin()); + if (s_p.size() > 0) { + s_p.front() = s_p.front() * spbuf; + } + s.erase(s.begin()); + m_q.insert(m_q.begin(), 1); + if (s_q.size() > 0) { + s_q.front() = s_q.front() * 4; + } + s_q.insert(s_q.begin(), cln::cl_N("1/4")); + signum = -signum; + } else { + m_p.front()--; + m_q.insert(m_q.begin(), 1); + if (s_q.size() > 0) { + s_q.front() = s_q.front() * 2; + } + s_q.insert(s_q.begin(), cln::cl_N("1/2")); + } + } + + // exiting the loop + if (m_p.size() == 0) break; + + res = res + signum * multipleLi_do_sum(m_p, s_p) * multipleLi_do_sum(m_q, s_q); + + } while (true); + + // last term + res = res + signum * multipleLi_do_sum(m_q, s_q); + + return res; +} + + +} // end of anonymous namespace + + +////////////////////////////////////////////////////////////////////// +// +// Multiple zeta values zeta(x) +// Alternating Euler sum zeta(x,s) +// +// GiNaC function +// +////////////////////////////////////////////////////////////////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(zeta_function, + print_func(&zeta_function::do_print_latex)) + +ex zeta_function::eval(int level) const +{ + if (seq.size() == 1) { + // zeta1 + const ex& m = seq[0]; + if (is_exactly_a(m)) { + if (m.nops() == 1) { + return zeta(m.op(0)); + } + return this->hold(); + } + + if (m.info(info_flags::numeric)) { + const numeric& y = ex_to(m); + // trap integer arguments: + if (y.is_integer()) { + if (y.is_zero()) { + return _ex_1_2; + } + if (y.is_equal(*_num1_p)) { + return this->hold(); + } + if (y.info(info_flags::posint)) { + if (y.info(info_flags::odd)) { + return this->hold(); + } else { + return abs(bernoulli(y)) * pow(Pi, y) * pow(*_num2_p, y-(*_num1_p)) / factorial(y); + } + } else { + if (y.info(info_flags::odd)) { + return -bernoulli((*_num1_p)-y) / ((*_num1_p)-y); + } else { + return _ex0; + } + } + } + // zeta(float) + if (y.info(info_flags::numeric) && !y.info(info_flags::crational)) { + return zeta(m).evalf(); + } + } + return zeta(m).hold(); + } else { + // zeta 2 + const ex& m = seq[0]; + const ex& s_ = seq[1]; + if (is_exactly_a(s_)) { + const lst& s = ex_to(s_); + for (lst::const_iterator it = s.begin(); it != s.end(); it++) { + if ((*it).info(info_flags::positive)) { + continue; + } + return this->hold(); + } + return zeta(m); + } else if (s_.info(info_flags::positive)) { + return zeta(m); + } + + return this->hold(); + } +} + +ex zeta_function::evalf(int level) const +{ + if (seq.size() == 1) { + // zeta1 + const ex& x = seq[0]; + if (is_exactly_a(x) && (x.nops()>1)) { + + // multiple zeta value + const int count = x.nops(); + const lst& xlst = ex_to(x); + std::vector r(count); + + // check parameters and convert them + lst::const_iterator it1 = xlst.begin(); + std::vector::iterator it2 = r.begin(); + do { + if (!(*it1).info(info_flags::posint)) { + return this->hold(); + } + *it2 = ex_to(*it1).to_int(); + it1++; + it2++; + } while (it2 != r.end()); + + // check for divergence + if (r[0] == 1) { + return this->hold(); + } + + // decide on summation algorithm + // this is still a bit clumsy + int limit = (Digits>17) ? 10 : 6; + if ((r[0] < limit) || ((count > 3) && (r[1] < limit/2))) { + return numeric(zeta_do_sum_Crandall(r)); + } else { + return numeric(zeta_do_sum_simple(r)); + } + } + + // single zeta value + if (is_exactly_a(x) && (x != 1)) { + try { + return zeta(ex_to(x)); + } catch (const dunno &e) { } + } + + return zeta(x).hold(); + } else { + // zeta 2 + const ex& x = seq[0]; + const ex& s = seq[1]; + if (is_exactly_a(x)) { + + // alternating Euler sum + const int count = x.nops(); + const lst& xlst = ex_to(x); + const lst& slst = ex_to(s); + std::vector xi(count); + std::vector si(count); + + // check parameters and convert them + lst::const_iterator it_xread = xlst.begin(); + lst::const_iterator it_sread = slst.begin(); + std::vector::iterator it_xwrite = xi.begin(); + std::vector::iterator it_swrite = si.begin(); + do { + if (!(*it_xread).info(info_flags::posint)) { + return this->hold(); + } + *it_xwrite = ex_to(*it_xread).to_int(); + if (*it_sread > 0) { + *it_swrite = 1; + } else { + *it_swrite = -1; + } + it_xread++; + it_sread++; + it_xwrite++; + it_swrite++; + } while (it_xwrite != xi.end()); + + // check for divergence + if ((xi[0] == 1) && (si[0] == 1)) { + return this->hold(); + } + + // use Hoelder convolution + return numeric(zeta_do_Hoelder_convolution(xi, si)); + } + + return this->hold(); + } +} + +ex zeta_function::pderivative(unsigned deriv_param) const +{ + GINAC_ASSERT(deriv_param==0); + + if (seq.size() == 1) { + // zeta 1 + const ex& m = seq[0]; + if (is_exactly_a(m)) { + return _ex0; + } else { + return zetaderiv(_ex1, m); + } + } else { + // zeta 2 + const ex& m = seq[0]; + const ex& s = seq[1]; + if (is_exactly_a(m)) { + return _ex0; + } else { + if ((is_exactly_a(s) && s.op(0).info(info_flags::positive)) || s.info(info_flags::positive)) { + return zetaderiv(_ex1, m); + } + return _ex0; + } + } +} + +/** Numeric evaluation of Riemann's Zeta function. Currently works only for + * integer arguments. */ +numeric zeta_function::calc(const numeric& x) +{ + // A dirty hack to allow for things like zeta(3.0), since CLN currently + // only knows about integer arguments and zeta(3).evalf() automatically + // cascades down to zeta(3.0).evalf(). The trick is to rely on 3.0-3 + // being an exact zero for CLN, which can be tested and then we can just + // pass the number casted to an int: + if (x.is_real()) { + const int aux = (int)(cln::double_approx(cln::the(x.to_cl_N()))); + if (cln::zerop(x.to_cl_N()-aux)) + return cln::zeta(aux); + } + throw dunno(); +} + +void zeta_function::do_print_latex(const print_context& c, unsigned level) const +{ + if (seq.size() == 1) { + // zeta 1 + const ex& m_ = seq[0]; + c.s << "\\zeta("; + if (is_a(m_)) { + const lst& m = ex_to(m_); + lst::const_iterator it = m.begin(); + (*it).print(c); + it++; + for (; it != m.end(); it++) { + c.s << ","; + (*it).print(c); + } + } else { + m_.print(c); + } + c.s << ")"; + } else { + // zeta 2 + const ex& m_ = seq[0]; + const ex& s_ = seq[1]; + lst m; + if (is_a(m_)) { + m = ex_to(m_); + } else { + m = lst(m_); + } + lst s; + if (is_a(s_)) { + s = ex_to(s_); + } else { + s = lst(s_); + } + c.s << "\\zeta("; + lst::const_iterator itm = m.begin(); + lst::const_iterator its = s.begin(); + if (*its < 0) { + c.s << "\\overline{"; + (*itm).print(c); + c.s << "}"; + } else { + (*itm).print(c); + } + its++; + itm++; + for (; itm != m.end(); itm++, its++) { + c.s << ","; + if (*its < 0) { + c.s << "\\overline{"; + (*itm).print(c); + c.s << "}"; + } else { + (*itm).print(c); + } + } + c.s << ")"; + } +} + +////////// +// Derivatives of Riemann's Zeta-function zetaderiv(0,x)==zeta(x) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(zetaderiv_function, + print_func(&zetaderiv_function::do_print_latex)) + +ex zetaderiv_function::eval(int level) const +{ + const ex& n = seq[0]; + const ex& x = seq[1]; + if (n.info(info_flags::numeric)) { + // zetaderiv(0,x) -> zeta(x) + if (n.is_zero()) + return zeta(x); + } + + return this->hold(); +} + +ex zetaderiv_function::pderivative(unsigned deriv_param) const +{ + GINAC_ASSERT(deriv_param<2); + + const ex& n = seq[0]; + const ex& x = seq[1]; + if (deriv_param==0) { + // d/dn zeta(n,x) + throw(std::logic_error("cannot diff zetaderiv(n,x) with respect to n")); + } + // d/dx psi(n,x) + return zetaderiv(n+1,x); +} + +void zetaderiv_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\zeta^\\prime"; + inherited::do_print(c,level); +} + +} // namespace GiNaC diff --git a/ginac/inifcns_polylog.h b/ginac/inifcns_polylog.h new file mode 100644 index 00000000..2569a7a1 --- /dev/null +++ b/ginac/inifcns_polylog.h @@ -0,0 +1,148 @@ +/** @file inifcns_polylog.h + * + * Interface to GiNaC's TODO */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __GINAC_INIFCNS_POLYLOG_H__ +#define __GINAC_INIFCNS_POLYLOG_H__ + +#include "numeric.h" +#include "function.h" +#include "ex.h" +#include "inifcns.h" + +namespace GiNaC { + +/** Generalized multiple polylogarithm. */ +class G_function : public function +{ + GINAC_DECLARE_FUNCTION(G_function) +public: + /** Generalized multiple polylogarithm. */ + G_function(const ex& x) : inherited(&G_function::tinfo_static, x) { } + /** Generalized multiple polylogarithm with explicit imaginary parts. */ + G_function(const ex& x, const ex& y) : inherited(&G_function::tinfo_static, x, y) { } + /** Generalized multiple polylogarithm with explicit imaginary parts. */ + G_function(const ex& x, const ex& s, const ex& y) : inherited(&G_function::tinfo_static, x, s, y) { } +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; +}; + +template inline G_function G(const T1& x1) { return G_function(x1); } +template inline G_function G(const T1& x1, const T2& x2) { return G_function(x1, x2); } +template inline G_function G(const T1& x1, const T2& x2, const T3& x3) { return G_function(x1, x2, x3); } + +/** Polylogarithm and multiple polylogarithm. */ +class Li_function : public function +{ + GINAC_DECLARE_FUNCTION_2P(Li_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline Li_function Li(const T1& x1, const T2& x2) { return Li_function(x1, x2); } + +/** Nielsen's generalized polylogarithm. */ +class S_function : public function +{ + GINAC_DECLARE_FUNCTION_3P(S_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline S_function S(const T1& x1, const T2& x2, const T3& x3) { return S_function(x1, x2, x3); } + +/** Harmonic polylogarithm. */ +class H_function : public function +{ + GINAC_DECLARE_FUNCTION_2P(H_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; + virtual ex series(const relational& r, int order, unsigned options = 0) const; + static bool do_eval; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline H_function H(const T1& x1, const T2& x2) { return H_function(x1, x2); } + +/** Multiple zeta value including Riemann's zeta-function. */ +class zeta_function : public function +{ + GINAC_DECLARE_FUNCTION(zeta_function) +public: + /** Multiple zeta value including Riemann's zeta-function. */ + zeta_function(const ex& x) : inherited(&zeta_function::tinfo_static, x) { } + /** Alternating Euler sum or colored MZV. */ + zeta_function(const ex& x, const ex& s) : inherited(&zeta_function::tinfo_static, x, s) { } +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; +public: + static numeric calc(const numeric& x); +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline zeta_function zeta(const T1& x1) { return zeta_function(x1); } +//inline zeta_function zeta(const ex& x1) { return zeta_function(x1); } +template inline zeta_function zeta(const T1& x1, const T2& x2) { return zeta_function(x1, x2); } + +/** Converts a given list containing parameters for H in Remiddi/Vermaseren notation into + * the corresponding GiNaC functions. + */ +ex convert_H_to_Li(const ex& parameterlst, const ex& arg); + +/** Dilogarithm. */ +template inline Li_function Li2(const T1& x1) { return Li_function(2, x1); } + +/** Trilogarithm. */ +template inline Li_function Li3(const T1& x1) { return Li_function(3, x1); } + +/** Derivatives of Riemann's Zeta-function. */ +class zetaderiv_function : public function +{ + GINAC_DECLARE_FUNCTION_2P(zetaderiv_function) +public: + virtual ex eval(int level = 0) const; + virtual ex pderivative(unsigned deriv_param) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline zetaderiv_function zetaderiv(const T1& x1, const T2& x2) { return zetaderiv_function(x1, x2); } + +} // namespace GiNaC + +#endif // ndef __GINAC_INIFCNS_H__ diff --git a/ginac/inifcns_trans.cpp b/ginac/inifcns_trans.cpp deleted file mode 100644 index 70eccdce..00000000 --- a/ginac/inifcns_trans.cpp +++ /dev/null @@ -1,743 +0,0 @@ -/** @file inifcns_trans.cpp - * - * Implementation of transcendental (and trigonometric and hyperbolic) - * functions. */ - -/* - * GiNaC Copyright (C) 1999 Johannes Gutenberg University Mainz, Germany - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include -#include - -#include "inifcns.h" -#include "ex.h" -#include "constant.h" -#include "numeric.h" -#include "power.h" - -namespace GiNaC { - -////////// -// exponential function -////////// - -static ex exp_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(exp(x)) - - return exp(ex_to_numeric(x)); // -> numeric exp(numeric) -} - -static ex exp_eval(ex const & x) -{ - // exp(0) -> 1 - if (x.is_zero()) { - return exONE(); - } - // exp(n*Pi*I/2) -> {+1|+I|-1|-I} - ex TwoExOverPiI=(2*x)/(Pi*I); - if (TwoExOverPiI.info(info_flags::integer)) { - numeric z=mod(ex_to_numeric(TwoExOverPiI),numeric(4)); - if (z.is_equal(numZERO())) - return exONE(); - if (z.is_equal(numONE())) - return ex(I); - if (z.is_equal(numTWO())) - return exMINUSONE(); - if (z.is_equal(numTHREE())) - return ex(-I); - } - // exp(log(x)) -> x - if (is_ex_the_function(x, log)) - return x.op(0); - - // exp(float) - if (x.info(info_flags::numeric) && !x.info(info_flags::rational)) - return exp_evalf(x); - - return exp(x).hold(); -} - -static ex exp_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return exp(x); -} - -REGISTER_FUNCTION(exp, exp_eval, exp_evalf, exp_diff, NULL); - -////////// -// natural logarithm -////////// - -static ex log_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(log(x)) - - return log(ex_to_numeric(x)); // -> numeric log(numeric) -} - -static ex log_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // log(1) -> 0 - if (x.is_equal(exONE())) - return exZERO(); - // log(-1) -> I*Pi - if (x.is_equal(exMINUSONE())) - return (I*Pi); - // log(I) -> Pi*I/2 - if (x.is_equal(I)) - return (I*Pi*numeric(1,2)); - // log(-I) -> -Pi*I/2 - if (x.is_equal(-I)) - return (I*Pi*numeric(-1,2)); - // log(0) -> throw singularity - if (x.is_equal(exZERO())) - throw(std::domain_error("log_eval(): log(0)")); - // log(float) - if (!x.info(info_flags::rational)) - return log_evalf(x); - } - - return log(x).hold(); -} - -static ex log_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return power(x, -1); -} - -REGISTER_FUNCTION(log, log_eval, log_evalf, log_diff, NULL); - -////////// -// sine (trigonometric function) -////////// - -static ex sin_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(sin(x)) - - return sin(ex_to_numeric(x)); // -> numeric sin(numeric) -} - -static ex sin_eval(ex const & x) -{ - // sin(n*Pi) -> 0 - ex xOverPi=x/Pi; - if (xOverPi.info(info_flags::integer)) - return exZERO(); - - // sin((2n+1)*Pi/2) -> {+|-}1 - ex xOverPiMinusHalf=xOverPi-exHALF(); - if (xOverPiMinusHalf.info(info_flags::even)) - return exONE(); - else if (xOverPiMinusHalf.info(info_flags::odd)) - return exMINUSONE(); - - if (is_ex_exactly_of_type(x, function)) { - ex t=x.op(0); - // sin(asin(x)) -> x - if (is_ex_the_function(x, asin)) - return t; - // sin(acos(x)) -> (1-x^2)^(1/2) - if (is_ex_the_function(x, acos)) - return power(exONE()-power(t,exTWO()),exHALF()); - // sin(atan(x)) -> x*(1+x^2)^(-1/2) - if (is_ex_the_function(x, atan)) - return t*power(exONE()+power(t,exTWO()),exMINUSHALF()); - } - - // sin(float) -> float - if (x.info(info_flags::numeric) && !x.info(info_flags::rational)) - return sin_evalf(x); - - return sin(x).hold(); -} - -static ex sin_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return cos(x); -} - -REGISTER_FUNCTION(sin, sin_eval, sin_evalf, sin_diff, NULL); - -////////// -// cosine (trigonometric function) -////////// - -static ex cos_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(cos(x)) - - return cos(ex_to_numeric(x)); // -> numeric cos(numeric) -} - -static ex cos_eval(ex const & x) -{ - // cos(n*Pi) -> {+|-}1 - ex xOverPi=x/Pi; - if (xOverPi.info(info_flags::even)) - return exONE(); - else if (xOverPi.info(info_flags::odd)) - return exMINUSONE(); - - // cos((2n+1)*Pi/2) -> 0 - ex xOverPiMinusHalf=xOverPi-exHALF(); - if (xOverPiMinusHalf.info(info_flags::integer)) - return exZERO(); - - if (is_ex_exactly_of_type(x, function)) { - ex t=x.op(0); - // cos(acos(x)) -> x - if (is_ex_the_function(x, acos)) - return t; - // cos(asin(x)) -> (1-x^2)^(1/2) - if (is_ex_the_function(x, asin)) - return power(exONE()-power(t,exTWO()),exHALF()); - // cos(atan(x)) -> (1+x^2)^(-1/2) - if (is_ex_the_function(x, atan)) - return power(exONE()+power(t,exTWO()),exMINUSHALF()); - } - - // cos(float) -> float - if (x.info(info_flags::numeric) && !x.info(info_flags::rational)) - return cos_evalf(x); - - return cos(x).hold(); -} - -static ex cos_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return numMINUSONE()*sin(x); -} - -REGISTER_FUNCTION(cos, cos_eval, cos_evalf, cos_diff, NULL); - -////////// -// tangent (trigonometric function) -////////// - -static ex tan_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(tan(x)) // -> numeric tan(numeric) - - return tan(ex_to_numeric(x)); -} - -static ex tan_eval(ex const & x) -{ - // tan(n*Pi/3) -> {0|3^(1/2)|-(3^(1/2))} - ex ThreeExOverPi=numTHREE()*x/Pi; - if (ThreeExOverPi.info(info_flags::integer)) { - numeric z=mod(ex_to_numeric(ThreeExOverPi),numeric(3)); - if (z.is_equal(numZERO())) - return exZERO(); - if (z.is_equal(numONE())) - return power(exTHREE(),exHALF()); - if (z.is_equal(numTWO())) - return -power(exTHREE(),exHALF()); - } - - // tan((2n+1)*Pi/2) -> throw - ex ExOverPiMinusHalf=x/Pi-exHALF(); - if (ExOverPiMinusHalf.info(info_flags::integer)) - throw (std::domain_error("tan_eval(): infinity")); - - if (is_ex_exactly_of_type(x, function)) { - ex t=x.op(0); - // tan(atan(x)) -> x - if (is_ex_the_function(x, atan)) - return t; - // tan(asin(x)) -> x*(1+x^2)^(-1/2) - if (is_ex_the_function(x, asin)) - return t*power(exONE()-power(t,exTWO()),exMINUSHALF()); - // tan(acos(x)) -> (1-x^2)^(1/2)/x - if (is_ex_the_function(x, acos)) - return power(t,exMINUSONE())*power(exONE()-power(t,exTWO()),exHALF()); - } - - // tan(float) -> float - if (x.info(info_flags::numeric) && !x.info(info_flags::rational)) { - return tan_evalf(x); - } - - return tan(x).hold(); -} - -static ex tan_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return (1+power(tan(x),exTWO())); -} - -REGISTER_FUNCTION(tan, tan_eval, tan_evalf, tan_diff, NULL); - -////////// -// inverse sine (arc sine) -////////// - -static ex asin_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(asin(x)) - - return asin(ex_to_numeric(x)); // -> numeric asin(numeric) -} - -static ex asin_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // asin(0) -> 0 - if (x.is_zero()) - return x; - // asin(1/2) -> Pi/6 - if (x.is_equal(exHALF())) - return numeric(1,6)*Pi; - // asin(1) -> Pi/2 - if (x.is_equal(exONE())) - return numeric(1,2)*Pi; - // asin(-1/2) -> -Pi/6 - if (x.is_equal(exMINUSHALF())) - return numeric(-1,6)*Pi; - // asin(-1) -> -Pi/2 - if (x.is_equal(exMINUSONE())) - return numeric(-1,2)*Pi; - // asin(float) -> float - if (!x.info(info_flags::rational)) - return asin_evalf(x); - } - - return asin(x).hold(); -} - -static ex asin_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return power(1-power(x,exTWO()),exMINUSHALF()); -} - -REGISTER_FUNCTION(asin, asin_eval, asin_evalf, asin_diff, NULL); - -////////// -// inverse cosine (arc cosine) -////////// - -static ex acos_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(acos(x)) - - return acos(ex_to_numeric(x)); // -> numeric acos(numeric) -} - -static ex acos_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // acos(1) -> 0 - if (x.is_equal(exONE())) - return exZERO(); - // acos(1/2) -> Pi/3 - if (x.is_equal(exHALF())) - return numeric(1,3)*Pi; - // acos(0) -> Pi/2 - if (x.is_zero()) - return numeric(1,2)*Pi; - // acos(-1/2) -> 2/3*Pi - if (x.is_equal(exMINUSHALF())) - return numeric(2,3)*Pi; - // acos(-1) -> Pi - if (x.is_equal(exMINUSONE())) - return Pi; - // acos(float) -> float - if (!x.info(info_flags::rational)) - return acos_evalf(x); - } - - return acos(x).hold(); -} - -static ex acos_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return numMINUSONE()*power(1-power(x,exTWO()),exMINUSHALF()); -} - -REGISTER_FUNCTION(acos, acos_eval, acos_evalf, acos_diff, NULL); - -////////// -// inverse tangent (arc tangent) -////////// - -static ex atan_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(atan(x)) - - return atan(ex_to_numeric(x)); // -> numeric atan(numeric) -} - -static ex atan_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // atan(0) -> 0 - if (x.is_equal(exZERO())) - return exZERO(); - // atan(float) -> float - if (!x.info(info_flags::rational)) - return atan_evalf(x); - } - - return atan(x).hold(); -} - -static ex atan_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return power(1+x*x, -1); -} - -REGISTER_FUNCTION(atan, atan_eval, atan_evalf, atan_diff, NULL); - -////////// -// inverse tangent (atan2(y,x)) -////////// - -static ex atan2_evalf(ex const & y, ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(y,numeric) - TYPECHECK(x,numeric) - END_TYPECHECK(atan2(y,x)) - - return atan(ex_to_numeric(y),ex_to_numeric(x)); // -> numeric atan(numeric) -} - -static ex atan2_eval(ex const & y, ex const & x) -{ - if (y.info(info_flags::numeric) && !y.info(info_flags::rational) && - x.info(info_flags::numeric) && !x.info(info_flags::rational)) { - return atan2_evalf(y,x); - } - - return atan2(y,x).hold(); -} - -static ex atan2_diff(ex const & y, ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param<2); - - if (diff_param==0) { - // d/dy atan(y,x) - return pow(x*(1+y*y/(x*x)),-1); - } - // d/dx atan(y,x) - return -y*pow(x*x+y*y,-1); -} - -REGISTER_FUNCTION(atan2, atan2_eval, atan2_evalf, atan2_diff, NULL); - -////////// -// hyperbolic sine (trigonometric function) -////////// - -static ex sinh_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(sinh(x)) - - return sinh(ex_to_numeric(x)); // -> numeric sinh(numeric) -} - -static ex sinh_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // sinh(0) -> 0 - if (x.is_zero()) - return exZERO(); - // sinh(float) -> float - if (!x.info(info_flags::rational)) - return sinh_evalf(x); - } - - if (is_ex_exactly_of_type(x, function)) { - ex t=x.op(0); - // sinh(asinh(x)) -> x - if (is_ex_the_function(x, asinh)) - return t; - // sinh(acosh(x)) -> (x-1)^(1/2) * (x+1)^(1/2) - if (is_ex_the_function(x, acosh)) - return power(t-exONE(),exHALF())*power(t+exONE(),exHALF()); - // sinh(atanh(x)) -> x*(1-x^2)^(-1/2) - if (is_ex_the_function(x, atanh)) - return t*power(exONE()-power(t,exTWO()),exMINUSHALF()); - } - - return sinh(x).hold(); -} - -static ex sinh_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return cosh(x); -} - -REGISTER_FUNCTION(sinh, sinh_eval, sinh_evalf, sinh_diff, NULL); - -////////// -// hyperbolic cosine (trigonometric function) -////////// - -static ex cosh_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(cosh(x)) - - return cosh(ex_to_numeric(x)); // -> numeric cosh(numeric) -} - -static ex cosh_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // cosh(0) -> 1 - if (x.is_zero()) - return exONE(); - // cosh(float) -> float - if (!x.info(info_flags::rational)) - return cosh_evalf(x); - } - - if (is_ex_exactly_of_type(x, function)) { - ex t=x.op(0); - // cosh(acosh(x)) -> x - if (is_ex_the_function(x, acosh)) - return t; - // cosh(asinh(x)) -> (1+x^2)^(1/2) - if (is_ex_the_function(x, asinh)) - return power(exONE()+power(t,exTWO()),exHALF()); - // cosh(atanh(x)) -> (1-x^2)^(-1/2) - if (is_ex_the_function(x, atanh)) - return power(exONE()-power(t,exTWO()),exMINUSHALF()); - } - - return cosh(x).hold(); -} - -static ex cosh_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return sinh(x); -} - -REGISTER_FUNCTION(cosh, cosh_eval, cosh_evalf, cosh_diff, NULL); - -////////// -// hyperbolic tangent (trigonometric function) -////////// - -static ex tanh_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(tanh(x)) - - return tanh(ex_to_numeric(x)); // -> numeric tanh(numeric) -} - -static ex tanh_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // tanh(0) -> 0 - if (x.is_zero()) - return exZERO(); - // tanh(float) -> float - if (!x.info(info_flags::rational)) - return tanh_evalf(x); - } - - if (is_ex_exactly_of_type(x, function)) { - ex t=x.op(0); - // tanh(atanh(x)) -> x - if (is_ex_the_function(x, atanh)) - return t; - // tanh(asinh(x)) -> x*(1+x^2)^(-1/2) - if (is_ex_the_function(x, asinh)) - return t*power(exONE()+power(t,exTWO()),exMINUSHALF()); - // tanh(acosh(x)) -> (x-1)^(1/2)*(x+1)^(1/2)/x - if (is_ex_the_function(x, acosh)) - return power(t-exONE(),exHALF())*power(t+exONE(),exHALF())*power(t,exMINUSONE()); - } - - return tanh(x).hold(); -} - -static ex tanh_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return exONE()-power(tanh(x),exTWO()); -} - -REGISTER_FUNCTION(tanh, tanh_eval, tanh_evalf, tanh_diff, NULL); - -////////// -// inverse hyperbolic sine (trigonometric function) -////////// - -static ex asinh_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(asinh(x)) - - return asinh(ex_to_numeric(x)); // -> numeric asinh(numeric) -} - -static ex asinh_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // asinh(0) -> 0 - if (x.is_zero()) - return exZERO(); - // asinh(float) -> float - if (!x.info(info_flags::rational)) - return asinh_evalf(x); - } - - return asinh(x).hold(); -} - -static ex asinh_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return power(1+power(x,exTWO()),exMINUSHALF()); -} - -REGISTER_FUNCTION(asinh, asinh_eval, asinh_evalf, asinh_diff, NULL); - -////////// -// inverse hyperbolic cosine (trigonometric function) -////////// - -static ex acosh_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(acosh(x)) - - return acosh(ex_to_numeric(x)); // -> numeric acosh(numeric) -} - -static ex acosh_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // acosh(0) -> Pi*I/2 - if (x.is_zero()) - return Pi*I*numeric(1,2); - // acosh(1) -> 0 - if (x.is_equal(exONE())) - return exZERO(); - // acosh(-1) -> Pi*I - if (x.is_equal(exMINUSONE())) - return Pi*I; - // acosh(float) -> float - if (!x.info(info_flags::rational)) - return acosh_evalf(x); - } - - return acosh(x).hold(); -} - -static ex acosh_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return power(x-1,exMINUSHALF())*power(x+1,exMINUSHALF()); -} - -REGISTER_FUNCTION(acosh, acosh_eval, acosh_evalf, acosh_diff, NULL); - -////////// -// inverse hyperbolic tangent (trigonometric function) -////////// - -static ex atanh_evalf(ex const & x) -{ - BEGIN_TYPECHECK - TYPECHECK(x,numeric) - END_TYPECHECK(atanh(x)) - - return atanh(ex_to_numeric(x)); // -> numeric atanh(numeric) -} - -static ex atanh_eval(ex const & x) -{ - if (x.info(info_flags::numeric)) { - // atanh(0) -> 0 - if (x.is_zero()) - return exZERO(); - // atanh({+|-}1) -> throw - if (x.is_equal(exONE()) || x.is_equal(exONE())) - throw (std::domain_error("atanh_eval(): infinity")); - // atanh(float) -> float - if (!x.info(info_flags::rational)) - return atanh_evalf(x); - } - - return atanh(x).hold(); -} - -static ex atanh_diff(ex const & x, unsigned diff_param) -{ - GINAC_ASSERT(diff_param==0); - - return power(exONE()-power(x,exTWO()),exMINUSONE()); -} - -REGISTER_FUNCTION(atanh, atanh_eval, atanh_evalf, atanh_diff, NULL); - -} // namespace GiNaC diff --git a/ginac/inifcns_trig.cpp b/ginac/inifcns_trig.cpp new file mode 100644 index 00000000..82dbf7e9 --- /dev/null +++ b/ginac/inifcns_trig.cpp @@ -0,0 +1,1116 @@ +/** @file inifcns_trig.cpp + * + * Implementation of trigonometric functions. */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "inifcns_trig.h" + +#include "inifcns.h" +#include "inifcns_exp.h" +#include "ex.h" +#include "constant.h" +#include "numeric.h" +#include "power.h" +#include "operators.h" +#include "relational.h" +#include "symbol.h" +#include "pseries.h" +#include "utils.h" + +#include +#include + +namespace GiNaC { + +//////////////////////////////////////////////////////////////////////////////// +// sine (trigonometric function) +//////////////////////////////////////////////////////////////////////////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(sin_function, + print_func(&sin_function::do_print_latex)) + +ex sin_function::eval(int level) const +{ + const ex& x = seq[0]; + // sin(n/d*Pi) -> { all known non-nested radicals } + const ex SixtyExOverPi = _ex60*x/Pi; + ex sign = _ex1; + if (SixtyExOverPi.info(info_flags::integer)) { + numeric z = mod(ex_to(SixtyExOverPi),*_num120_p); + if (z>=*_num60_p) { + // wrap to interval [0, Pi) + z -= *_num60_p; + sign = _ex_1; + } + if (z>*_num30_p) { + // wrap to interval [0, Pi/2) + z = *_num60_p-z; + } + if (z.is_equal(*_num0_p)) // sin(0) -> 0 + return _ex0; + if (z.is_equal(*_num5_p)) // sin(Pi/12) -> sqrt(6)/4*(1-sqrt(3)/3) + return sign*_ex1_4*sqrt(_ex6)*(_ex1+_ex_1_3*sqrt(_ex3)); + if (z.is_equal(*_num6_p)) // sin(Pi/10) -> sqrt(5)/4-1/4 + return sign*(_ex1_4*sqrt(_ex5)+_ex_1_4); + if (z.is_equal(*_num10_p)) // sin(Pi/6) -> 1/2 + return sign*_ex1_2; + if (z.is_equal(*_num15_p)) // sin(Pi/4) -> sqrt(2)/2 + return sign*_ex1_2*sqrt(_ex2); + if (z.is_equal(*_num18_p)) // sin(3/10*Pi) -> sqrt(5)/4+1/4 + return sign*(_ex1_4*sqrt(_ex5)+_ex1_4); + if (z.is_equal(*_num20_p)) // sin(Pi/3) -> sqrt(3)/2 + return sign*_ex1_2*sqrt(_ex3); + if (z.is_equal(*_num25_p)) // sin(5/12*Pi) -> sqrt(6)/4*(1+sqrt(3)/3) + return sign*_ex1_4*sqrt(_ex6)*(_ex1+_ex1_3*sqrt(_ex3)); + if (z.is_equal(*_num30_p)) // sin(Pi/2) -> 1 + return sign; + } + + if (is_exactly_a(x)) { + // sin(asin(x)) -> x + return x.op(0); + } else if (is_exactly_a(x)) { + // sin(acos(x)) -> sqrt(1-x^2) + return sqrt(_ex1-power::power(x.op(0),_ex2)); + } else if (is_exactly_a(x)) { + const ex& t = x.op(0); + // sin(atan(x)) -> x/sqrt(1+x^2) + return t*power::power(_ex1+power::power(t,_ex2),_ex_1_2); + } + + // sin(float) -> float + if (x.info(info_flags::numeric) && !x.info(info_flags::crational)) + return sin(ex_to(x)); + + // sin() is odd + if (x.info(info_flags::negative)) + return -sin(-x); + + return this->hold(); +} + +ex sin_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) + return sin(ex_to(x)); + + return this->hold(); +} + +ex sin_function::pderivative(unsigned deriv_param) const +{ + GINAC_ASSERT(deriv_param==0); + // d/dx sin(x) -> cos(x) + return cos(seq[0]); +} + +void sin_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\sin"; + inherited::do_print(c,level); +} + +////////// +// cosine (trigonometric function) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(cos_function, + print_func(&cos_function::do_print_latex)) + +ex cos_function::eval(int level) const +{ + const ex& x = seq[0]; + // cos(n/d*Pi) -> { all known non-nested radicals } + const ex SixtyExOverPi = _ex60*x/Pi; + ex sign = _ex1; + if (SixtyExOverPi.info(info_flags::integer)) { + numeric z = mod(ex_to(SixtyExOverPi),*_num120_p); + if (z>=*_num60_p) { + // wrap to interval [0, Pi) + z = *_num120_p-z; + } + if (z>=*_num30_p) { + // wrap to interval [0, Pi/2) + z = *_num60_p-z; + sign = _ex_1; + } + if (z.is_equal(*_num0_p)) // cos(0) -> 1 + return sign; + if (z.is_equal(*_num5_p)) // cos(Pi/12) -> sqrt(6)/4*(1+sqrt(3)/3) + return sign*_ex1_4*sqrt(_ex6)*(_ex1+_ex1_3*sqrt(_ex3)); + if (z.is_equal(*_num10_p)) // cos(Pi/6) -> sqrt(3)/2 + return sign*_ex1_2*sqrt(_ex3); + if (z.is_equal(*_num12_p)) // cos(Pi/5) -> sqrt(5)/4+1/4 + return sign*(_ex1_4*sqrt(_ex5)+_ex1_4); + if (z.is_equal(*_num15_p)) // cos(Pi/4) -> sqrt(2)/2 + return sign*_ex1_2*sqrt(_ex2); + if (z.is_equal(*_num20_p)) // cos(Pi/3) -> 1/2 + return sign*_ex1_2; + if (z.is_equal(*_num24_p)) // cos(2/5*Pi) -> sqrt(5)/4-1/4x + return sign*(_ex1_4*sqrt(_ex5)+_ex_1_4); + if (z.is_equal(*_num25_p)) // cos(5/12*Pi) -> sqrt(6)/4*(1-sqrt(3)/3) + return sign*_ex1_4*sqrt(_ex6)*(_ex1+_ex_1_3*sqrt(_ex3)); + if (z.is_equal(*_num30_p)) // cos(Pi/2) -> 0 + return _ex0; + } + + if (is_exactly_a(x)) { + // cos(acos(x)) -> x + return x.op(0); + } else if (is_exactly_a(x)) { + // cos(asin(x)) -> sqrt(1-x^2) + return sqrt(_ex1-power::power(x.op(0),_ex2)); + } else if (is_exactly_a(x)) { + // cos(atan(x)) -> 1/sqrt(1+x^2) + return power::power(_ex1+power::power(x.op(0),_ex2),_ex_1_2); + } + + // cos(float) -> float + if (x.info(info_flags::numeric) && !x.info(info_flags::crational)) + return cos(ex_to(x)); + + // cos() is even + if (x.info(info_flags::negative)) + return cos(-x); + + return this->hold(); +} + +ex cos_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) + return cos(ex_to(x)); + + return this->hold(); +} + +ex cos_function::pderivative(unsigned deriv_param) const +{ + GINAC_ASSERT(deriv_param==0); + + // d/dx cos(x) -> -sin(x) + return -sin(seq[0]); +} + +void cos_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\cos"; + inherited::do_print(c,level); +} + +////////// +// tangent (trigonometric function) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(tan_function, + print_func(&tan_function::do_print_latex)) + +ex tan_function::eval(int level) const +{ + const ex& x = seq[0]; + // tan(n/d*Pi) -> { all known non-nested radicals } + const ex SixtyExOverPi = _ex60*x/Pi; + ex sign = _ex1; + if (SixtyExOverPi.info(info_flags::integer)) { + numeric z = mod(ex_to(SixtyExOverPi),*_num60_p); + if (z>=*_num60_p) { + // wrap to interval [0, Pi) + z -= *_num60_p; + } + if (z>=*_num30_p) { + // wrap to interval [0, Pi/2) + z = *_num60_p-z; + sign = _ex_1; + } + if (z.is_equal(*_num0_p)) // tan(0) -> 0 + return _ex0; + if (z.is_equal(*_num5_p)) // tan(Pi/12) -> 2-sqrt(3) + return sign*(_ex2-sqrt(_ex3)); + if (z.is_equal(*_num10_p)) // tan(Pi/6) -> sqrt(3)/3 + return sign*_ex1_3*sqrt(_ex3); + if (z.is_equal(*_num15_p)) // tan(Pi/4) -> 1 + return sign; + if (z.is_equal(*_num20_p)) // tan(Pi/3) -> sqrt(3) + return sign*sqrt(_ex3); + if (z.is_equal(*_num25_p)) // tan(5/12*Pi) -> 2+sqrt(3) + return sign*(sqrt(_ex3)+_ex2); + if (z.is_equal(*_num30_p)) // tan(Pi/2) -> infinity + throw (pole_error("tan_eval(): simple pole",1)); + } + + if (is_exactly_a(x)) { + // tan(atan(x)) -> x + return x.op(0); + } else if (is_exactly_a(x)) { + // tan(asin(x)) -> x/sqrt(1+x^2) + const ex& t = x.op(0); + return t*power::power(_ex1-power::power(t,_ex2),_ex_1_2); + } else if (is_exactly_a(x)) { + // tan(acos(x)) -> sqrt(1-x^2)/x + const ex& t = x.op(0); + return power::power(t,_ex_1)*sqrt(_ex1-power::power(t,_ex2)); + } + + // tan(float) -> float + if (x.info(info_flags::numeric) && !x.info(info_flags::crational)) { + return tan(ex_to(x)); + } + + // tan() is odd + if (x.info(info_flags::negative)) { + return -tan(-x); + } + + return this->hold(); +} + +ex tan_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) + return tan(ex_to(x)); + + return this->hold(); +} + +ex tan_function::pderivative(unsigned deriv_param) const +{ + GINAC_ASSERT(deriv_param==0); + + // d/dx tan(x) -> 1+tan(x)^2; + return (_ex1+power::power(tan(seq[0]),_ex2)); +} + +ex tan_function::series(const relational& rel, int order, unsigned options) const +{ + GINAC_ASSERT(is_a(rel.lhs())); + // method: + // Taylor series where there is no pole falls back to tan_deriv. + // On a pole simply expand sin(x)/cos(x). + const ex x_pt = seq[0].subs(rel, subs_options::no_pattern); + if (!(2*x_pt/Pi).info(info_flags::odd)) { + return basic::series(rel, order, options); + } + // if we got here we have to care for a simple pole + const ex& x = seq[0]; + return (sin(x)/cos(x)).series(rel, order, options); +} + +void tan_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\tan"; + inherited::do_print(c,level); +} + +////////// +// inverse sine (arc sine) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(asin_function, + print_func(&asin_function::do_print_latex)) + +ex asin_function::eval(int level) const +{ + const ex& x = seq[0]; + + if (x.info(info_flags::numeric)) { + + // asin(0) -> 0 + if (x.is_zero()) + return x; + + // asin(1/2) -> Pi/6 + if (x.is_equal(_ex1_2)) + return numeric(1,6)*Pi; + + // asin(1) -> Pi/2 + if (x.is_equal(_ex1)) + return _ex1_2*Pi; + + // asin(-1/2) -> -Pi/6 + if (x.is_equal(_ex_1_2)) + return numeric(-1,6)*Pi; + + // asin(-1) -> -Pi/2 + if (x.is_equal(_ex_1)) + return _ex_1_2*Pi; + + // asin(float) -> float + if (!x.info(info_flags::crational)) + return asin(ex_to(x)); + + // asin() is odd + if (x.info(info_flags::negative)) + return -asin(-x); + } + + return this->hold(); +} + +ex asin_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) + return asin(ex_to(x)); + + return this->hold(); +} + +ex asin_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + GINAC_ASSERT(deriv_param==0); + + // d/dx asin(x) -> 1/sqrt(1-x^2) + return power::power(1-power::power(x,_ex2),_ex_1_2); +} + +void asin_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\arcsin"; + inherited::do_print(c,level); +} + +////////// +// inverse cosine (arc cosine) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(acos_function, + print_func(&acos_function::do_print_latex)) + +ex acos_function::eval(int level) const +{ + const ex& x = seq[0]; + + if (x.info(info_flags::numeric)) { + + // acos(1) -> 0 + if (x.is_equal(_ex1)) + return _ex0; + + // acos(1/2) -> Pi/3 + if (x.is_equal(_ex1_2)) + return _ex1_3*Pi; + + // acos(0) -> Pi/2 + if (x.is_zero()) + return _ex1_2*Pi; + + // acos(-1/2) -> 2/3*Pi + if (x.is_equal(_ex_1_2)) + return numeric(2,3)*Pi; + + // acos(-1) -> Pi + if (x.is_equal(_ex_1)) + return Pi; + + // acos(float) -> float + if (!x.info(info_flags::crational)) + return acos(ex_to(x)); + + // acos(-x) -> Pi-acos(x) + if (x.info(info_flags::negative)) + return Pi-acos(-x); + } + + return this->hold(); +} + +ex acos_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) + return acos(ex_to(x)); + + return this->hold(); +} + +ex acos_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + GINAC_ASSERT(deriv_param==0); + + // d/dx acos(x) -> -1/sqrt(1-x^2) + return -power::power(1-power::power(x,_ex2),_ex_1_2); +} + +void acos_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\arccos"; + inherited::do_print(c,level); +} + +////////// +// inverse tangent (arc tangent) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(atan_function, + print_func(&atan_function::do_print_latex)) + +ex atan_function::eval(int level) const +{ + const ex& x = seq[0]; + + if (x.info(info_flags::numeric)) { + + // atan(0) -> 0 + if (x.is_zero()) + return _ex0; + + // atan(1) -> Pi/4 + if (x.is_equal(_ex1)) + return _ex1_4*Pi; + + // atan(-1) -> -Pi/4 + if (x.is_equal(_ex_1)) + return _ex_1_4*Pi; + + if (x.is_equal(I) || x.is_equal(-I)) + throw (pole_error("atan_eval(): logarithmic pole",0)); + + // atan(float) -> float + if (!x.info(info_flags::crational)) + return atan(ex_to(x)); + + // atan() is odd + if (x.info(info_flags::negative)) + return -atan(-x); + } + + return this->hold(); +} + +ex atan_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) + return atan(ex_to(x)); + + return this->hold(); +} + +ex atan_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + GINAC_ASSERT(deriv_param==0); + + // d/dx atan(x) -> 1/(1+x^2) + return power::power(_ex1+power::power(x,_ex2), _ex_1); +} + +ex atan_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& arg = seq[0]; + GINAC_ASSERT(is_a(rel.lhs())); + // method: + // Taylor series where there is no pole or cut falls back to atan_deriv. + // There are two branch cuts, one runnig from I up the imaginary axis and + // one running from -I down the imaginary axis. The points I and -I are + // poles. + // On the branch cuts and the poles series expand + // (log(1+I*x)-log(1-I*x))/(2*I) + // instead. + const ex arg_pt = arg.subs(rel, subs_options::no_pattern); + if (!(I*arg_pt).info(info_flags::real)) + return basic::series(rel, order, options); + if ((I*arg_pt).info(info_flags::real) && abs(I*arg_pt)<_ex1) + return basic::series(rel, order, options); + // care for the poles, using the defining formula for atan()... + if (arg_pt.is_equal(I) || arg_pt.is_equal(-I)) + return ((log(1+I*arg)-log(1-I*arg))/(2*I)).series(rel, order, options); + if (!(options & series_options::suppress_branchcut)) { + // method: + // This is the branch cut: assemble the primitive series manually and + // then add the corresponding complex step function. + const symbol& s = ex_to(rel.lhs()); + const ex& point = rel.rhs(); + const symbol foo; + const ex replarg = atan(arg).series(s==foo, order).subs(foo==point, subs_options::no_pattern); + ex Order0correction = replarg.op(0)+csgn(arg)*Pi*_ex_1_2; + if ((I*arg_pt)<_ex0) + Order0correction += log((I*arg_pt+_ex_1)/(I*arg_pt+_ex1))*I*_ex_1_2; + else + Order0correction += log((I*arg_pt+_ex1)/(I*arg_pt+_ex_1))*I*_ex1_2; + epvector seq; + seq.push_back(expair(Order0correction, _ex0)); + seq.push_back(expair(Order(_ex1), order)); + return (replarg - pseries(rel, seq)).series(rel, order); + } + return basic::series(rel, order, options); +} + +void atan_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\arctan"; + inherited::do_print(c,level); +} + +////////// +// inverse tangent (atan2(y,x)) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(atan2_function, + print_func(&atan2_function::do_print_latex)) + +ex atan2_function::eval(int level) const +{ + const ex& y = seq[0]; + const ex& x = seq[1]; + + if (y.info(info_flags::numeric) && x.info(info_flags::numeric)) { + + if (y.is_zero()) { + + // atan(0, 0) -> 0 + if (x.is_zero()) + return _ex0; + + // atan(0, x), x real and positive -> 0 + if (x.info(info_flags::positive)) + return _ex0; + + // atan(0, x), x real and negative -> -Pi + if (x.info(info_flags::negative)) + return _ex_1*Pi; + } + + if (x.is_zero()) { + + // atan(y, 0), y real and positive -> Pi/2 + if (y.info(info_flags::positive)) + return _ex1_2*Pi; + + // atan(y, 0), y real and negative -> -Pi/2 + if (y.info(info_flags::negative)) + return _ex_1_2*Pi; + } + + if (y.is_equal(x)) { + + // atan(y, y), y real and positive -> Pi/4 + if (y.info(info_flags::positive)) + return _ex1_4*Pi; + + // atan(y, y), y real and negative -> -3/4*Pi + if (y.info(info_flags::negative)) + return numeric(-3, 4)*Pi; + } + + if (y.is_equal(-x)) { + + // atan(y, -y), y real and positive -> 3*Pi/4 + if (y.info(info_flags::positive)) + return numeric(3, 4)*Pi; + + // atan(y, -y), y real and negative -> -Pi/4 + if (y.info(info_flags::negative)) + return _ex_1_4*Pi; + } + + // atan(float, float) -> float + if (!y.info(info_flags::crational) && !x.info(info_flags::crational)) + return atan(ex_to(y), ex_to(x)); + + // atan(real, real) -> atan(y/x) +/- Pi + if (y.info(info_flags::real) && x.info(info_flags::real)) { + if (x.info(info_flags::positive)) + return atan(y/x); + else if(y.info(info_flags::positive)) + return atan(y/x)+Pi; + else + return atan(y/x)-Pi; + } + } + + return this->hold(); +} + +ex atan2_function::evalf(int level) const +{ + const ex& y = seq[0]; + const ex& x = seq[1]; + if (is_exactly_a(y) && is_exactly_a(x)) + return atan(ex_to(y), ex_to(x)); + + return this->hold(); +} + +ex atan2_function::pderivative(unsigned deriv_param) const +{ + const ex& y = seq[0]; + const ex& x = seq[1]; + GINAC_ASSERT(deriv_param<2); + + if (deriv_param==0) { + // d/dy atan(y,x) + return x*power::power(power::power(x,_ex2)+power::power(y,_ex2),_ex_1); + } + // d/dx atan(y,x) + return -y*power::power(power::power(x,_ex2)+power::power(y,_ex2),_ex_1); +} + +void atan2_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\arctan_2"; + inherited::do_print(c,level); +} + +////////// +// hyperbolic sine (trigonometric function) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(sinh_function, + print_func(&sinh_function::do_print_latex)) + +ex sinh_function::eval(int level) const +{ + const ex& x = seq[0]; + + if (x.info(info_flags::numeric)) { + + // sinh(0) -> 0 + if (x.is_zero()) + return _ex0; + + // sinh(float) -> float + if (!x.info(info_flags::crational)) + return sinh(ex_to(x)); + + // sinh() is odd + if (x.info(info_flags::negative)) + return -sinh(-x); + } + + if ((x/Pi).info(info_flags::numeric) && + ex_to(x/Pi).real().is_zero()) // sinh(I*x) -> I*sin(x) + return I*sin(x/I); + + const ex& t = x.op(0); + if (is_exactly_a(x)) { + // sinh(asinh(x)) -> x + return t; + } else if (is_exactly_a(x)) { + // sinh(acosh(x)) -> sqrt(x-1) * sqrt(x+1) + return sqrt(t-_ex1)*sqrt(t+_ex1); + } else if (is_exactly_a(x)) { + // sinh(atanh(x)) -> x/sqrt(1-x^2) + return t*power::power(_ex1-power::power(t,_ex2),_ex_1_2); + } + + return this->hold(); +} + +ex sinh_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) + return sinh(ex_to(x)); + + return this->hold(); +} + +ex sinh_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + GINAC_ASSERT(deriv_param==0); + + // d/dx sinh(x) -> cosh(x) + return cosh(x); +} + +void sinh_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\arcsin"; + inherited::do_print(c,level); +} + +////////// +// hyperbolic cosine (trigonometric function) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(cosh_function, + print_func(&cosh_function::do_print_latex)) + +ex cosh_function::eval(int level) const +{ + const ex& x = seq[0]; + + if (x.info(info_flags::numeric)) { + + // cosh(0) -> 1 + if (x.is_zero()) + return _ex1; + + // cosh(float) -> float + if (!x.info(info_flags::crational)) + return cosh(ex_to(x)); + + // cosh() is even + if (x.info(info_flags::negative)) + return cosh(-x); + } + + if ((x/Pi).info(info_flags::numeric) && + ex_to(x/Pi).real().is_zero()) // cosh(I*x) -> cos(x) + return cos(x/I); + + if (is_exactly_a(x)) { + // cosh(acosh(x)) -> x + return x.op(0); + } else if (is_exactly_a(x)) { + // cosh(asinh(x)) -> sqrt(1+x^2) + return sqrt(_ex1+power::power(x.op(0),_ex2)); + } else if (is_exactly_a(x)) { + // cosh(atanh(x)) -> 1/sqrt(1-x^2) + return power::power(_ex1-power::power(x.op(0),_ex2),_ex_1_2); + } + + return this->hold(); +} + +ex cosh_function::evalf(int level) const +{ + const ex& x = seq[0]; + + if (is_exactly_a(x)) + return cosh(ex_to(x)); + + return this->hold(); +} + +ex cosh_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + GINAC_ASSERT(deriv_param==0); + + // d/dx cosh(x) -> sinh(x) + return sinh(x); +} + +void cosh_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\arccos"; + inherited::do_print(c,level); +} + +////////// +// hyperbolic tangent (trigonometric function) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(tanh_function, + print_func(&tanh_function::do_print_latex)) + +ex tanh_function::eval(int level) const +{ + const ex& x = seq[0]; + + if (x.info(info_flags::numeric)) { + + // tanh(0) -> 0 + if (x.is_zero()) + return _ex0; + + // tanh(float) -> float + if (!x.info(info_flags::crational)) + return tanh(ex_to(x)); + + // tanh() is odd + if (x.info(info_flags::negative)) + return -tanh(-x); + } + + if ((x/Pi).info(info_flags::numeric) && + ex_to(x/Pi).real().is_zero()) // tanh(I*x) -> I*tan(x); + return I*tan(x/I); + + if (is_exactly_a(x)) { + // tanh(atanh(x)) -> x + return x.op(0); + } else if (is_exactly_a(x)) { + // tanh(asinh(x)) -> x/sqrt(1+x^2) + const ex& t = x.op(0); + return t*power::power(_ex1+power::power(t,_ex2),_ex_1_2); + } else if (is_exactly_a(x)) { + // tanh(acosh(x)) -> sqrt(x-1)*sqrt(x+1)/x + const ex& t = x.op(0); + return sqrt(t-_ex1)*sqrt(t+_ex1)*power::power(t,_ex_1); + } + + return this->hold(); +} + +ex tanh_function::evalf(int level) const +{ + const ex& x = seq[0]; + if (is_exactly_a(x)) + return tanh(ex_to(x)); + + return this->hold(); +} + +ex tanh_function::pderivative(unsigned deriv_param) const +{ + GINAC_ASSERT(deriv_param==0); + + // d/dx tanh(x) -> 1-tanh(x)^2 + return _ex1-power::power(tanh(seq[0]),_ex2); +} + +ex tanh_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& x = seq[0]; + + GINAC_ASSERT(is_a(rel.lhs())); + // method: + // Taylor series where there is no pole falls back to tanh_deriv. + // On a pole simply expand sinh(x)/cosh(x). + const ex x_pt = x.subs(rel, subs_options::no_pattern); + if (!(2*I*x_pt/Pi).info(info_flags::odd)) + return basic::series(rel, order, options); + // if we got here we have to care for a simple pole + return (sinh(x)/cosh(x)).series(rel, order, options); +} + +void tanh_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\arctan"; + inherited::do_print(c,level); +} + +////////// +// inverse hyperbolic sine (trigonometric function) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(asinh_function, + print_func(&asinh_function::do_print_latex)) + +ex asinh_function::eval(int level) const +{ + const ex& x = seq[0]; + + if (x.info(info_flags::numeric)) { + + // asinh(0) -> 0 + if (x.is_zero()) + return _ex0; + + // asinh(float) -> float + if (!x.info(info_flags::crational)) + return asinh(ex_to(x)); + + // asinh() is odd + if (x.info(info_flags::negative)) + return -asinh(-x); + } + + return this->hold(); +} + +ex asinh_function::evalf(int level) const +{ + const ex& x = seq[0]; + + if (is_exactly_a(x)) + return asinh(ex_to(x)); + + return this->hold(); +} + +ex asinh_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + + GINAC_ASSERT(deriv_param==0); + + // d/dx asinh(x) -> 1/sqrt(1+x^2) + return power::power(_ex1+power::power(x,_ex2),_ex_1_2); +} + +void asinh_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\arcsinh"; + inherited::do_print(c,level); +} + +////////// +// inverse hyperbolic cosine (trigonometric function) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(acosh_function, + print_func(&acosh_function::do_print_latex)) + +ex acosh_function::eval(int level) const +{ + const ex& x = seq[0]; + + if (x.info(info_flags::numeric)) { + + // acosh(0) -> Pi*I/2 + if (x.is_zero()) + return Pi*I*numeric(1,2); + + // acosh(1) -> 0 + if (x.is_equal(_ex1)) + return _ex0; + + // acosh(-1) -> Pi*I + if (x.is_equal(_ex_1)) + return Pi*I; + + // acosh(float) -> float + if (!x.info(info_flags::crational)) + return acosh(ex_to(x)); + + // acosh(-x) -> Pi*I-acosh(x) + if (x.info(info_flags::negative)) + return Pi*I-acosh(-x); + } + + return this->hold(); +} + +ex acosh_function::evalf(int level) const +{ + const ex& x = seq[0]; + + if (is_exactly_a(x)) + return acosh(ex_to(x)); + + return this->hold(); +} + +ex acosh_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + + GINAC_ASSERT(deriv_param==0); + + // d/dx acosh(x) -> 1/(sqrt(x-1)*sqrt(x+1)) + return power::power(x+_ex_1,_ex_1_2)*power::power(x+_ex1,_ex_1_2); +} + +void acosh_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\arccosh"; + inherited::do_print(c,level); +} + +////////// +// inverse hyperbolic tangent (trigonometric function) +////////// + +GINAC_IMPLEMENT_FUNCTION_OPT(atanh_function, + print_func(&atanh_function::do_print_latex)) + +ex atanh_function::eval(int level) const +{ + const ex& x = seq[0]; + if (x.info(info_flags::numeric)) { + + // atanh(0) -> 0 + if (x.is_zero()) + return _ex0; + + // atanh({+|-}1) -> throw + if (x.is_equal(_ex1) || x.is_equal(_ex_1)) + throw (pole_error("atanh_eval(): logarithmic pole",0)); + + // atanh(float) -> float + if (!x.info(info_flags::crational)) + return atanh(ex_to(x)); + + // atanh() is odd + if (x.info(info_flags::negative)) + return -atanh(-x); + } + + return this->hold(); +} + +ex atanh_function::evalf(int level) const +{ + const ex& x = seq[0]; + + if (is_exactly_a(x)) + return atanh(ex_to(x)); + + return this->hold(); +} + +ex atanh_function::pderivative(unsigned deriv_param) const +{ + const ex& x = seq[0]; + GINAC_ASSERT(deriv_param==0); + + // d/dx atanh(x) -> 1/(1-x^2) + return power::power(_ex1-power::power(x,_ex2),_ex_1); +} + +ex atanh_function::series(const relational& rel, int order, unsigned options) const +{ + const ex& arg = seq[0]; + + GINAC_ASSERT(is_a(rel.lhs())); + // method: + // Taylor series where there is no pole or cut falls back to atanh_deriv. + // There are two branch cuts, one runnig from 1 up the real axis and one + // one running from -1 down the real axis. The points 1 and -1 are poles + // On the branch cuts and the poles series expand + // (log(1+x)-log(1-x))/2 + // instead. + const ex arg_pt = arg.subs(rel, subs_options::no_pattern); + if (!(arg_pt).info(info_flags::real)) + return basic::series(rel, order, options); + if ((arg_pt).info(info_flags::real) && abs(arg_pt)<_ex1) + return basic::series(rel, order, options); + // care for the poles, using the defining formula for atanh()... + if (arg_pt.is_equal(_ex1) || arg_pt.is_equal(_ex_1)) + return ((log(_ex1+arg)-log(_ex1-arg))*_ex1_2).series(rel, order, options); + // ...and the branch cuts (the discontinuity at the cut being just I*Pi) + if (!(options & series_options::suppress_branchcut)) { + // method: + // This is the branch cut: assemble the primitive series manually and + // then add the corresponding complex step function. + const symbol &s = ex_to(rel.lhs()); + const ex &point = rel.rhs(); + const symbol foo; + const ex replarg = atanh(arg).series(s==foo, order).subs(foo==point, subs_options::no_pattern); + ex Order0correction = replarg.op(0)+csgn(I*arg)*Pi*I*_ex1_2; + if (arg_pt<_ex0) + Order0correction += log((arg_pt+_ex_1)/(arg_pt+_ex1))*_ex1_2; + else + Order0correction += log((arg_pt+_ex1)/(arg_pt+_ex_1))*_ex_1_2; + epvector seq; + seq.push_back(expair(Order0correction, _ex0)); + seq.push_back(expair(Order(_ex1), order)); + return (replarg - pseries(rel, seq)).series(rel, order); + } + return basic::series(rel, order, options); +} + +void atanh_function::do_print_latex(const print_context& c, unsigned level) const +{ + c.s << "\\arctanh"; + inherited::do_print(c,level); +} + +} // namespace GiNaC diff --git a/ginac/inifcns_trig.h b/ginac/inifcns_trig.h new file mode 100644 index 00000000..e5baf10c --- /dev/null +++ b/ginac/inifcns_trig.h @@ -0,0 +1,258 @@ +/** @file inifcns_trig.h + * + * Interface to GiNaC's initially known trigonometric functions. */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __GINAC_INIFCNS_TRIG_H__ +#define __GINAC_INIFCNS_TRIG_H__ + +#include "ex.h" +#include "function.h" + +namespace GiNaC { + +/** Sine. */ + +class sin_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(sin_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline sin_function sin(const T1& x1) { return sin_function(x1); } +inline sin_function sin(double x1); +inline sin_function sin(float x1); + +/** Cosine. */ + +class cos_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(cos_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline cos_function cos(const T1& x1) { return cos_function(x1); } +inline cos_function cos(double x1); +inline cos_function cos(float x1); + +/** Tangent. */ + +class tan_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(tan_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; + virtual ex series(const relational & r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline tan_function tan(const T1& x1) { return tan_function(x1); } +inline tan_function tan(double x1); +inline tan_function tan(float x1); + +/** Inverse sine (arc sine). */ + +class asin_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(asin_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline asin_function asin(const T1& x1) { return asin_function(x1); } +inline asin_function asin(double x1); +inline asin_function asin(float x1); + +/** Inverse cosine (arc cosine). */ + +class acos_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(acos_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline acos_function acos(const T1& x1) { return acos_function(x1); } +inline acos_function acos(double x1); +inline acos_function acos(float x1); + +/** Inverse tangent (arc tangent). */ + +class atan_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(atan_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; + virtual ex series(const relational & r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline atan_function atan(const T1& x1) { return atan_function(x1); } +inline atan_function atan(double x1); +inline atan_function atan(float x1); + +/** Inverse tangent with two arguments. */ + +class atan2_function : public function +{ + GINAC_DECLARE_FUNCTION_2P(atan2_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline atan2_function atan2(const T1& x1, const T2& x2) { return atan2_function(x1, x2); } +inline atan2_function atan2(double x1, double x2); +inline atan2_function atan2(float x1, float x2); + +/** Hyperbolic Sine. */ + +class sinh_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(sinh_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline sinh_function sinh(const T1& x1) { return sinh_function(x1); } +inline sinh_function sinh(double x1); +inline sinh_function sinh(float x1); + +/** Hyperbolic Cosine. */ + +class cosh_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(cosh_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline cosh_function cosh(const T1& x1) { return cosh_function(x1); } +inline cosh_function cosh(double x1); +inline cosh_function cosh(float x1); + +/** Hyperbolic Tangent. */ + +class tanh_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(tanh_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; + virtual ex series(const relational & r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline tanh_function tanh(const T1& x1) { return tanh_function(x1); } +inline tanh_function tanh(double x1); +inline tanh_function tanh(float x1); + +/** Inverse hyperbolic Sine (area hyperbolic sine). */ + +class asinh_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(asinh_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline asinh_function asinh(const T1& x1) { return asinh_function(x1); } +inline asinh_function asinh(double x1); +inline asinh_function asinh(float x1); + +/** Inverse hyperbolic Cosine (area hyperbolic cosine). */ + +class acosh_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(acosh_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline acosh_function acosh(const T1& x1) { return acosh_function(x1); } +inline acosh_function acosh(double x1); +inline acosh_function acosh(float x1); + +/** Inverse hyperbolic Tangent (area hyperbolic tangent). */ + +class atanh_function : public function +{ + GINAC_DECLARE_FUNCTION_1P(atanh_function) +public: + virtual ex eval(int level = 0) const; + virtual ex evalf(int level = 0) const; + virtual ex pderivative(unsigned diff_param) const; + virtual ex series(const relational & r, int order, unsigned options = 0) const; +protected: + void do_print_latex(const print_context& c, unsigned level) const; +}; + +template inline atanh_function atanh(const T1& x1) { return atanh_function(x1); } +inline atanh_function atanh(double x1); +inline atanh_function atanh(float x1); + +} // namespace GiNaC + +#endif // ifndef __GINAC_INIFCNS_TRIG_H__ diff --git a/ginac/input_parser.yy b/ginac/input_parser.yy new file mode 100644 index 00000000..a47f980c --- /dev/null +++ b/ginac/input_parser.yy @@ -0,0 +1,207 @@ +/** @file input_parser.yy + * + * Input grammar definition for reading expressions. + * This file must be processed with yacc/bison. */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +/* + * Definitions + */ + +%{ +#include + +#include "ex.h" +#include "input_lexer.h" +#include "relational.h" +#include "operators.h" +#include "symbol.h" +#include "lst.h" +#include "power.h" +#include "exprseq.h" +#include "idx.h" +#include "indexed.h" +#include "matrix.h" +#include "inifcns.h" + +namespace GiNaC { + +#define YYERROR_VERBOSE 1 + +// Parsed output expression +ex parsed_ex; + +// Last error message returned by parser +static std::string parser_error; + +// Prototypes +ex attach_index(const ex & base, ex i, bool covariant); +%} + +/* Tokens (T_LITERAL means a literal value returned by the parser, but not + of class numeric or symbol (e.g. a constant or the FAIL object)) */ +%token T_EOF T_NUMBER T_SYMBOL T_LITERAL T_DIGITS T_EQUAL T_NOTEQ T_LESSEQ T_GREATEREQ + +/* Operator precedence and associativity */ +%right '=' +%left T_EQUAL T_NOTEQ +%left '<' '>' T_LESSEQ T_GREATEREQ +%left '+' '-' +%left '*' '/' '%' +%nonassoc NEG +%right '^' +%left '.' '~' +%nonassoc '!' + +%start input + + +/* + * Grammar rules + */ + +%% +input : exp T_EOF { + try { + parsed_ex = $1; + YYACCEPT; + } catch (std::exception &err) { + parser_error = err.what(); + YYERROR; + } + } + ; + +exp : T_NUMBER {$$ = $1;} + | T_SYMBOL { + if (is_lexer_symbol_predefined($1)) + $$ = $1.eval(); + else + throw (std::runtime_error("unknown symbol '" + get_symbol_name($1) + "'")); + } + | T_LITERAL {$$ = $1;} + | T_DIGITS {$$ = $1;} + | T_SYMBOL '(' exprseq ')' { + std::string n = get_symbol_name($1); + if (n == "sqrt") { + if ($3.nops() != 1) + throw (std::runtime_error("too many arguments to sqrt()")); + $$ = sqrt($3.op(0)); + } else { + factory_p p = find_func_factory(n+"_function"); + // exprseq -> exvector + const exprseq& in = ex_to($3); + exvector out; + for (exprseq::const_iterator it = in.begin(); it != in.end(); ++it) { + out.push_back(*it); + } + $$ = (*p)(out); +// $$ = n(ex_to($3)); +//TODO +// unsigned i = function::find_function(n, $3.nops()); +// $$ = function(i, ex_to($3)).eval(1); + } + } + | exp T_EQUAL exp {$$ = $1 == $3;} + | exp T_NOTEQ exp {$$ = $1 != $3;} + | exp '<' exp {$$ = $1 < $3;} + | exp T_LESSEQ exp {$$ = $1 <= $3;} + | exp '>' exp {$$ = $1 > $3;} + | exp T_GREATEREQ exp {$$ = $1 >= $3;} + | exp '+' exp {$$ = $1 + $3;} + | exp '-' exp {$$ = $1 - $3;} + | exp '*' exp {$$ = $1 * $3;} + | exp '/' exp {$$ = $1 / $3;} + | '-' exp %prec NEG {$$ = -$2;} + | '+' exp %prec NEG {$$ = $2;} + | exp '^' exp {$$ = pow($1, $3);} + | exp '.' exp {$$ = attach_index($1, $3, true);} + | exp '~' exp {$$ = attach_index($1, $3, false);} + | exp '!' {$$ = factorial($1);} + | '(' exp ')' {$$ = $2;} + | '{' list_or_empty '}' {$$ = $2;} + | '[' matrix ']' {$$ = lst_to_matrix(ex_to($2));} + ; + +exprseq : exp {$$ = exprseq($1);} + | exprseq ',' exp {exprseq es(ex_to($1)); $$ = es.append($3);} + ; + +list_or_empty: /* empty */ {$$ = *new lst;} + | list {$$ = $1;} + ; + +list : exp {$$ = lst($1);} + | list ',' exp {lst l(ex_to($1)); $$ = l.append($3);} + ; + +matrix : '[' row ']' {$$ = lst($2);} + | matrix ',' '[' row ']' {lst l(ex_to($1)); $$ = l.append($4);} + ; + +row : exp {$$ = lst($1);} + | row ',' exp {lst l(ex_to($1)); $$ = l.append($3);} + ; + + +/* + * Routines + */ + +%% +// Attach index to expression +ex attach_index(const ex & base, ex i, bool covariant) +{ + // Toggle index variance if necessary + if (is_a(i)) { + const varidx &vi = ex_to(i); + if (vi.is_covariant() != covariant) + i = vi.toggle_variance(); + } else if (!covariant) + throw (std::runtime_error("index '" + get_symbol_name(i) + "' is not a varidx and cannot be contravariant")); + + // Add index to an existing indexed object, or create a new indexed + // object if there are no indices yet + if (is_a(base)) { + const ex &b = base.op(0); + exvector iv; + for (unsigned n=1; n(&integral::do_print). + print_func(&integral::do_print_latex)) + + +////////// +// default constructor +////////// + +integral::integral() + : inherited(&integral::tinfo_static), + x((new symbol())->setflag(status_flags::dynallocated)) +{} + +////////// +// other constructors +////////// + +// public + +integral::integral(const ex & x_, const ex & a_, const ex & b_, const ex & f_) + : inherited(&integral::tinfo_static), x(x_), a(a_), b(b_), f(f_) +{ + if (!is_a(x)) { + throw(std::invalid_argument("first argument of integral must be of type symbol")); + } +} + +////////// +// archiving +////////// + +integral::integral(const archive_node & n, lst & sym_lst) : inherited(n, sym_lst) +{ + n.find_ex("x", x, sym_lst); + n.find_ex("a", a, sym_lst); + n.find_ex("b", b, sym_lst); + n.find_ex("f", f, sym_lst); +} + +void integral::archive(archive_node & n) const +{ + inherited::archive(n); + n.add_ex("x", x); + n.add_ex("a", a); + n.add_ex("b", b); + n.add_ex("f", f); +} + +DEFAULT_UNARCHIVE(integral) + +////////// +// functions overriding virtual functions from base classes +////////// + +void integral::do_print(const print_context & c, unsigned level) const +{ + c.s << "integral("; + x.print(c); + c.s << ","; + a.print(c); + c.s << ","; + b.print(c); + c.s << ","; + f.print(c); + c.s << ")"; +} + +void integral::do_print_latex(const print_latex & c, unsigned level) const +{ + string varname = ex_to(x).get_name(); + if (level > precedence()) + c.s << "\\left("; + c.s << "\\int_{"; + a.print(c); + c.s << "}^{"; + b.print(c); + c.s << "} d"; + if (varname.size() > 1) + c.s << "\\," << varname << "\\:"; + else + c.s << varname << "\\,"; + f.print(c,precedence()); + if (level > precedence()) + c.s << "\\right)"; +} + +int integral::compare_same_type(const basic & other) const +{ + GINAC_ASSERT(is_exactly_a(other)); + const integral &o = static_cast(other); + + int cmpval = x.compare(o.x); + if (cmpval) + return cmpval; + cmpval = a.compare(o.a); + if (cmpval) + return cmpval; + cmpval = b.compare(o.b); + if (cmpval) + return cmpval; + return f.compare(o.f); +} + +ex integral::eval(int level) const +{ + if ((level==1) && (flags & status_flags::evaluated)) + return *this; + if (level == -max_recursion_level) + throw(std::runtime_error("max recursion level reached")); + + ex eintvar = (level==1) ? x : x.eval(level-1); + ex ea = (level==1) ? a : a.eval(level-1); + ex eb = (level==1) ? b : b.eval(level-1); + ex ef = (level==1) ? f : f.eval(level-1); + + if (!ef.has(eintvar) && !haswild(ef)) + return eb*ef-ea*ef; + + if (ea==eb) + return _ex0; + + if (are_ex_trivially_equal(eintvar,x) && are_ex_trivially_equal(ea,a) + && are_ex_trivially_equal(eb,b) && are_ex_trivially_equal(ef,f)) + return this->hold(); + return (new integral(eintvar, ea, eb, ef)) + ->setflag(status_flags::dynallocated | status_flags::evaluated); +} + +ex integral::evalf(int level) const +{ + ex ea; + ex eb; + ex ef; + + if (level==1) { + ea = a; + eb = b; + ef = f; + } else if (level == -max_recursion_level) { + throw(runtime_error("max recursion level reached")); + } else { + ea = a.evalf(level-1); + eb = b.evalf(level-1); + ef = f.evalf(level-1); + } + + // 12.34 is just an arbitrary number used to check whether a number + // results after subsituting a number for the integration variable. + if (is_exactly_a(ea) && is_exactly_a(eb) + && is_exactly_a(ef.subs(x==12.34).evalf())) { + try { + return adaptivesimpson(x, ea, eb, ef); + } catch (runtime_error &rte) {} + } + + if (are_ex_trivially_equal(a, ea) && are_ex_trivially_equal(b, eb) + && are_ex_trivially_equal(f, ef)) + return *this; + else + return (new integral(x, ea, eb, ef)) + ->setflag(status_flags::dynallocated); +} + +int integral::max_integration_level = 15; +ex integral::relative_integration_error = 1e-8; + +ex subsvalue(const ex & var, const ex & value, const ex & fun) +{ + ex result = fun.subs(var==value).evalf(); + if (is_a(result)) + return result; + throw logic_error("integrand does not evaluate to numeric"); +} + +struct error_and_integral +{ + error_and_integral(const ex &err, const ex &integ) + :error(err), integral(integ){} + ex error; + ex integral; +}; + +struct error_and_integral_is_less +{ + bool operator()(const error_and_integral &e1,const error_and_integral &e2) + { + int c = e1.integral.compare(e2.integral); + if(c < 0) + return true; + if(c > 0) + return false; + return ex_is_less()(e1.error, e2.error); + } +}; + +typedef map lookup_map; + +/** Numeric integration routine based upon the "Adaptive Quadrature" one + * in "Numerical Analysis" by Burden and Faires. Parameters are integration + * variable, left boundary, right boundary, function to be integrated and + * the relative integration error. The function should evalf into a number + * after substituting the integration variable by a number. Another thing + * to note is that this implementation is no good at integrating functions + * with discontinuities. */ +ex adaptivesimpson(const ex & x, const ex & a_in, const ex & b_in, const ex & f, const ex & error) +{ + // Check whether boundaries and error are numbers. + ex a = is_exactly_a(a_in) ? a_in : a_in.evalf(); + ex b = is_exactly_a(b_in) ? b_in : b_in.evalf(); + if(!is_exactly_a(a) || !is_exactly_a(b)) + throw std::runtime_error("For numerical integration the boundaries of the integral should evalf into numbers."); + if(!is_exactly_a(error)) + throw std::runtime_error("For numerical integration the error should be a number."); + + // Use lookup table to be potentially much faster. + static lookup_map lookup; + static symbol ivar("ivar"); + ex lookupex = integral(ivar,a,b,f.subs(x==ivar)); + lookup_map::iterator emi = lookup.find(error_and_integral(error, lookupex)); + if (emi!=lookup.end()) + return emi->second; + + ex app = 0; + int i = 1; + exvector avec(integral::max_integration_level+1); + exvector hvec(integral::max_integration_level+1); + exvector favec(integral::max_integration_level+1); + exvector fbvec(integral::max_integration_level+1); + exvector fcvec(integral::max_integration_level+1); + exvector svec(integral::max_integration_level+1); + exvector errorvec(integral::max_integration_level+1); + vector lvec(integral::max_integration_level+1); + + avec[i] = a; + hvec[i] = (b-a)/2; + favec[i] = subsvalue(x, a, f); + fcvec[i] = subsvalue(x, a+hvec[i], f); + fbvec[i] = subsvalue(x, b, f); + svec[i] = hvec[i]*(favec[i]+4*fcvec[i]+fbvec[i])/3; + lvec[i] = 1; + errorvec[i] = error*svec[i]; + + while (i>0) { + ex fd = subsvalue(x, avec[i]+hvec[i]/2, f); + ex fe = subsvalue(x, avec[i]+3*hvec[i]/2, f); + ex s1 = hvec[i]*(favec[i]+4*fd+fcvec[i])/6; + ex s2 = hvec[i]*(fcvec[i]+4*fe+fbvec[i])/6; + ex nu1 = avec[i]; + ex nu2 = favec[i]; + ex nu3 = fcvec[i]; + ex nu4 = fbvec[i]; + ex nu5 = hvec[i]; + // hopefully prevents a crash if the function is zero sometimes. + ex nu6 = max(errorvec[i], (s1+s2)*error); + ex nu7 = svec[i]; + int nu8 = lvec[i]; + --i; + if (abs(ex_to(s1+s2-nu7)) <= nu6) + app+=(s1+s2); + else { + if (nu8>=integral::max_integration_level) + throw runtime_error("max integration level reached"); + ++i; + avec[i] = nu1+nu5; + favec[i] = nu3; + fcvec[i] = fe; + fbvec[i] = nu4; + hvec[i] = nu5/2; + errorvec[i]=nu6/2; + svec[i] = s2; + lvec[i] = nu8+1; + ++i; + avec[i] = nu1; + favec[i] = nu2; + fcvec[i] = fd; + fbvec[i] = nu3; + hvec[i] = hvec[i-1]; + errorvec[i]=errorvec[i-1]; + svec[i] = s1; + lvec[i] = lvec[i-1]; + } + } + + lookup[error_and_integral(error, lookupex)]=app; + return app; +} + +int integral::degree(const ex & s) const +{ + return ((b-a)*f).degree(s); +} + +int integral::ldegree(const ex & s) const +{ + return ((b-a)*f).ldegree(s); +} + +ex integral::eval_ncmul(const exvector & v) const +{ + return f.eval_ncmul(v); +} + +size_t integral::nops() const +{ + return 4; +} + +ex integral::op(size_t i) const +{ + GINAC_ASSERT(i<4); + + switch (i) { + case 0: + return x; + case 1: + return a; + case 2: + return b; + case 3: + return f; + default: + throw (std::out_of_range("integral::op() out of range")); + } +} + +ex & integral::let_op(size_t i) +{ + ensure_if_modifiable(); + switch (i) { + case 0: + return x; + case 1: + return a; + case 2: + return b; + case 3: + return f; + default: + throw (std::out_of_range("integral::let_op() out of range")); + } +} + +ex integral::expand(unsigned options) const +{ + if (options==0 && (flags & status_flags::expanded)) + return *this; + + ex newa = a.expand(options); + ex newb = b.expand(options); + ex newf = f.expand(options); + + if (is_a(newf)) { + exvector v; + v.reserve(newf.nops()); + for (size_t i=0; i(newf)) { + ex prefactor = 1; + ex rest = 1; + for (size_t i=0; isetflag(status_flags::expanded); + return *this; + } + + const basic & newint = (new integral(x, newa, newb, newf)) + ->setflag(status_flags::dynallocated); + if (options == 0) + newint.setflag(status_flags::expanded); + return newint; +} + +ex integral::derivative(const symbol & s) const +{ + if (s==x) + throw(logic_error("differentiation with respect to dummy variable")); + return b.diff(s)*f.subs(x==b)-a.diff(s)*f.subs(x==a)+integral(x, a, b, f.diff(s)); +} + +unsigned integral::return_type() const +{ + return f.return_type(); +} + +tinfo_t integral::return_type_tinfo() const +{ + return f.return_type_tinfo(); +} + +ex integral::conjugate() const +{ + ex conja = a.conjugate(); + ex conjb = b.conjugate(); + ex conjf = f.conjugate().subs(x.conjugate()==x); + + if (are_ex_trivially_equal(a, conja) && are_ex_trivially_equal(b, conjb) + && are_ex_trivially_equal(f, conjf)) + return *this; + + return (new integral(x, conja, conjb, conjf)) + ->setflag(status_flags::dynallocated); +} + +ex integral::eval_integ() const +{ + if (!(flags & status_flags::expanded)) + return this->expand().eval_integ(); + + if (f==x) + return b*b/2-a*a/2; + if (is_a(f) && f.op(0)==x) { + if (f.op(1)==-1) + return log(b/a); + if (!f.op(1).has(x)) { + ex primit = power(x,f.op(1)+1)/(f.op(1)+1); + return primit.subs(x==b)-primit.subs(x==a); + } + } + + return *this; +} + +} // namespace GiNaC diff --git a/ginac/matrix.cpp b/ginac/matrix.cpp index bacacad8..83b088b6 100644 --- a/ginac/matrix.cpp +++ b/ginac/matrix.cpp @@ -3,7 +3,7 @@ * Implementation of symbolic matrices */ /* - * GiNaC Copyright (C) 1999 Johannes Gutenberg University Mainz, Germany + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,227 +17,517 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include +#include +#include #include +#include #include #include "matrix.h" -#include "debugmsg.h" + +#include "relational.h" +#include "numeric.h" +#include "lst.h" +#include "idx.h" +#include "indexed.h" +#include "add.h" +#include "power.h" +#include "symbol.h" +#include "operators.h" +#include "normal.h" +#include "archive.h" +#include "utils.h" namespace GiNaC { +GINAC_IMPLEMENT_REGISTERED_CLASS_OPT(matrix, basic, + print_func(&matrix::do_print). + print_func(&matrix::do_print_latex). + print_func(&matrix::do_print_tree). + print_func(&matrix::do_print_python_repr)) + ////////// -// default constructor, destructor, copy constructor, assignment operator -// and helpers: +// default constructor ////////// -// public - /** Default ctor. Initializes to 1 x 1-dimensional zero-matrix. */ -matrix::matrix() - : basic(TINFO_matrix), row(1), col(1) +matrix::matrix() : inherited(&matrix::tinfo_static), row(1), col(1), m(1, _ex0) { - debugmsg("matrix default constructor",LOGLEVEL_CONSTRUCT); - m.push_back(exZERO()); + setflag(status_flags::not_shareable); } -matrix::~matrix() +////////// +// other constructors +////////// + +// public + +/** Very common ctor. Initializes to r x c-dimensional zero-matrix. + * + * @param r number of rows + * @param c number of cols */ +matrix::matrix(unsigned r, unsigned c) + : inherited(&matrix::tinfo_static), row(r), col(c), m(r*c, _ex0) { - debugmsg("matrix destructor",LOGLEVEL_DESTRUCT); + setflag(status_flags::not_shareable); } -matrix::matrix(matrix const & other) +// protected + +/** Ctor from representation, for internal use only. */ +matrix::matrix(unsigned r, unsigned c, const exvector & m2) + : inherited(&matrix::tinfo_static), row(r), col(c), m(m2) { - debugmsg("matrix copy constructor",LOGLEVEL_CONSTRUCT); - copy(other); + setflag(status_flags::not_shareable); } -matrix const & matrix::operator=(matrix const & other) +/** Construct matrix from (flat) list of elements. If the list has fewer + * elements than the matrix, the remaining matrix elements are set to zero. + * If the list has more elements than the matrix, the excessive elements are + * thrown away. */ +matrix::matrix(unsigned r, unsigned c, const lst & l) + : inherited(&matrix::tinfo_static), row(r), col(c), m(r*c, _ex0) { - debugmsg("matrix operator=",LOGLEVEL_ASSIGNMENT); - if (this != &other) { - destroy(1); - copy(other); - } - return *this; + setflag(status_flags::not_shareable); + + size_t i = 0; + for (lst::const_iterator it = l.begin(); it != l.end(); ++it, ++i) { + size_t x = i % c; + size_t y = i / c; + if (y >= r) + break; // matrix smaller than list: throw away excessive elements + m[y*c+x] = *it; + } } -// protected +////////// +// archiving +////////// -void matrix::copy(matrix const & other) +matrix::matrix(const archive_node &n, lst &sym_lst) : inherited(n, sym_lst) { - basic::copy(other); - row=other.row; - col=other.col; - m=other.m; // use STL's vector copying + setflag(status_flags::not_shareable); + + if (!(n.find_unsigned("row", row)) || !(n.find_unsigned("col", col))) + throw (std::runtime_error("unknown matrix dimensions in archive")); + m.reserve(row * col); + for (unsigned int i=0; true; i++) { + ex e; + if (n.find_ex("m", e, sym_lst, i)) + m.push_back(e); + else + break; + } } -void matrix::destroy(bool call_parent) +void matrix::archive(archive_node &n) const { - if (call_parent) basic::destroy(call_parent); + inherited::archive(n); + n.add_unsigned("row", row); + n.add_unsigned("col", col); + exvector::const_iterator i = m.begin(), iend = m.end(); + while (i != iend) { + n.add_ex("m", *i); + ++i; + } } +DEFAULT_UNARCHIVE(matrix) + ////////// -// other constructors +// functions overriding virtual functions from base classes ////////// // public -/** Very common ctor. Initializes to r x c-dimensional zero-matrix. - * - * @param r number of rows - * @param c number of cols */ -matrix::matrix(int r, int c) - : basic(TINFO_matrix), row(r), col(c) +void matrix::print_elements(const print_context & c, const char *row_start, const char *row_end, const char *row_sep, const char *col_sep) const { - debugmsg("matrix constructor from int,int",LOGLEVEL_CONSTRUCT); - m.resize(r*c, exZERO()); + for (unsigned ro=0; ro const & m2) - : basic(TINFO_matrix), row(r), col(c), m(m2) +void matrix::do_print(const print_context & c, unsigned level) const { - debugmsg("matrix constructor from int,int,vector",LOGLEVEL_CONSTRUCT); + c.s << "["; + print_elements(c, "[", "]", ",", ","); + c.s << "]"; } -////////// -// functions overriding virtual functions from bases classes -////////// - -// public +void matrix::do_print_latex(const print_latex & c, unsigned level) const +{ + c.s << "\\left(\\begin{array}{" << std::string(col,'c') << "}"; + print_elements(c, "", "", "\\\\", "&"); + c.s << "\\end{array}\\right)"; +} -basic * matrix::duplicate() const +void matrix::do_print_python_repr(const print_python_repr & c, unsigned level) const { - debugmsg("matrix duplicate",LOGLEVEL_DUPLICATE); - return new matrix(*this); + c.s << class_name() << '('; + print_elements(c, "[", "]", ",", ","); + c.s << ')'; } /** nops is defined to be rows x columns. */ -int matrix::nops() const +size_t matrix::nops() const { - return row*col; + return static_cast(row) * static_cast(col); } /** returns matrix entry at position (i/col, i%col). */ -ex & matrix::let_op(int const i) +ex matrix::op(size_t i) const { - return m[i]; + GINAC_ASSERT(i tmp(row*col); - for (int i=0; i::const_iterator r=m.begin(); r!=m.end(); ++r) { - if ((*r).has(other)) return true; - } - return false; + // check if we have to do anything at all + if ((level==1)&&(flags & status_flags::evaluated)) + return *this; + + // emergency break + if (level == -max_recursion_level) + throw (std::runtime_error("matrix::eval(): recursion limit exceeded")); + + // eval() entry by entry + exvector m2(row*col); + --level; + for (unsigned r=0; rsetflag(status_flags::dynallocated | + status_flags::evaluated); } -/** evaluate matrix entry by entry. */ -ex matrix::eval(int level) const +ex matrix::subs(const exmap & mp, unsigned options) const +{ + exvector m2(row * col); + for (unsigned r=0; r m2(row*col); - --level; - for (int r=0; rsetflag(status_flags::dynallocated | - status_flags::evaluated ); -} - -/** evaluate matrix numerically entry by entry. */ -ex matrix::evalf(int level) const -{ - debugmsg("matrix evalf",LOGLEVEL_MEMBER_FUNCTION); - - // check if we have to do anything at all - if (level==1) { - return *this; - } - - // emergency break - if (level == -max_recursion_level) { - throw (std::runtime_error("matrix::evalf(): recursion limit exceeded")); - } - - // evalf() entry by entry - vector m2(row*col); - --level; - for (int r=0; rconjugate(); + if (ev) { + ev->push_back(x); + continue; + } + if (are_ex_trivially_equal(x, *i)) { + continue; + } + ev = new exvector; + ev->reserve(m.size()); + for (exvector::const_iterator j=m.begin(); j!=i; ++j) { + ev->push_back(*j); + } + ev->push_back(x); + } + if (ev) { + ex result = matrix(row, col, *ev); + delete ev; + return result; + } + return *this; } // protected -int matrix::compare_same_type(basic const & other) const -{ - GINAC_ASSERT(is_exactly_of_type(other, matrix)); - matrix const & o=static_cast(const_cast(other)); - - // compare number of rows - if (row != o.rows()) { - return row < o.rows() ? -1 : 1; - } - - // compare number of columns - if (col != o.cols()) { - return col < o.cols() ? -1 : 1; - } - - // equal number of rows and columns, compare individual elements - int cmpval; - for (int r=0; r matrices are equal; - return 0; +int matrix::compare_same_type(const basic & other) const +{ + GINAC_ASSERT(is_exactly_a(other)); + const matrix &o = static_cast(other); + + // compare number of rows + if (row != o.rows()) + return row < o.rows() ? -1 : 1; + + // compare number of columns + if (col != o.cols()) + return col < o.cols() ? -1 : 1; + + // equal number of rows and columns, compare individual elements + int cmpval; + for (unsigned r=0; r matrices are equal; + return 0; } +bool matrix::match_same_type(const basic & other) const +{ + GINAC_ASSERT(is_exactly_a(other)); + const matrix & o = static_cast(other); + + // The number of rows and columns must be the same. This is necessary to + // prevent a 2x3 matrix from matching a 3x2 one. + return row == o.rows() && col == o.cols(); +} + +/** Automatic symbolic evaluation of an indexed matrix. */ +ex matrix::eval_indexed(const basic & i) const +{ + GINAC_ASSERT(is_a(i)); + GINAC_ASSERT(is_a(i.op(0))); + + bool all_indices_unsigned = static_cast(i).all_index_values_are(info_flags::nonnegint); + + // Check indices + if (i.nops() == 2) { + + // One index, must be one-dimensional vector + if (row != 1 && col != 1) + throw (std::runtime_error("matrix::eval_indexed(): vector must have exactly 1 index")); + + const idx & i1 = ex_to(i.op(1)); + + if (col == 1) { + + // Column vector + if (!i1.get_dim().is_equal(row)) + throw (std::runtime_error("matrix::eval_indexed(): dimension of index must match number of vector elements")); + + // Index numeric -> return vector element + if (all_indices_unsigned) { + unsigned n1 = ex_to(i1.get_value()).to_int(); + if (n1 >= row) + throw (std::runtime_error("matrix::eval_indexed(): value of index exceeds number of vector elements")); + return (*this)(n1, 0); + } + + } else { + + // Row vector + if (!i1.get_dim().is_equal(col)) + throw (std::runtime_error("matrix::eval_indexed(): dimension of index must match number of vector elements")); + + // Index numeric -> return vector element + if (all_indices_unsigned) { + unsigned n1 = ex_to(i1.get_value()).to_int(); + if (n1 >= col) + throw (std::runtime_error("matrix::eval_indexed(): value of index exceeds number of vector elements")); + return (*this)(0, n1); + } + } + + } else if (i.nops() == 3) { + + // Two indices + const idx & i1 = ex_to(i.op(1)); + const idx & i2 = ex_to(i.op(2)); + + if (!i1.get_dim().is_equal(row)) + throw (std::runtime_error("matrix::eval_indexed(): dimension of first index must match number of rows")); + if (!i2.get_dim().is_equal(col)) + throw (std::runtime_error("matrix::eval_indexed(): dimension of second index must match number of columns")); + + // Pair of dummy indices -> compute trace + if (is_dummy_pair(i1, i2)) + return trace(); + + // Both indices numeric -> return matrix element + if (all_indices_unsigned) { + unsigned n1 = ex_to(i1.get_value()).to_int(), n2 = ex_to(i2.get_value()).to_int(); + if (n1 >= row) + throw (std::runtime_error("matrix::eval_indexed(): value of first index exceeds number of rows")); + if (n2 >= col) + throw (std::runtime_error("matrix::eval_indexed(): value of second index exceeds number of columns")); + return (*this)(n1, n2); + } + + } else + throw (std::runtime_error("matrix::eval_indexed(): matrix must have exactly 2 indices")); + + return i.hold(); +} + +/** Sum of two indexed matrices. */ +ex matrix::add_indexed(const ex & self, const ex & other) const +{ + GINAC_ASSERT(is_a(self)); + GINAC_ASSERT(is_a(self.op(0))); + GINAC_ASSERT(is_a(other)); + GINAC_ASSERT(self.nops() == 2 || self.nops() == 3); + + // Only add two matrices + if (is_a(other.op(0))) { + GINAC_ASSERT(other.nops() == 2 || other.nops() == 3); + + const matrix &self_matrix = ex_to(self.op(0)); + const matrix &other_matrix = ex_to(other.op(0)); + + if (self.nops() == 2 && other.nops() == 2) { // vector + vector + + if (self_matrix.row == other_matrix.row) + return indexed(self_matrix.add(other_matrix), self.op(1)); + else if (self_matrix.row == other_matrix.col) + return indexed(self_matrix.add(other_matrix.transpose()), self.op(1)); + + } else if (self.nops() == 3 && other.nops() == 3) { // matrix + matrix + + if (self.op(1).is_equal(other.op(1)) && self.op(2).is_equal(other.op(2))) + return indexed(self_matrix.add(other_matrix), self.op(1), self.op(2)); + else if (self.op(1).is_equal(other.op(2)) && self.op(2).is_equal(other.op(1))) + return indexed(self_matrix.add(other_matrix.transpose()), self.op(1), self.op(2)); + + } + } + + // Don't know what to do, return unevaluated sum + return self + other; +} + +/** Product of an indexed matrix with a number. */ +ex matrix::scalar_mul_indexed(const ex & self, const numeric & other) const +{ + GINAC_ASSERT(is_a(self)); + GINAC_ASSERT(is_a(self.op(0))); + GINAC_ASSERT(self.nops() == 2 || self.nops() == 3); + + const matrix &self_matrix = ex_to(self.op(0)); + + if (self.nops() == 2) + return indexed(self_matrix.mul(other), self.op(1)); + else // self.nops() == 3 + return indexed(self_matrix.mul(other), self.op(1), self.op(2)); +} + +/** Contraction of an indexed matrix with something else. */ +bool matrix::contract_with(exvector::iterator self, exvector::iterator other, exvector & v) const +{ + GINAC_ASSERT(is_a(*self)); + GINAC_ASSERT(is_a(*other)); + GINAC_ASSERT(self->nops() == 2 || self->nops() == 3); + GINAC_ASSERT(is_a(self->op(0))); + + // Only contract with other matrices + if (!is_a(other->op(0))) + return false; + + GINAC_ASSERT(other->nops() == 2 || other->nops() == 3); + + const matrix &self_matrix = ex_to(self->op(0)); + const matrix &other_matrix = ex_to(other->op(0)); + + if (self->nops() == 2) { + + if (other->nops() == 2) { // vector * vector (scalar product) + + if (self_matrix.col == 1) { + if (other_matrix.col == 1) { + // Column vector * column vector, transpose first vector + *self = self_matrix.transpose().mul(other_matrix)(0, 0); + } else { + // Column vector * row vector, swap factors + *self = other_matrix.mul(self_matrix)(0, 0); + } + } else { + if (other_matrix.col == 1) { + // Row vector * column vector, perfect + *self = self_matrix.mul(other_matrix)(0, 0); + } else { + // Row vector * row vector, transpose second vector + *self = self_matrix.mul(other_matrix.transpose())(0, 0); + } + } + *other = _ex1; + return true; + + } else { // vector * matrix + + // B_i * A_ij = (B*A)_j (B is row vector) + if (is_dummy_pair(self->op(1), other->op(1))) { + if (self_matrix.row == 1) + *self = indexed(self_matrix.mul(other_matrix), other->op(2)); + else + *self = indexed(self_matrix.transpose().mul(other_matrix), other->op(2)); + *other = _ex1; + return true; + } + + // B_j * A_ij = (A*B)_i (B is column vector) + if (is_dummy_pair(self->op(1), other->op(2))) { + if (self_matrix.col == 1) + *self = indexed(other_matrix.mul(self_matrix), other->op(1)); + else + *self = indexed(other_matrix.mul(self_matrix.transpose()), other->op(1)); + *other = _ex1; + return true; + } + } + + } else if (other->nops() == 3) { // matrix * matrix + + // A_ij * B_jk = (A*B)_ik + if (is_dummy_pair(self->op(2), other->op(1))) { + *self = indexed(self_matrix.mul(other_matrix), self->op(1), other->op(2)); + *other = _ex1; + return true; + } + + // A_ij * B_kj = (A*Btrans)_ik + if (is_dummy_pair(self->op(2), other->op(2))) { + *self = indexed(self_matrix.mul(other_matrix.transpose()), self->op(1), other->op(1)); + *other = _ex1; + return true; + } + + // A_ji * B_jk = (Atrans*B)_ik + if (is_dummy_pair(self->op(1), other->op(1))) { + *self = indexed(self_matrix.transpose().mul(other_matrix), self->op(2), other->op(2)); + *other = _ex1; + return true; + } + + // A_ji * B_kj = (B*A)_ki + if (is_dummy_pair(self->op(1), other->op(2))) { + *self = indexed(other_matrix.mul(self_matrix), other->op(1), self->op(2)); + *other = _ex1; + return true; + } + } + + return false; +} + + ////////// // non-virtual functions in this class ////////// @@ -247,629 +537,1116 @@ int matrix::compare_same_type(basic const & other) const /** Sum of matrices. * * @exception logic_error (incompatible matrices) */ -matrix matrix::add(matrix const & other) const -{ - if (col != other.col || row != other.row) { - throw (std::logic_error("matrix::add(): incompatible matrices")); - } - - vector sum(this->m); - vector::iterator i; - vector::const_iterator ci; - for (i=sum.begin(), ci=other.m.begin(); - i!=sum.end(); - ++i, ++ci) { - (*i) += (*ci); - } - return matrix(row,col,sum); +matrix matrix::add(const matrix & other) const +{ + if (col != other.col || row != other.row) + throw std::logic_error("matrix::add(): incompatible matrices"); + + exvector sum(this->m); + exvector::iterator i = sum.begin(), end = sum.end(); + exvector::const_iterator ci = other.m.begin(); + while (i != end) + *i++ += *ci++; + + return matrix(row,col,sum); } + /** Difference of matrices. * * @exception logic_error (incompatible matrices) */ -matrix matrix::sub(matrix const & other) const -{ - if (col != other.col || row != other.row) { - throw (std::logic_error("matrix::sub(): incompatible matrices")); - } - - vector dif(this->m); - vector::iterator i; - vector::const_iterator ci; - for (i=dif.begin(), ci=other.m.begin(); - i!=dif.end(); - ++i, ++ci) { - (*i) -= (*ci); - } - return matrix(row,col,dif); +matrix matrix::sub(const matrix & other) const +{ + if (col != other.col || row != other.row) + throw std::logic_error("matrix::sub(): incompatible matrices"); + + exvector dif(this->m); + exvector::iterator i = dif.begin(), end = dif.end(); + exvector::const_iterator ci = other.m.begin(); + while (i != end) + *i++ -= *ci++; + + return matrix(row,col,dif); } + /** Product of matrices. * * @exception logic_error (incompatible matrices) */ -matrix matrix::mul(matrix const & other) const -{ - if (col != other.row) { - throw (std::logic_error("matrix::mul(): incompatible matrices")); - } - - vector prod(row*other.col); - for (int i=0; icols() != other.rows()) + throw std::logic_error("matrix::mul(): incompatible matrices"); + + exvector prod(this->rows()*other.cols()); + + for (unsigned r1=0; r1rows(); ++r1) { + for (unsigned c=0; ccols(); ++c) { + // Quick test: can we shortcut? + if (m[r1*col+c].is_zero()) + continue; + for (unsigned r2=0; r2(expn)) { + // Integer cases are computed by successive multiplication, using the + // obvious shortcut of storing temporaries, like A^4 == (A*A)*(A*A). + if (expn.info(info_flags::integer)) { + numeric b = ex_to(expn); + matrix A(row,col); + if (expn.info(info_flags::negative)) { + b *= -1; + A = this->inverse(); + } else { + A = *this; + } + matrix C(row,col); + for (unsigned r=0; r=row || co<0 || co>=col) { - throw (std::range_error("matrix::operator(): index out of range")); - } - - return m[ro*col+co]; + if (ro>=row || co>=col) + throw (std::range_error("matrix::operator(): index out of range")); + + return m[ro*col+co]; } -/** Set individual elements manually. + +/** operator() to access elements for writing. * + * @param ro row of element + * @param co column of element * @exception range_error (index out of range) */ -matrix & matrix::set(int ro, int co, ex value) +ex & matrix::operator() (unsigned ro, unsigned co) { - if (ro<0 || ro>=row || co<0 || co>=col) { - throw (std::range_error("matrix::set(): index out of range")); - } - - ensure_if_modifiable(); - m[ro*col+co]=value; - return *this; + if (ro>=row || co>=col) + throw (std::range_error("matrix::operator(): index out of range")); + + ensure_if_modifiable(); + return m[ro*col+co]; } + /** Transposed of an m x n matrix, producing a new n x m matrix object that * represents the transposed. */ -matrix matrix::transpose(void) const -{ - vector trans(col*row); - - for (int r=0; r -int permutation_sign(vector s) -{ - if (s.size() < 2) - return 0; - int sigma=1; - for (typename vector::iterator i=s.begin(); i!=s.end()-1; ++i) { - for (typename vector::iterator j=i+1; j!=s.end(); ++j) { - if (*i == *j) - return 0; - if (*i > *j) { - iter_swap(i,j); - sigma = -sigma; - } - } - } - return sigma; -} - -/** Determinant built by application of the full permutation group. This - * routine is only called internally by matrix::determinant(). */ -ex determinant_symbolic_perm(const matrix & M) -{ - GINAC_ASSERT(M.rows()==M.cols()); // cannot happen, just in case... - - if (M.rows()==1) { // speed things up - return M(0,0); - } - - ex det; - ex term; - vector sigma(M.cols()); - for (int i=0; icols()*this->rows()); + + for (unsigned r=0; rcols(); ++r) + for (unsigned c=0; crows(); ++c) + trans[r*this->rows()+c] = m[c*this->cols()+r]; + + return matrix(this->cols(),this->rows(),trans); +} /** Determinant of square matrix. This routine doesn't actually calculate the * determinant, it only implements some heuristics about which algorithm to - * call. When the parameter for normalization is explicitly turned off this - * method does not normalize its result at the end, which might imply that - * the symbolic 2x2 matrix [[a/(a-b),1],[b/(a-b),1]] is not immediatly - * recognized to be unity. (This is Mathematica's default behaviour, it - * should be used with care.) + * run. If all the elements of the matrix are elements of an integral domain + * the determinant is also in that integral domain and the result is expanded + * only. If one or more elements are from a quotient field the determinant is + * usually also in that quotient field and the result is normalized before it + * is returned. This implies that the determinant of the symbolic 2x2 matrix + * [[a/(a-b),1],[b/(a-b),1]] is returned as unity. (In this respect, it + * behaves like MapleV and unlike Mathematica.) * - * @param normalized may be set to false if no normalization of the - * result is desired (i.e. to force Mathematica behavior, Maple - * does normalize the result). + * @param algo allows to chose an algorithm * @return the determinant as a new expression - * @exception logic_error (matrix not square) */ -ex matrix::determinant(bool normalized) const -{ - if (row != col) { - throw (std::logic_error("matrix::determinant(): matrix not square")); - } - - // check, if there are non-numeric entries in the matrix: - for (vector::const_iterator r=m.begin(); r!=m.end(); ++r) { - if (!(*r).info(info_flags::numeric)) { - if (normalized) { - return determinant_symbolic_minor(*this).normal(); - } else { - return determinant_symbolic_perm(*this); - } - } - } - // if it turns out that all elements are numeric - return determinant_numeric(*this); -} - -/** Trace of a matrix. + * @exception logic_error (matrix not square) + * @see determinant_algo */ +ex matrix::determinant(unsigned algo) const +{ + if (row!=col) + throw (std::logic_error("matrix::determinant(): matrix not square")); + GINAC_ASSERT(row*col==m.capacity()); + + // Gather some statistical information about this matrix: + bool numeric_flag = true; + bool normal_flag = false; + unsigned sparse_count = 0; // counts non-zero elements + exvector::const_iterator r = m.begin(), rend = m.end(); + while (r != rend) { + if (!r->info(info_flags::numeric)) + numeric_flag = false; + exmap srl; // symbol replacement list + ex rtest = r->to_rational(srl); + if (!rtest.is_zero()) + ++sparse_count; + if (!rtest.info(info_flags::crational_polynomial) && + rtest.info(info_flags::rational_function)) + normal_flag = true; + ++r; + } + + // Here is the heuristics in case this routine has to decide: + if (algo == determinant_algo::automatic) { + // Minor expansion is generally a good guess: + algo = determinant_algo::laplace; + // Does anybody know when a matrix is really sparse? + // Maybe <~row/2.236 nonzero elements average in a row? + if (row>3 && 5*sparse_count<=row*col) + algo = determinant_algo::bareiss; + // Purely numeric matrix can be handled by Gauss elimination. + // This overrides any prior decisions. + if (numeric_flag) + algo = determinant_algo::gauss; + } + + // Trap the trivial case here, since some algorithms don't like it + if (this->row==1) { + // for consistency with non-trivial determinants... + if (normal_flag) + return m[0].normal(); + else + return m[0].expand(); + } + + // Compute the determinant + switch(algo) { + case determinant_algo::gauss: { + ex det = 1; + matrix tmp(*this); + int sign = tmp.gauss_elimination(true); + for (unsigned d=0; d uintpair; + std::vector c_zeros; // number of zeros in column + for (unsigned c=0; c pre_sort; + for (std::vector::const_iterator i=c_zeros.begin(); i!=c_zeros.end(); ++i) + pre_sort.push_back(i->second); + std::vector pre_sort_test(pre_sort); // permutation_sign() modifies the vector so we make a copy here + int sign = permutation_sign(pre_sort_test.begin(), pre_sort_test.end()); + exvector result(row*col); // represents sorted matrix + unsigned c = 0; + for (std::vector::const_iterator i=pre_sort.begin(); + i!=pre_sort.end(); + ++i,++c) { + for (unsigned r=0; rinfo(info_flags::numeric)) + numeric_flag = false; + ++r; + } + + // The pure numeric case is traditionally rather common. Hence, it is + // trapped and we use Leverrier's algorithm which goes as row^3 for + // every coefficient. The expensive part is the matrix multiplication. + if (numeric_flag) { + + matrix B(*this); + ex c = B.trace(); + ex poly = power(lambda, row) - c*power(lambda, row-1); + for (unsigned i=1; imul(B); + c = B.trace() / ex(i+1); + poly -= c*power(lambda, row-i-1); + } + if (row%2) + return -poly; + else + return poly; + + } else { + + matrix M(*this); + for (unsigned r=0; rsolve(vars,identity); + } catch (const std::runtime_error & e) { + if (e.what()==std::string("matrix::solve(): inconsistent linear system")) + throw (std::runtime_error("matrix::inverse(): singular matrix")); + else + throw; + } + return sol; +} + + +/** Solve a linear system consisting of a m x n matrix and a m x p right hand + * side by applying an elimination scheme to the augmented matrix. * - * @param vars n x p matrix + * @param vars n x p matrix, all elements must be symbols * @param rhs m x p matrix + * @param algo selects the solving algorithm + * @return n x p solution matrix * @exception logic_error (incompatible matrices) - * @exception runtime_error (singular matrix) */ -matrix matrix::fraction_free_elim(matrix const & vars, - matrix const & rhs) const -{ - if ((row != rhs.row) || (col != vars.row) || (rhs.col != vars.col)) { - throw (std::logic_error("matrix::solve(): incompatible matrices")); - } - - matrix a(*this); // make a copy of the matrix - matrix b(rhs); // make a copy of the rhs vector - - // given an m x n matrix a, reduce it to upper echelon form - int m=a.row; - int n=a.col; - int sign=1; - ex divisor=1; - int r=1; - - // eliminate below row r, with pivot in column k - for (int k=1; (k<=n)&&(r<=m); ++k) { - // find a nonzero pivot - int p; - for (p=r; (p<=m)&&(a.ffe_get(p,k).is_equal(exZERO())); ++p) {} - // pivot is in row p - if (p<=m) { - if (p!=r) { - // switch rows p and r - for (int j=k; j<=n; ++j) { - a.ffe_swap(p,j,r,j); - } - b.ffe_swap(p,1,r,1); - // keep track of sign changes due to row exchange - sign=-sign; - } - for (int i=r+1; i<=m; ++i) { - for (int j=k+1; j<=n; ++j) { - a.ffe_set(i,j,(a.ffe_get(r,k)*a.ffe_get(i,j) - -a.ffe_get(r,j)*a.ffe_get(i,k))/divisor); - a.ffe_set(i,j,a.ffe_get(i,j).normal() /*.normal() */ ); - } - b.ffe_set(i,1,(a.ffe_get(r,k)*b.ffe_get(i,1) - -b.ffe_get(r,1)*a.ffe_get(i,k))/divisor); - b.ffe_set(i,1,b.ffe_get(i,1).normal() /*.normal() */ ); - a.ffe_set(i,k,0); - } - divisor=a.ffe_get(r,k); - r++; - } - } - // optionally compute the determinant for square or augmented matrices - // if (r==m+1) { det=sign*divisor; } else { det=0; } - - /* - for (int r=1; r<=m; ++r) { - for (int c=1; c<=n; ++c) { - cout << a.ffe_get(r,c) << "\t"; - } - cout << " | " << b.ffe_get(r,1) << endl; - } - */ - -#ifdef DO_GINAC_ASSERT - // test if we really have an upper echelon matrix - int zero_in_last_row=-1; - for (int r=1; r<=m; ++r) { - int zero_in_this_row=0; - for (int c=1; c<=n; ++c) { - if (a.ffe_get(r,c).is_equal(exZERO())) { - zero_in_this_row++; - } else { - break; - } - } - GINAC_ASSERT((zero_in_this_row>zero_in_last_row)||(zero_in_this_row=n)); - zero_in_last_row=zero_in_this_row; - } -#endif // def DO_GINAC_ASSERT - - // assemble solution - matrix sol(n,1); - int last_assigned_sol=n+1; - for (int r=m; r>0; --r) { - int first_non_zero=1; - while ((first_non_zero<=n)&&(a.ffe_get(r,first_non_zero).is_zero())) { - first_non_zero++; - } - if (first_non_zero>n) { - // row consists only of zeroes, corresponding rhs must be 0 as well - if (!b.ffe_get(r,1).is_zero()) { - throw (std::runtime_error("matrix::fraction_free_elim(): singular matrix")); - } - } else { - // assign solutions for vars between first_non_zero+1 and - // last_assigned_sol-1: free parameters - for (int c=first_non_zero+1; c<=last_assigned_sol-1; ++c) { - sol.ffe_set(c,1,vars.ffe_get(c,1)); - } - ex e=b.ffe_get(r,1); - for (int c=first_non_zero+1; c<=n; ++c) { - e=e-a.ffe_get(r,c)*sol.ffe_get(c,1); - } - sol.ffe_set(first_non_zero,1, - (e/a.ffe_get(r,first_non_zero)).normal()); - last_assigned_sol=first_non_zero; - } - } - // assign solutions for vars between 1 and - // last_assigned_sol-1: free parameters - for (int c=1; c<=last_assigned_sol-1; ++c) { - sol.ffe_set(c,1,vars.ffe_get(c,1)); - } - - /* - for (int c=1; c<=n; ++c) { - cout << vars.ffe_get(c,1) << "->" << sol.ffe_get(c,1) << endl; - } - */ - -#ifdef DO_GINAC_ASSERT - // test solution with echelon matrix - for (int r=1; r<=m; ++r) { - ex e=0; - for (int c=1; c<=n; ++c) { - e=e+a.ffe_get(r,c)*sol.ffe_get(c,1); - } - if (!(e-b.ffe_get(r,1)).normal().is_zero()) { - cout << "e=" << e; - cout << "b.ffe_get(" << r<<",1)=" << b.ffe_get(r,1) << endl; - cout << "diff=" << (e-b.ffe_get(r,1)).normal() << endl; - } - GINAC_ASSERT((e-b.ffe_get(r,1)).normal().is_zero()); - } - - // test solution with original matrix - for (int r=1; r<=m; ++r) { - ex e=0; - for (int c=1; c<=n; ++c) { - e=e+ffe_get(r,c)*sol.ffe_get(c,1); - } - try { - if (!(e-rhs.ffe_get(r,1)).normal().is_zero()) { - cout << "e=" << e << endl; - e.printtree(cout); - ex en=e.normal(); - cout << "e.normal()=" << en << endl; - en.printtree(cout); - cout << "rhs.ffe_get(" << r<<",1)=" << rhs.ffe_get(r,1) << endl; - cout << "diff=" << (e-rhs.ffe_get(r,1)).normal() << endl; - } - } catch (...) { - ex xxx=e-rhs.ffe_get(r,1); - cerr << "xxx=" << xxx << endl << endl; - } - GINAC_ASSERT((e-rhs.ffe_get(r,1)).normal().is_zero()); - } -#endif // def DO_GINAC_ASSERT - - return sol; -} - -/** Solve simultaneous set of equations. */ -matrix matrix::solve(matrix const & v) const -{ - if (!(row == col && col == v.row)) { - throw (std::logic_error("matrix::solve(): incompatible matrices")); - } - - // build the extended matrix of *this with v attached to the right - matrix tmp(row,col+v.col); - for (int r=0; r sol(v.row*v.col); - for (int c=0; c=0; --r) { - sol[r*v.col+c] = tmp[r*tmp.col+c]; - for (int i=r+1; irows(); + const unsigned n = this->cols(); + const unsigned p = rhs.cols(); + + // syntax checks + if ((rhs.rows() != m) || (vars.rows() != n) || (vars.col != p)) + throw (std::logic_error("matrix::solve(): incompatible matrices")); + for (unsigned ro=0; rom[r*n+c]; + for (unsigned c=0; cinfo(info_flags::numeric)) + numeric_flag = false; + ++r; + } + + // Here is the heuristics in case this routine has to decide: + if (algo == solve_algo::automatic) { + // Bareiss (fraction-free) elimination is generally a good guess: + algo = solve_algo::bareiss; + // For m<3, Bareiss elimination is equivalent to division free + // elimination but has more logistic overhead + if (m<3) + algo = solve_algo::divfree; + // This overrides any prior decisions. + if (numeric_flag) + algo = solve_algo::gauss; + } + + // Eliminate the augmented matrix: + switch(algo) { + case solve_algo::gauss: + aug.gauss_elimination(); + break; + case solve_algo::divfree: + aug.division_free_elimination(); + break; + case solve_algo::bareiss: + default: + aug.fraction_free_elimination(); + } + + // assemble the solution matrix: + matrix sol(n,p); + for (unsigned co=0; co=0; --r) { + unsigned fnz = 1; // first non-zero in row + while ((fnz<=n) && (aug.m[r*(n+p)+(fnz-1)].is_zero())) + ++fnz; + if (fnz>n) { + // row consists only of zeros, corresponding rhs must be 0, too + if (!aug.m[r*(n+p)+n+co].is_zero()) { + throw (std::runtime_error("matrix::solve(): inconsistent linear system")); + } + } else { + // assign solutions for vars between fnz+1 and + // last_assigned_sol-1: free parameters + for (unsigned c=fnz; ccols(); + if (n==1) + return m[0].expand(); + if (n==2) + return (m[0]*m[3]-m[2]*m[1]).expand(); + if (n==3) + return (m[0]*m[4]*m[8]-m[0]*m[5]*m[7]- + m[1]*m[3]*m[8]+m[2]*m[3]*m[7]+ + m[1]*m[5]*m[6]-m[2]*m[4]*m[6]).expand(); + + // This algorithm can best be understood by looking at a naive + // implementation of Laplace-expansion, like this one: + // ex det; + // matrix minorM(this->rows()-1,this->cols()-1); + // for (unsigned r1=0; r1rows(); ++r1) { + // // shortcut if element(r1,0) vanishes + // if (m[r1*col].is_zero()) + // continue; + // // assemble the minor matrix + // for (unsigned r=0; r Pkey; + Pkey.reserve(n); + // key for minor determinant (a subpartition of Pkey) + std::vector Mkey; + Mkey.reserve(n-1); + // we store our subminors in maps, keys being the rows they arise from + typedef std::map,class ex> Rmap; + typedef std::map,class ex>::value_type Rmap_value; + Rmap A; + Rmap B; + ex det; + // initialize A with last column: + for (unsigned r=0; r=0; --c) { + Pkey.erase(Pkey.begin(),Pkey.end()); // don't change capacity + Mkey.erase(Mkey.begin(),Mkey.end()); + for (unsigned i=0; i0; --fc) { + ++Pkey[fc-1]; + if (Pkey[fc-1]0) + for (unsigned j=fc; jrows(); + const unsigned n = this->cols(); + GINAC_ASSERT(!det || n==m); + int sign = 1; + + unsigned r0 = 0; + for (unsigned c0=0; c0=0) { + if (indx > 0) + sign = -sign; + for (unsigned r2=r0+1; r2m[r2*n+c0].is_zero()) { + // yes, there is something to do in this row + ex piv = this->m[r2*n+c0] / this->m[r0*n+c0]; + for (unsigned c=c0+1; cm[r2*n+c] -= piv * this->m[r0*n+c]; + if (!this->m[r2*n+c].info(info_flags::numeric)) + this->m[r2*n+c] = this->m[r2*n+c].normal(); + } + } + // fill up left hand side with zeros + for (unsigned c=r0; c<=c0; ++c) + this->m[r2*n+c] = _ex0; + } + if (det) { + // save space by deleting no longer needed elements + for (unsigned c=r0+1; cm[r0*n+c] = _ex0; + } + ++r0; + } + } + // clear remaining rows + for (unsigned r=r0+1; rm[r*n+c] = _ex0; + } + + return sign; +} + + +/** Perform the steps of division free elimination to bring the m x n matrix + * into an upper echelon form. + * + * @param det may be set to true to save a lot of space if one is only + * interested in the diagonal elements (i.e. for calculating determinants). + * The others are set to zero in this case. + * @return sign is 1 if an even number of rows was swapped, -1 if an odd + * number of rows was swapped and 0 if the matrix is singular. */ +int matrix::division_free_elimination(const bool det) +{ + ensure_if_modifiable(); + const unsigned m = this->rows(); + const unsigned n = this->cols(); + GINAC_ASSERT(!det || n==m); + int sign = 1; + + unsigned r0 = 0; + for (unsigned c0=0; c0=0) { + if (indx>0) + sign = -sign; + for (unsigned r2=r0+1; r2m[r2*n+c] = (this->m[r0*n+c0]*this->m[r2*n+c] - this->m[r2*n+c0]*this->m[r0*n+c]).expand(); + // fill up left hand side with zeros + for (unsigned c=r0; c<=c0; ++c) + this->m[r2*n+c] = _ex0; + } + if (det) { + // save space by deleting no longer needed elements + for (unsigned c=r0+1; cm[r0*n+c] = _ex0; + } + ++r0; + } + } + // clear remaining rows + for (unsigned r=r0+1; rm[r*n+c] = _ex0; + } + + return sign; +} + + +/** Perform the steps of Bareiss' one-step fraction free elimination to bring + * the matrix into an upper echelon form. Fraction free elimination means + * that divide is used straightforwardly, without computing GCDs first. This + * is possible, since we know the divisor at each step. + * + * @param det may be set to true to save a lot of space if one is only + * interested in the last element (i.e. for calculating determinants). The + * others are set to zero in this case. + * @return sign is 1 if an even number of rows was swapped, -1 if an odd + * number of rows was swapped and 0 if the matrix is singular. */ +int matrix::fraction_free_elimination(const bool det) +{ + // Method: + // (single-step fraction free elimination scheme, already known to Jordan) + // + // Usual division-free elimination sets m[0](r,c) = m(r,c) and then sets + // m[k+1](r,c) = m[k](k,k) * m[k](r,c) - m[k](r,k) * m[k](k,c). + // + // Bareiss (fraction-free) elimination in addition divides that element + // by m[k-1](k-1,k-1) for k>1, where it can be shown by means of the + // Sylvester identity that this really divides m[k+1](r,c). + // + // We also allow rational functions where the original prove still holds. + // However, we must care for numerator and denominator separately and + // "manually" work in the integral domains because of subtle cancellations + // (see below). This blows up the bookkeeping a bit and the formula has + // to be modified to expand like this (N{x} stands for numerator of x, + // D{x} for denominator of x): + // N{m[k+1](r,c)} = N{m[k](k,k)}*N{m[k](r,c)}*D{m[k](r,k)}*D{m[k](k,c)} + // -N{m[k](r,k)}*N{m[k](k,c)}*D{m[k](k,k)}*D{m[k](r,c)} + // D{m[k+1](r,c)} = D{m[k](k,k)}*D{m[k](r,c)}*D{m[k](r,k)}*D{m[k](k,c)} + // where for k>1 we now divide N{m[k+1](r,c)} by + // N{m[k-1](k-1,k-1)} + // and D{m[k+1](r,c)} by + // D{m[k-1](k-1,k-1)}. + + ensure_if_modifiable(); + const unsigned m = this->rows(); + const unsigned n = this->cols(); + GINAC_ASSERT(!det || n==m); + int sign = 1; + if (m==1) + return 1; + ex divisor_n = 1; + ex divisor_d = 1; + ex dividend_n; + ex dividend_d; + + // We populate temporary matrices to subsequently operate on. There is + // one holding numerators and another holding denominators of entries. + // This is a must since the evaluator (or even earlier mul's constructor) + // might cancel some trivial element which causes divide() to fail. The + // elements are normalized first (yes, even though this algorithm doesn't + // need GCDs) since the elements of *this might be unnormalized, which + // makes things more complicated than they need to be. + matrix tmp_n(*this); + matrix tmp_d(m,n); // for denominators, if needed + exmap srl; // symbol replacement list + exvector::const_iterator cit = this->m.begin(), citend = this->m.end(); + exvector::iterator tmp_n_it = tmp_n.m.begin(), tmp_d_it = tmp_d.m.begin(); + while (cit != citend) { + ex nd = cit->normal().to_rational(srl).numer_denom(); + ++cit; + *tmp_n_it++ = nd.op(0); + *tmp_d_it++ = nd.op(1); + } + + unsigned r0 = 0; + for (unsigned c0=0; c0=0) { + if (indx>0) { + sign = -sign; + // tmp_n's rows r0 and indx were swapped, do the same in tmp_d: + for (unsigned c=c0; cm.begin(), itend = this->m.end(); + tmp_n_it = tmp_n.m.begin(); + tmp_d_it = tmp_d.m.begin(); + while (it != itend) + *it++ = ((*tmp_n_it++)/(*tmp_d_it++)).subs(srl, subs_options::no_pattern); + + return sign; +} + + +/** Partial pivoting method for matrix elimination schemes. + * Usual pivoting (symbolic==false) returns the index to the element with the + * largest absolute value in column ro and swaps the current row with the one + * where the element was found. With (symbolic==true) it does the same thing + * with the first non-zero element. + * + * @param ro is the row from where to begin + * @param co is the column to be inspected + * @param symbolic signal if we want the first non-zero element to be pivoted + * (true) or the one with the largest absolute value (false). + * @return 0 if no interchange occured, -1 if all are zero (usually signaling + * a degeneracy) and positive integer k means that rows ro and k were swapped. + */ +int matrix::pivot(unsigned ro, unsigned co, bool symbolic) +{ + unsigned k = ro; + if (symbolic) { + // search first non-zero element in column co beginning at row ro + while ((km[k*col+co].expand().is_zero())) + ++k; + } else { + // search largest element in column co beginning at row ro + GINAC_ASSERT(is_exactly_a(this->m[k*col+co])); + unsigned kmax = k+1; + numeric mmax = abs(ex_to(m[kmax*col+co])); + while (kmax(this->m[kmax*col+co])); + numeric tmp = ex_to(this->m[kmax*col+co]); + if (abs(tmp) > mmax) { + mmax = tmp; + k = kmax; + } + ++kmax; + } + if (!mmax.is_zero()) + k = kmax; + } + if (k==row) + // all elements in column co below row ro vanish + return -1; + if (k==ro) + // matrix needs no pivoting + return 0; + // matrix needs pivoting, so swap rows k and ro + ensure_if_modifiable(); + for (unsigned c=0; cm[k*col+c].swap(this->m[ro*col+c]); + + return k; +} + +ex lst_to_matrix(const lst & l) +{ + lst::const_iterator itr, itc; + + // Find number of rows and columns + size_t rows = l.nops(), cols = 0; + for (itr = l.begin(); itr != l.end(); ++itr) { + if (!is_a(*itr)) + throw (std::invalid_argument("lst_to_matrix: argument must be a list of lists")); + if (itr->nops() > cols) + cols = itr->nops(); + } + + // Allocate and fill matrix + matrix &M = *new matrix(rows, cols); + M.setflag(status_flags::dynallocated); + + unsigned i; + for (itr = l.begin(), i = 0; itr != l.end(); ++itr, ++i) { + unsigned j; + for (itc = ex_to(*itr).begin(), j = 0; itc != ex_to(*itr).end(); ++itc, ++j) + M(i, j) = *itc; + } + + return M; +} + +ex diag_matrix(const lst & l) +{ + lst::const_iterator it; + size_t dim = l.nops(); + + // Allocate and fill matrix + matrix &M = *new matrix(dim, dim); + M.setflag(status_flags::dynallocated); + + unsigned i; + for (it = l.begin(), i = 0; it != l.end(); ++it, ++i) + M(i, i) = *it; + + return M; +} + +ex unit_matrix(unsigned r, unsigned c) +{ + matrix &Id = *new matrix(r, c); + Id.setflag(status_flags::dynallocated); + for (unsigned i=0; i 10 || c > 10); + bool single_row = (r == 1 || c == 1); + + for (unsigned i=0; im.rows() || c+1>m.cols() || m.cols()<2 || m.rows()<2) + throw std::runtime_error("minor_matrix(): index out of bounds"); + + const unsigned rows = m.rows()-1; + const unsigned cols = m.cols()-1; + matrix &M = *new matrix(rows, cols); + M.setflag(status_flags::dynallocated | status_flags::evaluated); + + unsigned ro = 0; + unsigned ro2 = 0; + while (ro2m.rows() || c+nc>m.cols()) + throw std::runtime_error("sub_matrix(): index out of bounds"); + + matrix &M = *new matrix(nr, nc); + M.setflag(status_flags::dynallocated | status_flags::evaluated); + + for (unsigned ro=0; ro #include #include #include "normal.h" + #include "basic.h" #include "ex.h" #include "add.h" #include "constant.h" #include "expairseq.h" #include "fail.h" -#include "indexed.h" #include "inifcns.h" #include "lst.h" #include "mul.h" -#include "ncmul.h" #include "numeric.h" #include "power.h" #include "relational.h" -#include "series.h" +#include "operators.h" +#include "matrix.h" +#include "pseries.h" #include "symbol.h" +#include "utils.h" namespace GiNaC { @@ -54,31 +54,58 @@ namespace GiNaC { #define FAST_COMPARE 1 // Set this if you want divide_in_z() to use remembering -#define USE_REMEMBER 1 +#define USE_REMEMBER 0 + +// Set this if you want divide_in_z() to use trial division followed by +// polynomial interpolation (always slower except for completely dense +// polynomials) +#define USE_TRIAL_DIVISION 0 + +// Set this to enable some statistical output for the GCD routines +#define STATISTICS 0 + + +#if STATISTICS +// Statistics variables +static int gcd_called = 0; +static int sr_gcd_called = 0; +static int heur_gcd_called = 0; +static int heur_gcd_failed = 0; + +// Print statistics at end of program +static struct _stat_print { + _stat_print() {} + ~_stat_print() { + std::cout << "gcd() called " << gcd_called << " times\n"; + std::cout << "sr_gcd() called " << sr_gcd_called << " times\n"; + std::cout << "heur_gcd() called " << heur_gcd_called << " times\n"; + std::cout << "heur_gcd() failed " << heur_gcd_failed << " times\n"; + } +} stat_print; +#endif -/** Return pointer to first symbol found in expression. Due to GiNaC´s +/** Return pointer to first symbol found in expression. Due to GiNaC's * internal ordering of terms, it may not be obvious which symbol this * function returns for a given expression. * * @param e expression to search - * @param x pointer to first symbol found (returned) + * @param x first symbol found (returned) * @return "false" if no symbol was found, "true" otherwise */ - -static bool get_first_symbol(const ex &e, const symbol *&x) +static bool get_first_symbol(const ex &e, ex &x) { - if (is_ex_exactly_of_type(e, symbol)) { - x = static_cast(e.bp); - return true; - } else if (is_ex_exactly_of_type(e, add) || is_ex_exactly_of_type(e, mul)) { - for (int i=0; i(e)) { + x = e; + return true; + } else if (is_exactly_a(e) || is_exactly_a(e)) { + for (size_t i=0; i(e)) { + if (get_first_symbol(e.op(0), x)) + return true; + } + return false; } @@ -93,56 +120,65 @@ static bool get_first_symbol(const ex &e, const symbol *&x) * * @see get_symbol_stats */ struct sym_desc { - /** Pointer to symbol */ - const symbol *sym; + /** Reference to symbol */ + ex sym; - /** Highest degree of symbol in polynomial "a" */ - int deg_a; + /** Highest degree of symbol in polynomial "a" */ + int deg_a; - /** Highest degree of symbol in polynomial "b" */ - int deg_b; + /** Highest degree of symbol in polynomial "b" */ + int deg_b; - /** Lowest degree of symbol in polynomial "a" */ - int ldeg_a; + /** Lowest degree of symbol in polynomial "a" */ + int ldeg_a; - /** Lowest degree of symbol in polynomial "b" */ - int ldeg_b; + /** Lowest degree of symbol in polynomial "b" */ + int ldeg_b; - /** Minimum of ldeg_a and ldeg_b (Used for sorting) */ - int min_deg; + /** Maximum of deg_a and deg_b (Used for sorting) */ + int max_deg; - /** Commparison operator for sorting */ - bool operator<(const sym_desc &x) const {return min_deg < x.min_deg;} + /** Maximum number of terms of leading coefficient of symbol in both polynomials */ + size_t max_lcnops; + + /** Commparison operator for sorting */ + bool operator<(const sym_desc &x) const + { + if (max_deg == x.max_deg) + return max_lcnops < x.max_lcnops; + else + return max_deg < x.max_deg; + } }; // Vector of sym_desc structures -typedef vector sym_desc_vec; +typedef std::vector sym_desc_vec; // Add symbol the sym_desc_vec (used internally by get_symbol_stats()) -static void add_symbol(const symbol *s, sym_desc_vec &v) +static void add_symbol(const ex &s, sym_desc_vec &v) { - sym_desc_vec::iterator it = v.begin(), itend = v.end(); - while (it != itend) { - if (it->sym->compare(*s) == 0) // If it's already in there, don't add it a second time - return; - it++; - } - sym_desc d; - d.sym = s; - v.push_back(d); + sym_desc_vec::const_iterator it = v.begin(), itend = v.end(); + while (it != itend) { + if (it->sym.is_equal(s)) // If it's already in there, don't add it a second time + return; + ++it; + } + sym_desc d; + d.sym = s; + v.push_back(d); } // Collect all symbols of an expression (used internally by get_symbol_stats()) static void collect_symbols(const ex &e, sym_desc_vec &v) { - if (is_ex_exactly_of_type(e, symbol)) { - add_symbol(static_cast(e.bp), v); - } else if (is_ex_exactly_of_type(e, add) || is_ex_exactly_of_type(e, mul)) { - for (int i=0; i(e)) { + add_symbol(e, v); + } else if (is_exactly_a(e) || is_exactly_a(e)) { + for (size_t i=0; i(e)) { + collect_symbols(e.op(0), v); + } } /** Collect statistical information about symbols in polynomials. @@ -157,23 +193,33 @@ static void collect_symbols(const ex &e, sym_desc_vec &v) * @param a first multivariate polynomial * @param b second multivariate polynomial * @param v vector of sym_desc structs (filled in) */ - static void get_symbol_stats(const ex &a, const ex &b, sym_desc_vec &v) { - collect_symbols(a.eval(), v); // eval() to expand assigned symbols - collect_symbols(b.eval(), v); - sym_desc_vec::iterator it = v.begin(), itend = v.end(); - while (it != itend) { - int deg_a = a.degree(*(it->sym)); - int deg_b = b.degree(*(it->sym)); - it->deg_a = deg_a; - it->deg_b = deg_b; - it->min_deg = min(deg_a, deg_b); - it->ldeg_a = a.ldegree(*(it->sym)); - it->ldeg_b = b.ldegree(*(it->sym)); - it++; - } - sort(v.begin(), v.end()); + collect_symbols(a.eval(), v); // eval() to expand assigned symbols + collect_symbols(b.eval(), v); + sym_desc_vec::iterator it = v.begin(), itend = v.end(); + while (it != itend) { + int deg_a = a.degree(it->sym); + int deg_b = b.degree(it->sym); + it->deg_a = deg_a; + it->deg_b = deg_b; + it->max_deg = std::max(deg_a, deg_b); + it->max_lcnops = std::max(a.lcoeff(it->sym).nops(), b.lcoeff(it->sym).nops()); + it->ldeg_a = a.ldegree(it->sym); + it->ldeg_b = b.ldegree(it->sym); + ++it; + } + std::sort(v.begin(), v.end()); + +#if 0 + std::clog << "Symbols:\n"; + it = v.begin(); itend = v.end(); + while (it != itend) { + std::clog << " " << it->sym << ": deg_a=" << it->deg_a << ", deg_b=" << it->deg_b << ", ldeg_a=" << it->ldeg_a << ", ldeg_b=" << it->ldeg_b << ", max_deg=" << it->max_deg << ", max_lcnops=" << it->max_lcnops << endl; + std::clog << " lcoeff_a=" << a.lcoeff(it->sym) << ", lcoeff_b=" << b.lcoeff(it->sym) << endl; + ++it; + } +#endif } @@ -185,83 +231,124 @@ static void get_symbol_stats(const ex &a, const ex &b, sym_desc_vec &v) // expression recursively (used internally by lcm_of_coefficients_denominators()) static numeric lcmcoeff(const ex &e, const numeric &l) { - if (e.info(info_flags::rational)) - return lcm(ex_to_numeric(e).denom(), l); - else if (is_ex_exactly_of_type(e, add) || is_ex_exactly_of_type(e, mul)) { - numeric c = numONE(); - for (int i=0; i(e).denom(), l); + else if (is_exactly_a(e)) { + numeric c = *_num1_p; + for (size_t i=0; i(e)) { + numeric c = *_num1_p; + for (size_t i=0; i(e)) { + if (is_a(e.op(0))) + return l; + else + return pow(lcmcoeff(e.op(0), l), ex_to(e.op(1))); + } + return l; } /** Compute LCM of denominators of coefficients of a polynomial. * Given a polynomial with rational coefficients, this function computes * the LCM of the denominators of all coefficients. This can be used - * To bring a polynomial from Q[X] to Z[X]. + * to bring a polynomial from Q[X] to Z[X]. * - * @param e multivariate polynomial + * @param e multivariate polynomial (need not be expanded) * @return LCM of denominators of coefficients */ - static numeric lcm_of_coefficients_denominators(const ex &e) { - return lcmcoeff(e.expand(), numONE()); + return lcmcoeff(e, *_num1_p); +} + +/** Bring polynomial from Q[X] to Z[X] by multiplying in the previously + * determined LCM of the coefficient's denominators. + * + * @param e multivariate polynomial (need not be expanded) + * @param lcm LCM to multiply in */ +static ex multiply_lcm(const ex &e, const numeric &lcm) +{ + if (is_exactly_a(e)) { + size_t num = e.nops(); + exvector v; v.reserve(num + 1); + numeric lcm_accum = *_num1_p; + for (size_t i=0; isetflag(status_flags::dynallocated); + } else if (is_exactly_a(e)) { + size_t num = e.nops(); + exvector v; v.reserve(num); + for (size_t i=0; isetflag(status_flags::dynallocated); + } else if (is_exactly_a(e)) { + if (is_a(e.op(0))) + return e * lcm; + else + return pow(multiply_lcm(e.op(0), lcm.power(ex_to(e.op(1)).inverse())), e.op(1)); + } else + return e * lcm; } /** Compute the integer content (= GCD of all numeric coefficients) of an - * expanded polynomial. + * expanded polynomial. For a polynomial with rational coefficients, this + * returns g/l where g is the GCD of the coefficients' numerators and l + * is the LCM of the coefficients' denominators. * - * @param e expanded polynomial * @return integer content */ - -numeric ex::integer_content(void) const +numeric ex::integer_content() const { - GINAC_ASSERT(bp!=0); - return bp->integer_content(); + return bp->integer_content(); } -numeric basic::integer_content(void) const +numeric basic::integer_content() const { - return numONE(); + return *_num1_p; } -numeric numeric::integer_content(void) const +numeric numeric::integer_content() const { - return abs(*this); + return abs(*this); } -numeric add::integer_content(void) const +numeric add::integer_content() const { - epvector::const_iterator it = seq.begin(); - epvector::const_iterator itend = seq.end(); - numeric c = numZERO(); - while (it != itend) { - GINAC_ASSERT(!is_ex_exactly_of_type(it->rest,numeric)); - GINAC_ASSERT(is_ex_exactly_of_type(it->coeff,numeric)); - c = gcd(ex_to_numeric(it->coeff), c); - it++; - } - GINAC_ASSERT(is_ex_exactly_of_type(overall_coeff,numeric)); - c = gcd(ex_to_numeric(overall_coeff),c); - return c; + epvector::const_iterator it = seq.begin(); + epvector::const_iterator itend = seq.end(); + numeric c = *_num0_p, l = *_num1_p; + while (it != itend) { + GINAC_ASSERT(!is_exactly_a(it->rest)); + GINAC_ASSERT(is_exactly_a(it->coeff)); + c = gcd(ex_to(it->coeff).numer(), c); + l = lcm(ex_to(it->coeff).denom(), l); + it++; + } + GINAC_ASSERT(is_exactly_a(overall_coeff)); + c = gcd(ex_to(overall_coeff).numer(), c); + l = lcm(ex_to(overall_coeff).denom(), l); + return c/l; } -numeric mul::integer_content(void) const +numeric mul::integer_content() const { #ifdef DO_GINAC_ASSERT - epvector::const_iterator it = seq.begin(); - epvector::const_iterator itend = seq.end(); - while (it != itend) { - GINAC_ASSERT(!is_ex_exactly_of_type(recombine_pair_to_ex(*it),numeric)); - ++it; - } + epvector::const_iterator it = seq.begin(); + epvector::const_iterator itend = seq.end(); + while (it != itend) { + GINAC_ASSERT(!is_exactly_a(recombine_pair_to_ex(*it))); + ++it; + } #endif // def DO_GINAC_ASSERT - GINAC_ASSERT(is_ex_exactly_of_type(overall_coeff,numeric)); - return abs(ex_to_numeric(overall_coeff)); + GINAC_ASSERT(is_exactly_a(overall_coeff)); + return abs(ex_to(overall_coeff)); } @@ -278,45 +365,44 @@ numeric mul::integer_content(void) const * @param check_args check whether a and b are polynomials with rational * coefficients (defaults to "true") * @return quotient of a and b in Q[x] */ - -ex quo(const ex &a, const ex &b, const symbol &x, bool check_args) +ex quo(const ex &a, const ex &b, const ex &x, bool check_args) { - if (b.is_zero()) - throw(std::overflow_error("quo: division by zero")); - if (is_ex_exactly_of_type(a, numeric) && is_ex_exactly_of_type(b, numeric)) - return a / b; + if (b.is_zero()) + throw(std::overflow_error("quo: division by zero")); + if (is_exactly_a(a) && is_exactly_a(b)) + return a / b; #if FAST_COMPARE - if (a.is_equal(b)) - return exONE(); + if (a.is_equal(b)) + return _ex1; #endif - if (check_args && (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial))) - throw(std::invalid_argument("quo: arguments must be polynomials over the rationals")); - - // Polynomial long division - ex q = exZERO(); - ex r = a.expand(); - if (r.is_zero()) - return r; - int bdeg = b.degree(x); - int rdeg = r.degree(x); - ex blcoeff = b.expand().coeff(x, bdeg); - bool blcoeff_is_numeric = is_ex_exactly_of_type(blcoeff, numeric); - while (rdeg >= bdeg) { - ex term, rcoeff = r.coeff(x, rdeg); - if (blcoeff_is_numeric) - term = rcoeff / blcoeff; - else { - if (!divide(rcoeff, blcoeff, term, false)) - return *new ex(fail()); - } - term *= power(x, rdeg - bdeg); - q += term; - r -= (term * b).expand(); - if (r.is_zero()) - break; - rdeg = r.degree(x); - } - return q; + if (check_args && (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial))) + throw(std::invalid_argument("quo: arguments must be polynomials over the rationals")); + + // Polynomial long division + ex r = a.expand(); + if (r.is_zero()) + return r; + int bdeg = b.degree(x); + int rdeg = r.degree(x); + ex blcoeff = b.expand().coeff(x, bdeg); + bool blcoeff_is_numeric = is_exactly_a(blcoeff); + exvector v; v.reserve(std::max(rdeg - bdeg + 1, 0)); + while (rdeg >= bdeg) { + ex term, rcoeff = r.coeff(x, rdeg); + if (blcoeff_is_numeric) + term = rcoeff / blcoeff; + else { + if (!divide(rcoeff, blcoeff, term, false)) + return (new fail())->setflag(status_flags::dynallocated); + } + term *= power(x, rdeg - bdeg); + v.push_back(term); + r -= (term * b).expand(); + if (r.is_zero()) + break; + rdeg = r.degree(x); + } + return (new add(v))->setflag(status_flags::dynallocated); } @@ -329,100 +415,166 @@ ex quo(const ex &a, const ex &b, const symbol &x, bool check_args) * @param check_args check whether a and b are polynomials with rational * coefficients (defaults to "true") * @return remainder of a(x) and b(x) in Q[x] */ - -ex rem(const ex &a, const ex &b, const symbol &x, bool check_args) +ex rem(const ex &a, const ex &b, const ex &x, bool check_args) { - if (b.is_zero()) - throw(std::overflow_error("rem: division by zero")); - if (is_ex_exactly_of_type(a, numeric)) { - if (is_ex_exactly_of_type(b, numeric)) - return exZERO(); - else - return b; - } + if (b.is_zero()) + throw(std::overflow_error("rem: division by zero")); + if (is_exactly_a(a)) { + if (is_exactly_a(b)) + return _ex0; + else + return a; + } #if FAST_COMPARE - if (a.is_equal(b)) - return exZERO(); + if (a.is_equal(b)) + return _ex0; #endif - if (check_args && (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial))) - throw(std::invalid_argument("rem: arguments must be polynomials over the rationals")); - - // Polynomial long division - ex r = a.expand(); - if (r.is_zero()) - return r; - int bdeg = b.degree(x); - int rdeg = r.degree(x); - ex blcoeff = b.expand().coeff(x, bdeg); - bool blcoeff_is_numeric = is_ex_exactly_of_type(blcoeff, numeric); - while (rdeg >= bdeg) { - ex term, rcoeff = r.coeff(x, rdeg); - if (blcoeff_is_numeric) - term = rcoeff / blcoeff; - else { - if (!divide(rcoeff, blcoeff, term, false)) - return *new ex(fail()); - } - term *= power(x, rdeg - bdeg); - r -= (term * b).expand(); - if (r.is_zero()) - break; - rdeg = r.degree(x); - } - return r; -} - - -/** Pseudo-remainder of polynomials a(x) and b(x) in Z[x]. + if (check_args && (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial))) + throw(std::invalid_argument("rem: arguments must be polynomials over the rationals")); + + // Polynomial long division + ex r = a.expand(); + if (r.is_zero()) + return r; + int bdeg = b.degree(x); + int rdeg = r.degree(x); + ex blcoeff = b.expand().coeff(x, bdeg); + bool blcoeff_is_numeric = is_exactly_a(blcoeff); + while (rdeg >= bdeg) { + ex term, rcoeff = r.coeff(x, rdeg); + if (blcoeff_is_numeric) + term = rcoeff / blcoeff; + else { + if (!divide(rcoeff, blcoeff, term, false)) + return (new fail())->setflag(status_flags::dynallocated); + } + term *= power(x, rdeg - bdeg); + r -= (term * b).expand(); + if (r.is_zero()) + break; + rdeg = r.degree(x); + } + return r; +} + + +/** Decompose rational function a(x)=N(x)/D(x) into P(x)+n(x)/D(x) + * with degree(n, x) < degree(D, x). + * + * @param a rational function in x + * @param x a is a function of x + * @return decomposed function. */ +ex decomp_rational(const ex &a, const ex &x) +{ + ex nd = numer_denom(a); + ex numer = nd.op(0), denom = nd.op(1); + ex q = quo(numer, denom, x); + if (is_exactly_a(q)) + return a; + else + return q + rem(numer, denom, x) / denom; +} + + +/** Pseudo-remainder of polynomials a(x) and b(x) in Q[x]. + * + * @param a first polynomial in x (dividend) + * @param b second polynomial in x (divisor) + * @param x a and b are polynomials in x + * @param check_args check whether a and b are polynomials with rational + * coefficients (defaults to "true") + * @return pseudo-remainder of a(x) and b(x) in Q[x] */ +ex prem(const ex &a, const ex &b, const ex &x, bool check_args) +{ + if (b.is_zero()) + throw(std::overflow_error("prem: division by zero")); + if (is_exactly_a(a)) { + if (is_exactly_a(b)) + return _ex0; + else + return b; + } + if (check_args && (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial))) + throw(std::invalid_argument("prem: arguments must be polynomials over the rationals")); + + // Polynomial long division + ex r = a.expand(); + ex eb = b.expand(); + int rdeg = r.degree(x); + int bdeg = eb.degree(x); + ex blcoeff; + if (bdeg <= rdeg) { + blcoeff = eb.coeff(x, bdeg); + if (bdeg == 0) + eb = _ex0; + else + eb -= blcoeff * power(x, bdeg); + } else + blcoeff = _ex1; + + int delta = rdeg - bdeg + 1, i = 0; + while (rdeg >= bdeg && !r.is_zero()) { + ex rlcoeff = r.coeff(x, rdeg); + ex term = (power(x, rdeg - bdeg) * eb * rlcoeff).expand(); + if (rdeg == 0) + r = _ex0; + else + r -= rlcoeff * power(x, rdeg); + r = (blcoeff * r).expand() - term; + rdeg = r.degree(x); + i++; + } + return power(blcoeff, delta - i) * r; +} + + +/** Sparse pseudo-remainder of polynomials a(x) and b(x) in Q[x]. * * @param a first polynomial in x (dividend) * @param b second polynomial in x (divisor) * @param x a and b are polynomials in x * @param check_args check whether a and b are polynomials with rational * coefficients (defaults to "true") - * @return pseudo-remainder of a(x) and b(x) in Z[x] */ - -ex prem(const ex &a, const ex &b, const symbol &x, bool check_args) -{ - if (b.is_zero()) - throw(std::overflow_error("prem: division by zero")); - if (is_ex_exactly_of_type(a, numeric)) { - if (is_ex_exactly_of_type(b, numeric)) - return exZERO(); - else - return b; - } - if (check_args && (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial))) - throw(std::invalid_argument("prem: arguments must be polynomials over the rationals")); - - // Polynomial long division - ex r = a.expand(); - ex eb = b.expand(); - int rdeg = r.degree(x); - int bdeg = eb.degree(x); - ex blcoeff; - if (bdeg <= rdeg) { - blcoeff = eb.coeff(x, bdeg); - if (bdeg == 0) - eb = exZERO(); - else - eb -= blcoeff * power(x, bdeg); - } else - blcoeff = exONE(); - - int delta = rdeg - bdeg + 1, i = 0; - while (rdeg >= bdeg && !r.is_zero()) { - ex rlcoeff = r.coeff(x, rdeg); - ex term = (power(x, rdeg - bdeg) * eb * rlcoeff).expand(); - if (rdeg == 0) - r = exZERO(); - else - r -= rlcoeff * power(x, rdeg); - r = (blcoeff * r).expand() - term; - rdeg = r.degree(x); - i++; - } - return power(blcoeff, delta - i) * r; + * @return sparse pseudo-remainder of a(x) and b(x) in Q[x] */ +ex sprem(const ex &a, const ex &b, const ex &x, bool check_args) +{ + if (b.is_zero()) + throw(std::overflow_error("prem: division by zero")); + if (is_exactly_a(a)) { + if (is_exactly_a(b)) + return _ex0; + else + return b; + } + if (check_args && (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial))) + throw(std::invalid_argument("prem: arguments must be polynomials over the rationals")); + + // Polynomial long division + ex r = a.expand(); + ex eb = b.expand(); + int rdeg = r.degree(x); + int bdeg = eb.degree(x); + ex blcoeff; + if (bdeg <= rdeg) { + blcoeff = eb.coeff(x, bdeg); + if (bdeg == 0) + eb = _ex0; + else + eb -= blcoeff * power(x, bdeg); + } else + blcoeff = _ex1; + + while (rdeg >= bdeg && !r.is_zero()) { + ex rlcoeff = r.coeff(x, rdeg); + ex term = (power(x, rdeg - bdeg) * eb * rlcoeff).expand(); + if (rdeg == 0) + r = _ex0; + else + r -= rlcoeff * power(x, rdeg); + r = (blcoeff * r).expand() - term; + rdeg = r.degree(x); + } + return r; } @@ -434,55 +586,63 @@ ex prem(const ex &a, const ex &b, const symbol &x, bool check_args) * @param check_args check whether a and b are polynomials with rational * coefficients (defaults to "true") * @return "true" when exact division succeeds (quotient returned in q), - * "false" otherwise */ - + * "false" otherwise (q left untouched) */ bool divide(const ex &a, const ex &b, ex &q, bool check_args) { - q = exZERO(); - if (b.is_zero()) - throw(std::overflow_error("divide: division by zero")); - if (is_ex_exactly_of_type(b, numeric)) { - q = a / b; - return true; - } else if (is_ex_exactly_of_type(a, numeric)) - return false; + if (b.is_zero()) + throw(std::overflow_error("divide: division by zero")); + if (a.is_zero()) { + q = _ex0; + return true; + } + if (is_exactly_a(b)) { + q = a / b; + return true; + } else if (is_exactly_a(a)) + return false; #if FAST_COMPARE - if (a.is_equal(b)) { - q = exONE(); - return true; - } + if (a.is_equal(b)) { + q = _ex1; + return true; + } #endif - if (check_args && (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial))) - throw(std::invalid_argument("divide: arguments must be polynomials over the rationals")); - - // Find first symbol - const symbol *x; - if (!get_first_symbol(a, x) && !get_first_symbol(b, x)) - throw(std::invalid_argument("invalid expression in divide()")); - - // Polynomial long division (recursive) - ex r = a.expand(); - if (r.is_zero()) - return true; - int bdeg = b.degree(*x); - int rdeg = r.degree(*x); - ex blcoeff = b.expand().coeff(*x, bdeg); - bool blcoeff_is_numeric = is_ex_exactly_of_type(blcoeff, numeric); - while (rdeg >= bdeg) { - ex term, rcoeff = r.coeff(*x, rdeg); - if (blcoeff_is_numeric) - term = rcoeff / blcoeff; - else - if (!divide(rcoeff, blcoeff, term, false)) - return false; - term *= power(*x, rdeg - bdeg); - q += term; - r -= (term * b).expand(); - if (r.is_zero()) - return true; - rdeg = r.degree(*x); - } - return false; + if (check_args && (!a.info(info_flags::rational_polynomial) || + !b.info(info_flags::rational_polynomial))) + throw(std::invalid_argument("divide: arguments must be polynomials over the rationals")); + + // Find first symbol + ex x; + if (!get_first_symbol(a, x) && !get_first_symbol(b, x)) + throw(std::invalid_argument("invalid expression in divide()")); + + // Polynomial long division (recursive) + ex r = a.expand(); + if (r.is_zero()) { + q = _ex0; + return true; + } + int bdeg = b.degree(x); + int rdeg = r.degree(x); + ex blcoeff = b.expand().coeff(x, bdeg); + bool blcoeff_is_numeric = is_exactly_a(blcoeff); + exvector v; v.reserve(std::max(rdeg - bdeg + 1, 0)); + while (rdeg >= bdeg) { + ex term, rcoeff = r.coeff(x, rdeg); + if (blcoeff_is_numeric) + term = rcoeff / blcoeff; + else + if (!divide(rcoeff, blcoeff, term, false)) + return false; + term *= power(x, rdeg - bdeg); + v.push_back(term); + r -= (term * b).expand(); + if (r.is_zero()) { + q = (new add(v))->setflag(status_flags::dynallocated); + return true; + } + rdeg = r.degree(x); + } + return false; } @@ -491,24 +651,25 @@ bool divide(const ex &a, const ex &b, ex &q, bool check_args) * Remembering */ -typedef pair ex2; -typedef pair exbool; +typedef std::pair ex2; +typedef std::pair exbool; struct ex2_less { - bool operator() (const ex2 p, const ex2 q) const - { - return p.first.compare(q.first) < 0 || (!(q.first.compare(p.first) < 0) && p.second.compare(q.second) < 0); - } + bool operator() (const ex2 &p, const ex2 &q) const + { + int cmp = p.first.compare(q.first); + return ((cmp<0) || (!(cmp>0) && p.second.compare(q.second)<0)); + } }; -typedef map ex2_exbool_remember; +typedef std::map ex2_exbool_remember; #endif /** Exact polynomial division of a(X) by b(X) in Z[X]. * This functions works like divide() but the input and output polynomials are * in Z[X] instead of Q[X] (i.e. they have integer coefficients). Unlike - * divide(), it doesn´t check whether the input polynomials really are integer + * divide(), it doesn't check whether the input polynomials really are integer * polynomials, so be careful of what you pass in. Also, you have to run * get_symbol_stats() over the input polynomials before calling this function * and pass an iterator to the first element of the sym_desc vector. This @@ -523,127 +684,155 @@ typedef map ex2_exbool_remember; * @see get_symbol_stats, heur_gcd */ static bool divide_in_z(const ex &a, const ex &b, ex &q, sym_desc_vec::const_iterator var) { - q = exZERO(); - if (b.is_zero()) - throw(std::overflow_error("divide_in_z: division by zero")); - if (b.is_equal(exONE())) { - q = a; - return true; - } - if (is_ex_exactly_of_type(a, numeric)) { - if (is_ex_exactly_of_type(b, numeric)) { - q = a / b; - return q.info(info_flags::integer); - } else - return false; - } + q = _ex0; + if (b.is_zero()) + throw(std::overflow_error("divide_in_z: division by zero")); + if (b.is_equal(_ex1)) { + q = a; + return true; + } + if (is_exactly_a(a)) { + if (is_exactly_a(b)) { + q = a / b; + return q.info(info_flags::integer); + } else + return false; + } #if FAST_COMPARE - if (a.is_equal(b)) { - q = exONE(); - return true; - } + if (a.is_equal(b)) { + q = _ex1; + return true; + } #endif #if USE_REMEMBER - // Remembering - static ex2_exbool_remember dr_remember; - ex2_exbool_remember::const_iterator remembered = dr_remember.find(ex2(a, b)); - if (remembered != dr_remember.end()) { - q = remembered->second.first; - return remembered->second.second; - } + // Remembering + static ex2_exbool_remember dr_remember; + ex2_exbool_remember::const_iterator remembered = dr_remember.find(ex2(a, b)); + if (remembered != dr_remember.end()) { + q = remembered->second.first; + return remembered->second.second; + } #endif - // Main symbol - const symbol *x = var->sym; - - // Compare degrees - int adeg = a.degree(*x), bdeg = b.degree(*x); - if (bdeg > adeg) - return false; - -#if 1 - - // Polynomial long division (recursive) - ex r = a.expand(); - if (r.is_zero()) - return true; - int rdeg = adeg; - ex eb = b.expand(); - ex blcoeff = eb.coeff(*x, bdeg); - while (rdeg >= bdeg) { - ex term, rcoeff = r.coeff(*x, rdeg); - if (!divide_in_z(rcoeff, blcoeff, term, var+1)) - break; - term = (term * power(*x, rdeg - bdeg)).expand(); - q += term; - r -= (term * eb).expand(); - if (r.is_zero()) { + if (is_exactly_a(b)) { + const ex& bb(b.op(0)); + ex qbar = a; + int exp_b = ex_to(b.op(1)).to_int(); + for (int i=exp_b; i>0; i--) { + if (!divide_in_z(qbar, bb, q, var)) + return false; + qbar = q; + } + return true; + } + + if (is_exactly_a(b)) { + ex qbar = a; + for (const_iterator itrb = b.begin(); itrb != b.end(); ++itrb) { + sym_desc_vec sym_stats; + get_symbol_stats(a, *itrb, sym_stats); + if (!divide_in_z(qbar, *itrb, q, sym_stats.begin())) + return false; + + qbar = q; + } + return true; + } + + // Main symbol + const ex &x = var->sym; + + // Compare degrees + int adeg = a.degree(x), bdeg = b.degree(x); + if (bdeg > adeg) + return false; + +#if USE_TRIAL_DIVISION + + // Trial division with polynomial interpolation + int i, k; + + // Compute values at evaluation points 0..adeg + vector alpha; alpha.reserve(adeg + 1); + exvector u; u.reserve(adeg + 1); + numeric point = *_num0_p; + ex c; + for (i=0; i<=adeg; i++) { + ex bs = b.subs(x == point, subs_options::no_pattern); + while (bs.is_zero()) { + point += *_num1_p; + bs = b.subs(x == point, subs_options::no_pattern); + } + if (!divide_in_z(a.subs(x == point, subs_options::no_pattern), bs, c, var+1)) + return false; + alpha.push_back(point); + u.push_back(c); + point += *_num1_p; + } + + // Compute inverses + vector rcp; rcp.reserve(adeg + 1); + rcp.push_back(*_num0_p); + for (k=1; k<=adeg; k++) { + numeric product = alpha[k] - alpha[0]; + for (i=1; i=0; i--) + temp = temp * (alpha[k] - alpha[i]) + v[i]; + v.push_back((u[k] - temp) * rcp[k]); + } + + // Convert from Newton form to standard form + c = v[adeg]; + for (k=adeg-1; k>=0; k--) + c = c * (x - alpha[k]) + v[k]; + + if (c.degree(x) == (adeg - bdeg)) { + q = c.expand(); + return true; + } else + return false; + +#else + + // Polynomial long division (recursive) + ex r = a.expand(); + if (r.is_zero()) + return true; + int rdeg = adeg; + ex eb = b.expand(); + ex blcoeff = eb.coeff(x, bdeg); + exvector v; v.reserve(std::max(rdeg - bdeg + 1, 0)); + while (rdeg >= bdeg) { + ex term, rcoeff = r.coeff(x, rdeg); + if (!divide_in_z(rcoeff, blcoeff, term, var+1)) + break; + term = (term * power(x, rdeg - bdeg)).expand(); + v.push_back(term); + r -= (term * eb).expand(); + if (r.is_zero()) { + q = (new add(v))->setflag(status_flags::dynallocated); #if USE_REMEMBER - dr_remember[ex2(a, b)] = exbool(q, true); + dr_remember[ex2(a, b)] = exbool(q, true); #endif - return true; - } - rdeg = r.degree(*x); - } + return true; + } + rdeg = r.degree(x); + } #if USE_REMEMBER - dr_remember[ex2(a, b)] = exbool(q, false); + dr_remember[ex2(a, b)] = exbool(q, false); #endif - return false; + return false; -#else - - // Trial division using polynomial interpolation - int i, k; - - // Compute values at evaluation points 0..adeg - vector alpha; alpha.reserve(adeg + 1); - exvector u; u.reserve(adeg + 1); - numeric point = numZERO(); - ex c; - for (i=0; i<=adeg; i++) { - ex bs = b.subs(*x == point); - while (bs.is_zero()) { - point += numONE(); - bs = b.subs(*x == point); - } - if (!divide_in_z(a.subs(*x == point), bs, c, var+1)) - return false; - alpha.push_back(point); - u.push_back(c); - point += numONE(); - } - - // Compute inverses - vector rcp; rcp.reserve(adeg + 1); - rcp.push_back(0); - for (k=1; k<=adeg; k++) { - numeric product = alpha[k] - alpha[0]; - for (i=1; i=0; i--) - temp = temp * (alpha[k] - alpha[i]) + v[i]; - v.push_back((u[k] - temp) * rcp[k]); - } - - // Convert from Newton form to standard form - c = v[adeg]; - for (k=adeg-1; k>=0; k--) - c = c * (*x - alpha[k]) + v[k]; - - if (c.degree(*x) == (adeg - bdeg)) { - q = c.expand(); - return true; - } else - return false; #endif } @@ -653,110 +842,154 @@ static bool divide_in_z(const ex &a, const ex &b, ex &q, sym_desc_vec::const_ite */ /** Compute unit part (= sign of leading coefficient) of a multivariate - * polynomial in Z[x]. The product of unit part, content part, and primitive + * polynomial in Q[x]. The product of unit part, content part, and primitive * part is the polynomial itself. * - * @param x variable in which to compute the unit part + * @param x main variable * @return unit part - * @see ex::content, ex::primpart */ -ex ex::unit(const symbol &x) const + * @see ex::content, ex::primpart, ex::unitcontprim */ +ex ex::unit(const ex &x) const { - ex c = expand().lcoeff(x); - if (is_ex_exactly_of_type(c, numeric)) - return c < exZERO() ? exMINUSONE() : exONE(); - else { - const symbol *y; - if (get_first_symbol(c, y)) - return c.unit(*y); - else - throw(std::invalid_argument("invalid expression in unit()")); - } + ex c = expand().lcoeff(x); + if (is_exactly_a(c)) + return c.info(info_flags::negative) ?_ex_1 : _ex1; + else { + ex y; + if (get_first_symbol(c, y)) + return c.unit(y); + else + throw(std::invalid_argument("invalid expression in unit()")); + } } /** Compute content part (= unit normal GCD of all coefficients) of a - * multivariate polynomial in Z[x]. The product of unit part, content part, + * multivariate polynomial in Q[x]. The product of unit part, content part, * and primitive part is the polynomial itself. * - * @param x variable in which to compute the content part + * @param x main variable * @return content part - * @see ex::unit, ex::primpart */ -ex ex::content(const symbol &x) const -{ - if (is_zero()) - return exZERO(); - if (is_ex_exactly_of_type(*this, numeric)) - return info(info_flags::negative) ? -*this : *this; - ex e = expand(); - if (e.is_zero()) - return exZERO(); - - // First, try the integer content - ex c = e.integer_content(); - ex r = e / c; - ex lcoeff = r.lcoeff(x); - if (lcoeff.info(info_flags::integer)) - return c; - - // GCD of all coefficients - int deg = e.degree(x); - int ldeg = e.ldegree(x); - if (deg == ldeg) - return e.lcoeff(x) / e.unit(x); - c = exZERO(); - for (int i=ldeg; i<=deg; i++) - c = gcd(e.coeff(x, i), c, NULL, NULL, false); - return c; -} - - -/** Compute primitive part of a multivariate polynomial in Z[x]. - * The product of unit part, content part, and primitive part is the - * polynomial itself. + * @see ex::unit, ex::primpart, ex::unitcontprim */ +ex ex::content(const ex &x) const +{ + if (is_exactly_a(*this)) + return info(info_flags::negative) ? -*this : *this; + + ex e = expand(); + if (e.is_zero()) + return _ex0; + + // First, divide out the integer content (which we can calculate very efficiently). + // If the leading coefficient of the quotient is an integer, we are done. + ex c = e.integer_content(); + ex r = e / c; + int deg = r.degree(x); + ex lcoeff = r.coeff(x, deg); + if (lcoeff.info(info_flags::integer)) + return c; + + // GCD of all coefficients + int ldeg = r.ldegree(x); + if (deg == ldeg) + return lcoeff * c / lcoeff.unit(x); + ex cont = _ex0; + for (int i=ldeg; i<=deg; i++) + cont = gcd(r.coeff(x, i), cont, NULL, NULL, false); + return cont * c; +} + + +/** Compute primitive part of a multivariate polynomial in Q[x]. The result + * will be a unit-normal polynomial with a content part of 1. The product + * of unit part, content part, and primitive part is the polynomial itself. * - * @param x variable in which to compute the primitive part + * @param x main variable * @return primitive part - * @see ex::unit, ex::content */ -ex ex::primpart(const symbol &x) const + * @see ex::unit, ex::content, ex::unitcontprim */ +ex ex::primpart(const ex &x) const { - if (is_zero()) - return exZERO(); - if (is_ex_exactly_of_type(*this, numeric)) - return exONE(); - - ex c = content(x); - if (c.is_zero()) - return exZERO(); - ex u = unit(x); - if (is_ex_exactly_of_type(c, numeric)) - return *this / (c * u); - else - return quo(*this, c * u, x, false); + // We need to compute the unit and content anyway, so call unitcontprim() + ex u, c, p; + unitcontprim(x, u, c, p); + return p; } -/** Compute primitive part of a multivariate polynomial in Z[x] when the +/** Compute primitive part of a multivariate polynomial in Q[x] when the * content part is already known. This function is faster in computing the * primitive part than the previous function. * - * @param x variable in which to compute the primitive part + * @param x main variable * @param c previously computed content part * @return primitive part */ +ex ex::primpart(const ex &x, const ex &c) const +{ + if (is_zero() || c.is_zero()) + return _ex0; + if (is_exactly_a(*this)) + return _ex1; + + // Divide by unit and content to get primitive part + ex u = unit(x); + if (is_exactly_a(c)) + return *this / (c * u); + else + return quo(*this, c * u, x, false); +} + -ex ex::primpart(const symbol &x, const ex &c) const +/** Compute unit part, content part, and primitive part of a multivariate + * polynomial in Q[x]. The product of the three parts is the polynomial + * itself. + * + * @param x main variable + * @param u unit part (returned) + * @param c content part (returned) + * @param p primitive part (returned) + * @see ex::unit, ex::content, ex::primpart */ +void ex::unitcontprim(const ex &x, ex &u, ex &c, ex &p) const { - if (is_zero()) - return exZERO(); - if (c.is_zero()) - return exZERO(); - if (is_ex_exactly_of_type(*this, numeric)) - return exONE(); + // Quick check for zero (avoid expanding) + if (is_zero()) { + u = _ex1; + c = p = _ex0; + return; + } + + // Special case: input is a number + if (is_exactly_a(*this)) { + if (info(info_flags::negative)) { + u = _ex_1; + c = abs(ex_to(*this)); + } else { + u = _ex1; + c = *this; + } + p = _ex1; + return; + } + + // Expand input polynomial + ex e = expand(); + if (e.is_zero()) { + u = _ex1; + c = p = _ex0; + return; + } + + // Compute unit and content + u = unit(x); + c = content(x); - ex u = unit(x); - if (is_ex_exactly_of_type(c, numeric)) - return *this / (c * u); - else - return quo(*this, c * u, x, false); + // Divide by unit and content to get primitive part + if (c.is_zero()) { + p = _ex0; + return; + } + if (is_exactly_a(c)) + p = *this / (c * u); + else + p = quo(e, c * u, x, false); } @@ -765,190 +998,204 @@ ex ex::primpart(const symbol &x, const ex &c) const */ /** Compute GCD of multivariate polynomials using the subresultant PRS - * algorithm. This function is used internally gy gcd(). + * algorithm. This function is used internally by gcd(). * - * @param a first multivariate polynomial - * @param b second multivariate polynomial - * @param x pointer to symbol (main variable) in which to compute the GCD in + * @param a first multivariate polynomial + * @param b second multivariate polynomial + * @param var iterator to first element of vector of sym_desc structs * @return the GCD as a new expression * @see gcd */ -static ex sr_gcd(const ex &a, const ex &b, const symbol *x) -{ - // Sort c and d so that c has higher degree - ex c, d; - int adeg = a.degree(*x), bdeg = b.degree(*x); - int cdeg, ddeg; - if (adeg >= bdeg) { - c = a; - d = b; - cdeg = adeg; - ddeg = bdeg; - } else { - c = b; - d = a; - cdeg = bdeg; - ddeg = adeg; - } - - // Remove content from c and d, to be attached to GCD later - ex cont_c = c.content(*x); - ex cont_d = d.content(*x); - ex gamma = gcd(cont_c, cont_d, NULL, NULL, false); - if (ddeg == 0) - return gamma; - c = c.primpart(*x, cont_c); - d = d.primpart(*x, cont_d); - - // First element of subresultant sequence - ex r = exZERO(), ri = exONE(), psi = exONE(); - int delta = cdeg - ddeg; - - for (;;) { - // Calculate polynomial pseudo-remainder - r = prem(c, d, *x, false); - if (r.is_zero()) - return gamma * d.primpart(*x); - c = d; - cdeg = ddeg; - if (!divide(r, ri * power(psi, delta), d, false)) - throw(std::runtime_error("invalid expression in sr_gcd(), division failed")); - ddeg = d.degree(*x); - if (ddeg == 0) { - if (is_ex_exactly_of_type(r, numeric)) - return gamma; - else - return gamma * r.primpart(*x); - } - - // Next element of subresultant sequence - ri = c.expand().lcoeff(*x); - if (delta == 1) - psi = ri; - else if (delta) - divide(power(ri, delta), power(psi, delta-1), psi, false); - delta = cdeg - ddeg; - } +static ex sr_gcd(const ex &a, const ex &b, sym_desc_vec::const_iterator var) +{ +#if STATISTICS + sr_gcd_called++; +#endif + + // The first symbol is our main variable + const ex &x = var->sym; + + // Sort c and d so that c has higher degree + ex c, d; + int adeg = a.degree(x), bdeg = b.degree(x); + int cdeg, ddeg; + if (adeg >= bdeg) { + c = a; + d = b; + cdeg = adeg; + ddeg = bdeg; + } else { + c = b; + d = a; + cdeg = bdeg; + ddeg = adeg; + } + + // Remove content from c and d, to be attached to GCD later + ex cont_c = c.content(x); + ex cont_d = d.content(x); + ex gamma = gcd(cont_c, cont_d, NULL, NULL, false); + if (ddeg == 0) + return gamma; + c = c.primpart(x, cont_c); + d = d.primpart(x, cont_d); + + // First element of subresultant sequence + ex r = _ex0, ri = _ex1, psi = _ex1; + int delta = cdeg - ddeg; + + for (;;) { + + // Calculate polynomial pseudo-remainder + r = prem(c, d, x, false); + if (r.is_zero()) + return gamma * d.primpart(x); + + c = d; + cdeg = ddeg; + if (!divide_in_z(r, ri * pow(psi, delta), d, var)) + throw(std::runtime_error("invalid expression in sr_gcd(), division failed")); + ddeg = d.degree(x); + if (ddeg == 0) { + if (is_exactly_a(r)) + return gamma; + else + return gamma * r.primpart(x); + } + + // Next element of subresultant sequence + ri = c.expand().lcoeff(x); + if (delta == 1) + psi = ri; + else if (delta) + divide_in_z(pow(ri, delta), pow(psi, delta-1), psi, var+1); + delta = cdeg - ddeg; + } } /** Return maximum (absolute value) coefficient of a polynomial. * This function is used internally by heur_gcd(). * - * @param e expanded multivariate polynomial * @return maximum coefficient * @see heur_gcd */ - -numeric ex::max_coefficient(void) const +numeric ex::max_coefficient() const { - GINAC_ASSERT(bp!=0); - return bp->max_coefficient(); + return bp->max_coefficient(); } -numeric basic::max_coefficient(void) const +/** Implementation ex::max_coefficient(). + * @see heur_gcd */ +numeric basic::max_coefficient() const { - return numONE(); + return *_num1_p; } -numeric numeric::max_coefficient(void) const +numeric numeric::max_coefficient() const { - return abs(*this); + return abs(*this); } -numeric add::max_coefficient(void) const +numeric add::max_coefficient() const { - epvector::const_iterator it = seq.begin(); - epvector::const_iterator itend = seq.end(); - GINAC_ASSERT(is_ex_exactly_of_type(overall_coeff,numeric)); - numeric cur_max = abs(ex_to_numeric(overall_coeff)); - while (it != itend) { - numeric a; - GINAC_ASSERT(!is_ex_exactly_of_type(it->rest,numeric)); - a = abs(ex_to_numeric(it->coeff)); - if (a > cur_max) - cur_max = a; - it++; - } - return cur_max; + epvector::const_iterator it = seq.begin(); + epvector::const_iterator itend = seq.end(); + GINAC_ASSERT(is_exactly_a(overall_coeff)); + numeric cur_max = abs(ex_to(overall_coeff)); + while (it != itend) { + numeric a; + GINAC_ASSERT(!is_exactly_a(it->rest)); + a = abs(ex_to(it->coeff)); + if (a > cur_max) + cur_max = a; + it++; + } + return cur_max; } -numeric mul::max_coefficient(void) const +numeric mul::max_coefficient() const { #ifdef DO_GINAC_ASSERT - epvector::const_iterator it = seq.begin(); - epvector::const_iterator itend = seq.end(); - while (it != itend) { - GINAC_ASSERT(!is_ex_exactly_of_type(recombine_pair_to_ex(*it),numeric)); - it++; - } + epvector::const_iterator it = seq.begin(); + epvector::const_iterator itend = seq.end(); + while (it != itend) { + GINAC_ASSERT(!is_exactly_a(recombine_pair_to_ex(*it))); + it++; + } #endif // def DO_GINAC_ASSERT - GINAC_ASSERT(is_ex_exactly_of_type(overall_coeff,numeric)); - return abs(ex_to_numeric(overall_coeff)); + GINAC_ASSERT(is_exactly_a(overall_coeff)); + return abs(ex_to(overall_coeff)); } -/** Apply symmetric modular homomorphism to a multivariate polynomial. - * This function is used internally by heur_gcd(). +/** Apply symmetric modular homomorphism to an expanded multivariate + * polynomial. This function is usually used internally by heur_gcd(). * - * @param e expanded multivariate polynomial * @param xi modulus * @return mapped polynomial * @see heur_gcd */ - -ex ex::smod(const numeric &xi) const -{ - GINAC_ASSERT(bp!=0); - return bp->smod(xi); -} - ex basic::smod(const numeric &xi) const { - return *this; + return *this; } ex numeric::smod(const numeric &xi) const { - return GiNaC::smod(*this, xi); + return GiNaC::smod(*this, xi); } ex add::smod(const numeric &xi) const { - epvector newseq; - newseq.reserve(seq.size()+1); - epvector::const_iterator it = seq.begin(); - epvector::const_iterator itend = seq.end(); - while (it != itend) { - GINAC_ASSERT(!is_ex_exactly_of_type(it->rest,numeric)); - numeric coeff = GiNaC::smod(ex_to_numeric(it->coeff), xi); - if (!coeff.is_zero()) - newseq.push_back(expair(it->rest, coeff)); - it++; - } - GINAC_ASSERT(is_ex_exactly_of_type(overall_coeff,numeric)); - numeric coeff = GiNaC::smod(ex_to_numeric(overall_coeff), xi); - return (new add(newseq,coeff))->setflag(status_flags::dynallocated); + epvector newseq; + newseq.reserve(seq.size()+1); + epvector::const_iterator it = seq.begin(); + epvector::const_iterator itend = seq.end(); + while (it != itend) { + GINAC_ASSERT(!is_exactly_a(it->rest)); + numeric coeff = GiNaC::smod(ex_to(it->coeff), xi); + if (!coeff.is_zero()) + newseq.push_back(expair(it->rest, coeff)); + it++; + } + GINAC_ASSERT(is_exactly_a(overall_coeff)); + numeric coeff = GiNaC::smod(ex_to(overall_coeff), xi); + return (new add(newseq,coeff))->setflag(status_flags::dynallocated); } ex mul::smod(const numeric &xi) const { #ifdef DO_GINAC_ASSERT - epvector::const_iterator it = seq.begin(); - epvector::const_iterator itend = seq.end(); - while (it != itend) { - GINAC_ASSERT(!is_ex_exactly_of_type(recombine_pair_to_ex(*it),numeric)); - it++; - } + epvector::const_iterator it = seq.begin(); + epvector::const_iterator itend = seq.end(); + while (it != itend) { + GINAC_ASSERT(!is_exactly_a(recombine_pair_to_ex(*it))); + it++; + } #endif // def DO_GINAC_ASSERT - mul * mulcopyp=new mul(*this); - GINAC_ASSERT(is_ex_exactly_of_type(overall_coeff,numeric)); - mulcopyp->overall_coeff = GiNaC::smod(ex_to_numeric(overall_coeff),xi); - mulcopyp->clearflag(status_flags::evaluated); - mulcopyp->clearflag(status_flags::hash_calculated); - return mulcopyp->setflag(status_flags::dynallocated); + mul * mulcopyp = new mul(*this); + GINAC_ASSERT(is_exactly_a(overall_coeff)); + mulcopyp->overall_coeff = GiNaC::smod(ex_to(overall_coeff),xi); + mulcopyp->clearflag(status_flags::evaluated); + mulcopyp->clearflag(status_flags::hash_calculated); + return mulcopyp->setflag(status_flags::dynallocated); } -/** Exception thrown by heur_gcd() to signal failure */ +/** xi-adic polynomial interpolation */ +static ex interpolate(const ex &gamma, const numeric &xi, const ex &x, int degree_hint = 1) +{ + exvector g; g.reserve(degree_hint); + ex e = gamma; + numeric rxi = xi.inverse(); + for (int i=0; !e.is_zero(); i++) { + ex gi = e.smod(xi); + g.push_back(gi * power(x, i)); + e = (e - gi) * rxi; + } + return (new add(g))->setflag(status_flags::dynallocated); +} + +/** Exception thrown by heur_gcd() to signal failure. */ class gcdheu_failed {}; /** Compute GCD of multivariate polynomials using the heuristic GCD algorithm. @@ -966,184 +1213,386 @@ class gcdheu_failed {}; * @return the GCD as a new expression * @see gcd * @exception gcdheu_failed() */ - static ex heur_gcd(const ex &a, const ex &b, ex *ca, ex *cb, sym_desc_vec::const_iterator var) { - if (is_ex_exactly_of_type(a, numeric) && is_ex_exactly_of_type(b, numeric)) { - numeric g = gcd(ex_to_numeric(a), ex_to_numeric(b)); - numeric rg; - if (ca || cb) - rg = g.inverse(); - if (ca) - *ca = ex_to_numeric(a).mul(rg); - if (cb) - *cb = ex_to_numeric(b).mul(rg); - return g; - } - - // The first symbol is our main variable - const symbol *x = var->sym; - - // Remove integer content - numeric gc = gcd(a.integer_content(), b.integer_content()); - numeric rgc = gc.inverse(); - ex p = a * rgc; - ex q = b * rgc; - int maxdeg = max(p.degree(*x), q.degree(*x)); - - // Find evaluation point - numeric mp = p.max_coefficient(), mq = q.max_coefficient(); - numeric xi; - if (mp > mq) - xi = mq * numTWO() + numTWO(); - else - xi = mp * numTWO() + numTWO(); - - // 6 tries maximum - for (int t=0; t<6; t++) { - if (xi.int_length() * maxdeg > 50000) - throw gcdheu_failed(); - - // Apply evaluation homomorphism and calculate GCD - ex gamma = heur_gcd(p.subs(*x == xi), q.subs(*x == xi), NULL, NULL, var+1).expand(); - if (!is_ex_exactly_of_type(gamma, fail)) { - - // Reconstruct polynomial from GCD of mapped polynomials - ex g = exZERO(); - numeric rxi = xi.inverse(); - for (int i=0; !gamma.is_zero(); i++) { - ex gi = gamma.smod(xi); - g += gi * power(*x, i); - gamma = (gamma - gi) * rxi; - } - // Remove integer content - g /= g.integer_content(); - - // If the calculated polynomial divides both a and b, this is the GCD - ex dummy; - if (divide_in_z(p, g, ca ? *ca : dummy, var) && divide_in_z(q, g, cb ? *cb : dummy, var)) { - g *= gc; - ex lc = g.lcoeff(*x); - if (is_ex_exactly_of_type(lc, numeric) && lc.compare(exZERO()) < 0) - return -g; - else - return g; - } - } - - // Next evaluation point - xi = iquo(xi * isqrt(isqrt(xi)) * numeric(73794), numeric(27011)); - } - return *new ex(fail()); +#if STATISTICS + heur_gcd_called++; +#endif + + // Algorithm only works for non-vanishing input polynomials + if (a.is_zero() || b.is_zero()) + return (new fail())->setflag(status_flags::dynallocated); + + // GCD of two numeric values -> CLN + if (is_exactly_a(a) && is_exactly_a(b)) { + numeric g = gcd(ex_to(a), ex_to(b)); + if (ca) + *ca = ex_to(a) / g; + if (cb) + *cb = ex_to(b) / g; + return g; + } + + // The first symbol is our main variable + const ex &x = var->sym; + + // Remove integer content + numeric gc = gcd(a.integer_content(), b.integer_content()); + numeric rgc = gc.inverse(); + ex p = a * rgc; + ex q = b * rgc; + int maxdeg = std::max(p.degree(x), q.degree(x)); + + // Find evaluation point + numeric mp = p.max_coefficient(); + numeric mq = q.max_coefficient(); + numeric xi; + if (mp > mq) + xi = mq * (*_num2_p) + (*_num2_p); + else + xi = mp * (*_num2_p) + (*_num2_p); + + // 6 tries maximum + for (int t=0; t<6; t++) { + if (xi.int_length() * maxdeg > 100000) { + throw gcdheu_failed(); + } + + // Apply evaluation homomorphism and calculate GCD + ex cp, cq; + ex gamma = heur_gcd(p.subs(x == xi, subs_options::no_pattern), q.subs(x == xi, subs_options::no_pattern), &cp, &cq, var+1).expand(); + if (!is_exactly_a(gamma)) { + + // Reconstruct polynomial from GCD of mapped polynomials + ex g = interpolate(gamma, xi, x, maxdeg); + + // Remove integer content + g /= g.integer_content(); + + // If the calculated polynomial divides both p and q, this is the GCD + ex dummy; + if (divide_in_z(p, g, ca ? *ca : dummy, var) && divide_in_z(q, g, cb ? *cb : dummy, var)) { + g *= gc; + return g; + } + } + + // Next evaluation point + xi = iquo(xi * isqrt(isqrt(xi)) * numeric(73794), numeric(27011)); + } + return (new fail())->setflag(status_flags::dynallocated); } /** Compute GCD (Greatest Common Divisor) of multivariate polynomials a(X) - * and b(X) in Z[X]. + * and b(X) in Z[X]. Optionally also compute the cofactors of a and b, + * defined by a = ca * gcd(a, b) and b = cb * gcd(a, b). * * @param a first multivariate polynomial * @param b second multivariate polynomial + * @param ca pointer to expression that will receive the cofactor of a, or NULL + * @param cb pointer to expression that will receive the cofactor of b, or NULL * @param check_args check whether a and b are polynomials with rational * coefficients (defaults to "true") * @return the GCD as a new expression */ - ex gcd(const ex &a, const ex &b, ex *ca, ex *cb, bool check_args) { - // Some trivial cases +#if STATISTICS + gcd_called++; +#endif + + // GCD of numerics -> CLN + if (is_exactly_a(a) && is_exactly_a(b)) { + numeric g = gcd(ex_to(a), ex_to(b)); + if (ca || cb) { + if (g.is_zero()) { + if (ca) + *ca = _ex0; + if (cb) + *cb = _ex0; + } else { + if (ca) + *ca = ex_to(a) / g; + if (cb) + *cb = ex_to(b) / g; + } + } + return g; + } + + // Check arguments + if (check_args && (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial))) { + throw(std::invalid_argument("gcd: arguments must be polynomials over the rationals")); + } + + // Partially factored cases (to avoid expanding large expressions) + if (is_exactly_a(a)) { + if (is_exactly_a(b) && b.nops() > a.nops()) + goto factored_b; +factored_a: + size_t num = a.nops(); + exvector g; g.reserve(num); + exvector acc_ca; acc_ca.reserve(num); + ex part_b = b; + for (size_t i=0; isetflag(status_flags::dynallocated); + if (cb) + *cb = part_b; + return (new mul(g))->setflag(status_flags::dynallocated); + } else if (is_exactly_a(b)) { + if (is_exactly_a(a) && a.nops() > b.nops()) + goto factored_a; +factored_b: + size_t num = b.nops(); + exvector g; g.reserve(num); + exvector acc_cb; acc_cb.reserve(num); + ex part_a = a; + for (size_t i=0; isetflag(status_flags::dynallocated); + return (new mul(g))->setflag(status_flags::dynallocated); + } + +#if FAST_COMPARE + // Input polynomials of the form poly^n are sometimes also trivial + if (is_exactly_a(a)) { + ex p = a.op(0); + const ex& exp_a = a.op(1); + if (is_exactly_a(b)) { + ex pb = b.op(0); + const ex& exp_b = b.op(1); + if (p.is_equal(pb)) { + // a = p^n, b = p^m, gcd = p^min(n, m) + if (exp_a < exp_b) { + if (ca) + *ca = _ex1; + if (cb) + *cb = power(p, exp_b - exp_a); + return power(p, exp_a); + } else { + if (ca) + *ca = power(p, exp_a - exp_b); + if (cb) + *cb = _ex1; + return power(p, exp_b); + } + } else { + ex p_co, pb_co; + ex p_gcd = gcd(p, pb, &p_co, &pb_co, check_args); + if (p_gcd.is_equal(_ex1)) { + // a(x) = p(x)^n, b(x) = p_b(x)^m, gcd (p, p_b) = 1 ==> + // gcd(a,b) = 1 + if (ca) + *ca = a; + if (cb) + *cb = b; + return _ex1; + // XXX: do I need to check for p_gcd = -1? + } else { + // there are common factors: + // a(x) = g(x)^n A(x)^n, b(x) = g(x)^m B(x)^m ==> + // gcd(a, b) = g(x)^n gcd(A(x)^n, g(x)^(n-m) B(x)^m + if (exp_a < exp_b) { + return power(p_gcd, exp_a)* + gcd(power(p_co, exp_a), power(p_gcd, exp_b-exp_a)*power(pb_co, exp_b), ca, cb, false); + } else { + return power(p_gcd, exp_b)* + gcd(power(p_gcd, exp_a - exp_b)*power(p_co, exp_a), power(pb_co, exp_b), ca, cb, false); + } + } // p_gcd.is_equal(_ex1) + } // p.is_equal(pb) + + } else { + if (p.is_equal(b)) { + // a = p^n, b = p, gcd = p + if (ca) + *ca = power(p, a.op(1) - 1); + if (cb) + *cb = _ex1; + return p; + } + + ex p_co, bpart_co; + ex p_gcd = gcd(p, b, &p_co, &bpart_co, false); + + if (p_gcd.is_equal(_ex1)) { + // a(x) = p(x)^n, gcd(p, b) = 1 ==> gcd(a, b) = 1 + if (ca) + *ca = a; + if (cb) + *cb = b; + return _ex1; + } else { + // a(x) = g(x)^n A(x)^n, b(x) = g(x) B(x) ==> gcd(a, b) = g(x) gcd(g(x)^(n-1) A(x)^n, B(x)) + return p_gcd*gcd(power(p_gcd, exp_a-1)*power(p_co, exp_a), bpart_co, ca, cb, false); + } + } // is_exactly_a(b) + + } else if (is_exactly_a(b)) { + ex p = b.op(0); + if (p.is_equal(a)) { + // a = p, b = p^n, gcd = p + if (ca) + *ca = _ex1; + if (cb) + *cb = power(p, b.op(1) - 1); + return p; + } + + ex p_co, apart_co; + const ex& exp_b(b.op(1)); + ex p_gcd = gcd(a, p, &apart_co, &p_co, false); + if (p_gcd.is_equal(_ex1)) { + // b=p(x)^n, gcd(a, p) = 1 ==> gcd(a, b) == 1 + if (ca) + *ca = a; + if (cb) + *cb = b; + return _ex1; + } else { + // there are common factors: + // a(x) = g(x) A(x), b(x) = g(x)^n B(x)^n ==> gcd = g(x) gcd(g(x)^(n-1) A(x)^n, B(x)) + + return p_gcd*gcd(apart_co, power(p_gcd, exp_b-1)*power(p_co, exp_b), ca, cb, false); + } // p_gcd.is_equal(_ex1) + } +#endif + + // Some trivial cases ex aex = a.expand(), bex = b.expand(); - if (aex.is_zero()) { - if (ca) - *ca = exZERO(); - if (cb) - *cb = exONE(); - return b; - } - if (bex.is_zero()) { - if (ca) - *ca = exONE(); - if (cb) - *cb = exZERO(); - return a; - } - if (aex.is_equal(exONE()) || bex.is_equal(exONE())) { - if (ca) - *ca = a; - if (cb) - *cb = b; - return exONE(); - } + if (aex.is_zero()) { + if (ca) + *ca = _ex0; + if (cb) + *cb = _ex1; + return b; + } + if (bex.is_zero()) { + if (ca) + *ca = _ex1; + if (cb) + *cb = _ex0; + return a; + } + if (aex.is_equal(_ex1) || bex.is_equal(_ex1)) { + if (ca) + *ca = a; + if (cb) + *cb = b; + return _ex1; + } #if FAST_COMPARE - if (a.is_equal(b)) { - if (ca) - *ca = exONE(); - if (cb) - *cb = exONE(); - return a; - } + if (a.is_equal(b)) { + if (ca) + *ca = _ex1; + if (cb) + *cb = _ex1; + return a; + } +#endif + + if (is_a(aex)) { + if (! bex.subs(aex==_ex0, subs_options::no_pattern).is_zero()) { + if (ca) + *ca = a; + if (cb) + *cb = b; + return _ex1; + } + } + + if (is_a(bex)) { + if (! aex.subs(bex==_ex0, subs_options::no_pattern).is_zero()) { + if (ca) + *ca = a; + if (cb) + *cb = b; + return _ex1; + } + } + + // Gather symbol statistics + sym_desc_vec sym_stats; + get_symbol_stats(a, b, sym_stats); + + // The symbol with least degree is our main variable + sym_desc_vec::const_iterator var = sym_stats.begin(); + const ex &x = var->sym; + + // Cancel trivial common factor + int ldeg_a = var->ldeg_a; + int ldeg_b = var->ldeg_b; + int min_ldeg = std::min(ldeg_a,ldeg_b); + if (min_ldeg > 0) { + ex common = power(x, min_ldeg); + return gcd((aex / common).expand(), (bex / common).expand(), ca, cb, false) * common; + } + + // Try to eliminate variables + if (var->deg_a == 0) { + ex bex_u, bex_c, bex_p; + bex.unitcontprim(x, bex_u, bex_c, bex_p); + ex g = gcd(aex, bex_c, ca, cb, false); + if (cb) + *cb *= bex_u * bex_p; + return g; + } else if (var->deg_b == 0) { + ex aex_u, aex_c, aex_p; + aex.unitcontprim(x, aex_u, aex_c, aex_p); + ex g = gcd(aex_c, bex, ca, cb, false); + if (ca) + *ca *= aex_u * aex_p; + return g; + } + + // Try heuristic algorithm first, fall back to PRS if that failed + ex g; + try { + g = heur_gcd(aex, bex, ca, cb, var); + } catch (gcdheu_failed) { + g = fail(); + } + if (is_exactly_a(g)) { +#if STATISTICS + heur_gcd_failed++; #endif - if (is_ex_exactly_of_type(aex, numeric) && is_ex_exactly_of_type(bex, numeric)) { - numeric g = gcd(ex_to_numeric(aex), ex_to_numeric(bex)); - if (ca) - *ca = ex_to_numeric(aex) / g; - if (cb) - *cb = ex_to_numeric(bex) / g; - return g; - } - if (check_args && !a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial)) { - throw(std::invalid_argument("gcd: arguments must be polynomials over the rationals")); - } - - // Gather symbol statistics - sym_desc_vec sym_stats; - get_symbol_stats(a, b, sym_stats); - - // The symbol with least degree is our main variable - sym_desc_vec::const_iterator var = sym_stats.begin(); - const symbol *x = var->sym; - - // Cancel trivial common factor - int ldeg_a = var->ldeg_a; - int ldeg_b = var->ldeg_b; - int min_ldeg = min(ldeg_a, ldeg_b); - if (min_ldeg > 0) { - ex common = power(*x, min_ldeg); -//clog << "trivial common factor " << common << endl; - return gcd((aex / common).expand(), (bex / common).expand(), ca, cb, false) * common; - } - - // Try to eliminate variables - if (var->deg_a == 0) { -//clog << "eliminating variable " << *x << " from b" << endl; - ex c = bex.content(*x); - ex g = gcd(aex, c, ca, cb, false); - if (cb) - *cb *= bex.unit(*x) * bex.primpart(*x, c); - return g; - } else if (var->deg_b == 0) { -//clog << "eliminating variable " << *x << " from a" << endl; - ex c = aex.content(*x); - ex g = gcd(c, bex, ca, cb, false); - if (ca) - *ca *= aex.unit(*x) * aex.primpart(*x, c); - return g; - } - - // Try heuristic algorithm first, fall back to PRS if that failed - ex g; - try { - g = heur_gcd(aex, bex, ca, cb, var); - } catch (gcdheu_failed) { - g = *new ex(fail()); - } - if (is_ex_exactly_of_type(g, fail)) { -//clog << "heuristics failed\n"; - g = sr_gcd(aex, bex, x); - if (ca) - divide(aex, g, *ca, false); - if (cb) - divide(bex, g, *cb, false); - } - return g; + g = sr_gcd(aex, bex, var); + if (g.is_equal(_ex1)) { + // Keep cofactors factored if possible + if (ca) + *ca = a; + if (cb) + *cb = b; + } else { + if (ca) + divide(aex, g, *ca, false); + if (cb) + divide(bex, g, *cb, false); + } + } else { + if (g.is_equal(_ex1)) { + // Keep cofactors factored if possible + if (ca) + *ca = a; + if (cb) + *cb = b; + } + } + + return g; } @@ -1156,14 +1605,14 @@ ex gcd(const ex &a, const ex &b, ex *ca, ex *cb, bool check_args) * @return the LCM as a new expression */ ex lcm(const ex &a, const ex &b, bool check_args) { - if (is_ex_exactly_of_type(a, numeric) && is_ex_exactly_of_type(b, numeric)) - return gcd(ex_to_numeric(a), ex_to_numeric(b)); - if (check_args && !a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial)) - throw(std::invalid_argument("lcm: arguments must be polynomials over the rationals")); - - ex ca, cb; - ex g = gcd(a, b, &ca, &cb, false); - return ca * cb * g; + if (is_exactly_a(a) && is_exactly_a(b)) + return lcm(ex_to(a), ex_to(b)); + if (check_args && (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial))) + throw(std::invalid_argument("lcm: arguments must be polynomials over the rationals")); + + ex ca, cb; + ex g = gcd(a, b, &ca, &cb, false); + return ca * cb * g; } @@ -1171,70 +1620,204 @@ ex lcm(const ex &a, const ex &b, bool check_args) * Square-free factorization */ -// Univariate GCD of polynomials in Q[x] (used internally by sqrfree()). -// a and b can be multivariate polynomials but they are treated as univariate polynomials in x. -static ex univariate_gcd(const ex &a, const ex &b, const symbol &x) -{ - if (a.is_zero()) - return b; - if (b.is_zero()) - return a; - if (a.is_equal(exONE()) || b.is_equal(exONE())) - return exONE(); - if (is_ex_of_type(a, numeric) && is_ex_of_type(b, numeric)) - return gcd(ex_to_numeric(a), ex_to_numeric(b)); - if (!a.info(info_flags::rational_polynomial) || !b.info(info_flags::rational_polynomial)) - throw(std::invalid_argument("univariate_gcd: arguments must be polynomials over the rationals")); - - // Euclidean algorithm - ex c, d, r; - if (a.degree(x) >= b.degree(x)) { - c = a; - d = b; - } else { - c = b; - d = a; - } - for (;;) { - r = rem(c, d, x, false); - if (r.is_zero()) - break; - c = d; - d = r; - } - return d / d.lcoeff(x); +/** Compute square-free factorization of multivariate polynomial a(x) using + * Yun's algorithm. Used internally by sqrfree(). + * + * @param a multivariate polynomial over Z[X], treated here as univariate + * polynomial in x. + * @param x variable to factor in + * @return vector of factors sorted in ascending degree */ +static exvector sqrfree_yun(const ex &a, const symbol &x) +{ + exvector res; + ex w = a; + ex z = w.diff(x); + ex g = gcd(w, z); + if (g.is_equal(_ex1)) { + res.push_back(a); + return res; + } + ex y; + do { + w = quo(w, g, x); + y = quo(z, g, x); + z = y - w.diff(x); + g = gcd(w, z); + res.push_back(g); + } while (!z.is_zero()); + return res; } -/** Compute square-free factorization of multivariate polynomial a(x) using - * Yun´s algorithm. +/** Compute a square-free factorization of a multivariate polynomial in Q[X]. + * + * @param a multivariate polynomial over Q[X] + * @param l lst of variables to factor in, may be left empty for autodetection + * @return a square-free factorization of \p a. + * + * \note + * A polynomial \f$p(X) \in C[X]\f$ is said square-free + * if, whenever any two polynomials \f$q(X)\f$ and \f$r(X)\f$ + * are such that + * \f[ + * p(X) = q(X)^2 r(X), + * \f] + * we have \f$q(X) \in C\f$. + * This means that \f$p(X)\f$ has no repeated factors, apart + * eventually from constants. + * Given a polynomial \f$p(X) \in C[X]\f$, we say that the + * decomposition + * \f[ + * p(X) = b \cdot p_1(X)^{a_1} \cdot p_2(X)^{a_2} \cdots p_r(X)^{a_r} + * \f] + * is a square-free factorization of \f$p(X)\f$ if the + * following conditions hold: + * -# \f$b \in C\f$ and \f$b \neq 0\f$; + * -# \f$a_i\f$ is a positive integer for \f$i = 1, \ldots, r\f$; + * -# the degree of the polynomial \f$p_i\f$ is strictly positive + * for \f$i = 1, \ldots, r\f$; + * -# the polynomial \f$\Pi_{i=1}^r p_i(X)\f$ is square-free. + * + * Square-free factorizations need not be unique. For example, if + * \f$a_i\f$ is even, we could change the polynomial \f$p_i(X)\f$ + * into \f$-p_i(X)\f$. + * Observe also that the factors \f$p_i(X)\f$ need not be irreducible + * polynomials. + */ +ex sqrfree(const ex &a, const lst &l) +{ + if (is_exactly_a(a) || // algorithm does not trap a==0 + is_a(a)) // shortcut + return a; + + // If no lst of variables to factorize in was specified we have to + // invent one now. Maybe one can optimize here by reversing the order + // or so, I don't know. + lst args; + if (l.nops()==0) { + sym_desc_vec sdv; + get_symbol_stats(a, _ex0, sdv); + sym_desc_vec::const_iterator it = sdv.begin(), itend = sdv.end(); + while (it != itend) { + args.append(it->sym); + ++it; + } + } else { + args = l; + } + + // Find the symbol to factor in at this stage + if (!is_a(args.op(0))) + throw (std::runtime_error("sqrfree(): invalid factorization variable")); + const symbol &x = ex_to(args.op(0)); + + // convert the argument from something in Q[X] to something in Z[X] + const numeric lcm = lcm_of_coefficients_denominators(a); + const ex tmp = multiply_lcm(a,lcm); + + // find the factors + exvector factors = sqrfree_yun(tmp, x); + + // construct the next list of symbols with the first element popped + lst newargs = args; + newargs.remove_first(); + + // recurse down the factors in remaining variables + if (newargs.nops()>0) { + exvector::iterator i = factors.begin(); + while (i != factors.end()) { + *i = sqrfree(*i, newargs); + ++i; + } + } + + // Done with recursion, now construct the final result + ex result = _ex1; + exvector::const_iterator it = factors.begin(), itend = factors.end(); + for (int p = 1; it!=itend; ++it, ++p) + result *= power(*it, p); + + // Yun's algorithm does not account for constant factors. (For univariate + // polynomials it works only in the monic case.) We can correct this by + // inserting what has been lost back into the result. For completeness + // we'll also have to recurse down that factor in the remaining variables. + if (newargs.nops()>0) + result *= sqrfree(quo(tmp, result, x), newargs); + else + result *= quo(tmp, result, x); + + // Put in the reational overall factor again and return + return result * lcm.inverse(); +} + + +/** Compute square-free partial fraction decomposition of rational function + * a(x). * - * @param a multivariate polynomial - * @param x variable to factor in - * @return factored polynomial */ -ex sqrfree(const ex &a, const symbol &x) -{ - int i = 1; - ex res = exONE(); - ex b = a.diff(x); - ex c = univariate_gcd(a, b, x); - ex w; - if (c.is_equal(exONE())) { - w = a; - } else { - w = quo(a, c, x); - ex y = quo(b, c, x); - ex z = y - w.diff(x); - while (!z.is_zero()) { - ex g = univariate_gcd(w, z, x); - res *= power(g, i); - w = quo(w, g, x); - y = quo(z, g, x); - z = y - w.diff(x); - i++; - } - } - return res * power(w, i); + * @param a rational function over Z[x], treated as univariate polynomial + * in x + * @param x variable to factor in + * @return decomposed rational function */ +ex sqrfree_parfrac(const ex & a, const symbol & x) +{ + // Find numerator and denominator + ex nd = numer_denom(a); + ex numer = nd.op(0), denom = nd.op(1); +//clog << "numer = " << numer << ", denom = " << denom << endl; + + // Convert N(x)/D(x) -> Q(x) + R(x)/D(x), so degree(R) < degree(D) + ex red_poly = quo(numer, denom, x), red_numer = rem(numer, denom, x).expand(); +//clog << "red_poly = " << red_poly << ", red_numer = " << red_numer << endl; + + // Factorize denominator and compute cofactors + exvector yun = sqrfree_yun(denom, x); +//clog << "yun factors: " << exprseq(yun) << endl; + size_t num_yun = yun.size(); + exvector factor; factor.reserve(num_yun); + exvector cofac; cofac.reserve(num_yun); + for (size_t i=0; isecond; + + // Otherwise create new symbol and add to list, taking care that the + // replacement expression doesn't itself contain symbols from repl, + // because subs() is not recursive + ex es = (new symbol)->setflag(status_flags::dynallocated); + ex e_replaced = e.subs(repl, subs_options::no_pattern); + repl.insert(std::make_pair(es, e_replaced)); + rev_lookup.insert(std::make_pair(e_replaced, es)); + return es; +} - // Otherwise create new symbol and add to list, taking care that the - // replacement expression doesn't contain symbols from the sym_lst +/** Create a symbol for replacing the expression "e" (or return a previously + * assigned symbol). The symbol and expression are appended to repl, and the + * symbol is returned. + * @see basic::to_rational + * @see basic::to_polynomial */ +static ex replace_with_symbol(const ex & e, exmap & repl) +{ + // Expression already replaced? Then return the assigned symbol + for (exmap::const_iterator it = repl.begin(); it != repl.end(); ++it) + if (it->second.is_equal(e)) + return it->first; + + // Otherwise create new symbol and add to list, taking care that the + // replacement expression doesn't itself contain symbols from repl, // because subs() is not recursive - symbol s; - ex es(s); - ex e_replaced = e.subs(sym_lst, repl_lst); - sym_lst.append(es); - repl_lst.append(e_replaced); - return es; + ex es = (new symbol)->setflag(status_flags::dynallocated); + ex e_replaced = e.subs(repl, subs_options::no_pattern); + repl.insert(std::make_pair(es, e_replaced)); + return es; } -/** Default implementation of ex::normal(). It replaces the object with a - * temporary symbol. +/** Function object to be applied by basic::normal(). */ +struct normal_map_function : public map_function { + int level; + normal_map_function(int l) : level(l) {} + ex operator()(const ex & e) { return normal(e, level); } +}; + +/** Default implementation of ex::normal(). It normalizes the children and + * replaces the object with a temporary symbol. * @see ex::normal */ -ex basic::normal(lst &sym_lst, lst &repl_lst, int level) const +ex basic::normal(exmap & repl, exmap & rev_lookup, int level) const { - return replace_with_symbol(*this, sym_lst, repl_lst); + if (nops() == 0) + return (new lst(replace_with_symbol(*this, repl, rev_lookup), _ex1))->setflag(status_flags::dynallocated); + else { + if (level == 1) + return (new lst(replace_with_symbol(*this, repl, rev_lookup), _ex1))->setflag(status_flags::dynallocated); + else if (level == -max_recursion_level) + throw(std::runtime_error("max recursion level reached")); + else { + normal_map_function map_normal(level - 1); + return (new lst(replace_with_symbol(map(map_normal), repl, rev_lookup), _ex1))->setflag(status_flags::dynallocated); + } + } } -/** Implementation of ex::normal() for symbols. This returns the unmodifies symbol. +/** Implementation of ex::normal() for symbols. This returns the unmodified symbol. * @see ex::normal */ -ex symbol::normal(lst &sym_lst, lst &repl_lst, int level) const +ex symbol::normal(exmap & repl, exmap & rev_lookup, int level) const { - return *this; + return (new lst(*this, _ex1))->setflag(status_flags::dynallocated); } @@ -1285,180 +1916,248 @@ ex symbol::normal(lst &sym_lst, lst &repl_lst, int level) const * into re+I*im and replaces I and non-rational real numbers with a temporary * symbol. * @see ex::normal */ -ex numeric::normal(lst &sym_lst, lst &repl_lst, int level) const +ex numeric::normal(exmap & repl, exmap & rev_lookup, int level) const { - if (is_real()) - if (is_rational()) - return *this; - else - return replace_with_symbol(*this, sym_lst, repl_lst); - else { // complex - numeric re = real(), im = imag(); - ex re_ex = re.is_rational() ? re : replace_with_symbol(re, sym_lst, repl_lst); - ex im_ex = im.is_rational() ? im : replace_with_symbol(im, sym_lst, repl_lst); - return re_ex + im_ex * replace_with_symbol(I, sym_lst, repl_lst); + numeric num = numer(); + ex numex = num; + + if (num.is_real()) { + if (!num.is_integer()) + numex = replace_with_symbol(numex, repl, rev_lookup); + } else { // complex + numeric re = num.real(), im = num.imag(); + ex re_ex = re.is_rational() ? re : replace_with_symbol(re, repl, rev_lookup); + ex im_ex = im.is_rational() ? im : replace_with_symbol(im, repl, rev_lookup); + numex = re_ex + im_ex * replace_with_symbol(I, repl, rev_lookup); } -} + // Denominator is always a real integer (see numeric::denom()) + return (new lst(numex, denom()))->setflag(status_flags::dynallocated); +} -/* - * Helper function for fraction cancellation (returns cancelled fraction n/d) - */ +/** Fraction cancellation. + * @param n numerator + * @param d denominator + * @return cancelled fraction {n, d} as a list */ static ex frac_cancel(const ex &n, const ex &d) { - ex num = n; - ex den = d; - ex pre_factor = exONE(); - - // Handle special cases where numerator or denominator is 0 - if (num.is_zero()) - return exZERO(); - if (den.expand().is_zero()) - throw(std::overflow_error("frac_cancel: division by zero in frac_cancel")); - - // More special cases - if (is_ex_exactly_of_type(den, numeric)) - return num / den; - if (num.is_zero()) - return exZERO(); - - // Bring numerator and denominator to Z[X] by multiplying with - // LCM of all coefficients' denominators - ex num_lcm = lcm_of_coefficients_denominators(num); - ex den_lcm = lcm_of_coefficients_denominators(den); - num *= num_lcm; - den *= den_lcm; - pre_factor = den_lcm / num_lcm; - - // Cancel GCD from numerator and denominator - ex cnum, cden; - if (gcd(num, den, &cnum, &cden, false) != exONE()) { + ex num = n; + ex den = d; + numeric pre_factor = *_num1_p; + +//std::clog << "frac_cancel num = " << num << ", den = " << den << std::endl; + + // Handle trivial case where denominator is 1 + if (den.is_equal(_ex1)) + return (new lst(num, den))->setflag(status_flags::dynallocated); + + // Handle special cases where numerator or denominator is 0 + if (num.is_zero()) + return (new lst(num, _ex1))->setflag(status_flags::dynallocated); + if (den.expand().is_zero()) + throw(std::overflow_error("frac_cancel: division by zero in frac_cancel")); + + // Bring numerator and denominator to Z[X] by multiplying with + // LCM of all coefficients' denominators + numeric num_lcm = lcm_of_coefficients_denominators(num); + numeric den_lcm = lcm_of_coefficients_denominators(den); + num = multiply_lcm(num, num_lcm); + den = multiply_lcm(den, den_lcm); + pre_factor = den_lcm / num_lcm; + + // Cancel GCD from numerator and denominator + ex cnum, cden; + if (gcd(num, den, &cnum, &cden, false) != _ex1) { num = cnum; den = cden; } // Make denominator unit normal (i.e. coefficient of first symbol // as defined by get_first_symbol() is made positive) - const symbol *x; - if (get_first_symbol(den, x)) { - if (den.unit(*x).compare(exZERO()) < 0) { - num *= exMINUSONE(); - den *= exMINUSONE(); + if (is_exactly_a(den)) { + if (ex_to(den).is_negative()) { + num *= _ex_1; + den *= _ex_1; + } + } else { + ex x; + if (get_first_symbol(den, x)) { + GINAC_ASSERT(is_exactly_a(den.unit(x))); + if (ex_to(den.unit(x)).is_negative()) { + num *= _ex_1; + den *= _ex_1; + } } } - return pre_factor * num / den; + + // Return result as list +//std::clog << " returns num = " << num << ", den = " << den << ", pre_factor = " << pre_factor << std::endl; + return (new lst(num * pre_factor.numer(), den * pre_factor.denom()))->setflag(status_flags::dynallocated); } /** Implementation of ex::normal() for a sum. It expands terms and performs * fractional addition. * @see ex::normal */ -ex add::normal(lst &sym_lst, lst &repl_lst, int level) const -{ - // Normalize and expand children - exvector o; - o.reserve(seq.size()+1); - epvector::const_iterator it = seq.begin(), itend = seq.end(); - while (it != itend) { - ex n = recombine_pair_to_ex(*it).bp->normal(sym_lst, repl_lst, level-1).expand(); - if (is_ex_exactly_of_type(n, add)) { - epvector::const_iterator bit = (static_cast(n.bp))->seq.begin(), bitend = (static_cast(n.bp))->seq.end(); - while (bit != bitend) { - o.push_back(recombine_pair_to_ex(*bit)); - bit++; - } - o.push_back((static_cast(n.bp))->overall_coeff); - } else - o.push_back(n); - it++; - } - o.push_back(overall_coeff.bp->normal(sym_lst, repl_lst, level-1)); - - // Determine common denominator - ex den = exONE(); - exvector::const_iterator ait = o.begin(), aitend = o.end(); - while (ait != aitend) { - den = lcm((*ait).denom(false), den, false); - ait++; - } - - // Add fractions - if (den.is_equal(exONE())) - return (new add(o))->setflag(status_flags::dynallocated); - else { - exvector num_seq; - for (ait=o.begin(); ait!=aitend; ait++) { - ex q; - if (!divide(den, (*ait).denom(false), q, false)) { - // should not happen - throw(std::runtime_error("invalid expression in add::normal, division failed")); - } - num_seq.push_back((*ait).numer(false) * q); - } - ex num = add(num_seq); - - // Cancel common factors from num/den - return frac_cancel(num, den); - } +ex add::normal(exmap & repl, exmap & rev_lookup, int level) const +{ + if (level == 1) + return (new lst(replace_with_symbol(*this, repl, rev_lookup), _ex1))->setflag(status_flags::dynallocated); + else if (level == -max_recursion_level) + throw(std::runtime_error("max recursion level reached")); + + // Normalize children and split each one into numerator and denominator + exvector nums, dens; + nums.reserve(seq.size()+1); + dens.reserve(seq.size()+1); + epvector::const_iterator it = seq.begin(), itend = seq.end(); + while (it != itend) { + ex n = ex_to(recombine_pair_to_ex(*it)).normal(repl, rev_lookup, level-1); + nums.push_back(n.op(0)); + dens.push_back(n.op(1)); + it++; + } + ex n = ex_to(overall_coeff).normal(repl, rev_lookup, level-1); + nums.push_back(n.op(0)); + dens.push_back(n.op(1)); + GINAC_ASSERT(nums.size() == dens.size()); + + // Now, nums is a vector of all numerators and dens is a vector of + // all denominators +//std::clog << "add::normal uses " << nums.size() << " summands:\n"; + + // Add fractions sequentially + exvector::const_iterator num_it = nums.begin(), num_itend = nums.end(); + exvector::const_iterator den_it = dens.begin(), den_itend = dens.end(); +//std::clog << " num = " << *num_it << ", den = " << *den_it << std::endl; + ex num = *num_it++, den = *den_it++; + while (num_it != num_itend) { +//std::clog << " num = " << *num_it << ", den = " << *den_it << std::endl; + ex next_num = *num_it++, next_den = *den_it++; + + // Trivially add sequences of fractions with identical denominators + while ((den_it != den_itend) && next_den.is_equal(*den_it)) { + next_num += *num_it; + num_it++; den_it++; + } + + // Additiion of two fractions, taking advantage of the fact that + // the heuristic GCD algorithm computes the cofactors at no extra cost + ex co_den1, co_den2; + ex g = gcd(den, next_den, &co_den1, &co_den2, false); + num = ((num * co_den2) + (next_num * co_den1)).expand(); + den *= co_den2; // this is the lcm(den, next_den) + } +//std::clog << " common denominator = " << den << std::endl; + + // Cancel common factors from num/den + return frac_cancel(num, den); } /** Implementation of ex::normal() for a product. It cancels common factors * from fractions. * @see ex::normal() */ -ex mul::normal(lst &sym_lst, lst &repl_lst, int level) const +ex mul::normal(exmap & repl, exmap & rev_lookup, int level) const { - // Normalize children - exvector o; - o.reserve(seq.size()+1); - epvector::const_iterator it = seq.begin(), itend = seq.end(); - while (it != itend) { - o.push_back(recombine_pair_to_ex(*it).bp->normal(sym_lst, repl_lst, level-1)); - it++; - } - o.push_back(overall_coeff.bp->normal(sym_lst, repl_lst, level-1)); - ex n = (new mul(o))->setflag(status_flags::dynallocated); - return frac_cancel(n.numer(false), n.denom(false)); + if (level == 1) + return (new lst(replace_with_symbol(*this, repl, rev_lookup), _ex1))->setflag(status_flags::dynallocated); + else if (level == -max_recursion_level) + throw(std::runtime_error("max recursion level reached")); + + // Normalize children, separate into numerator and denominator + exvector num; num.reserve(seq.size()); + exvector den; den.reserve(seq.size()); + ex n; + epvector::const_iterator it = seq.begin(), itend = seq.end(); + while (it != itend) { + n = ex_to(recombine_pair_to_ex(*it)).normal(repl, rev_lookup, level-1); + num.push_back(n.op(0)); + den.push_back(n.op(1)); + it++; + } + n = ex_to(overall_coeff).normal(repl, rev_lookup, level-1); + num.push_back(n.op(0)); + den.push_back(n.op(1)); + + // Perform fraction cancellation + return frac_cancel((new mul(num))->setflag(status_flags::dynallocated), + (new mul(den))->setflag(status_flags::dynallocated)); } -/** Implementation of ex::normal() for powers. It normalizes the basis, +/** Implementation of ex::normal([B) for powers. It normalizes the basis, * distributes integer exponents to numerator and denominator, and replaces * non-integer powers by temporary symbols. * @see ex::normal */ -ex power::normal(lst &sym_lst, lst &repl_lst, int level) const +ex power::normal(exmap & repl, exmap & rev_lookup, int level) const { - if (exponent.info(info_flags::integer)) { - // Integer powers are distributed - ex n = basis.bp->normal(sym_lst, repl_lst, level-1); - ex num = n.numer(false); - ex den = n.denom(false); - return power(num, exponent) / power(den, exponent); - } else { - // Non-integer powers are replaced by temporary symbol (after normalizing basis) - ex n = power(basis.bp->normal(sym_lst, repl_lst, level-1), exponent); - return replace_with_symbol(n, sym_lst, repl_lst); - } + if (level == 1) + return (new lst(replace_with_symbol(*this, repl, rev_lookup), _ex1))->setflag(status_flags::dynallocated); + else if (level == -max_recursion_level) + throw(std::runtime_error("max recursion level reached")); + + // Normalize basis and exponent (exponent gets reassembled) + ex n_basis = ex_to(basis).normal(repl, rev_lookup, level-1); + ex n_exponent = ex_to(exponent).normal(repl, rev_lookup, level-1); + n_exponent = n_exponent.op(0) / n_exponent.op(1); + + if (n_exponent.info(info_flags::integer)) { + + if (n_exponent.info(info_flags::positive)) { + + // (a/b)^n -> {a^n, b^n} + return (new lst(power(n_basis.op(0), n_exponent), power(n_basis.op(1), n_exponent)))->setflag(status_flags::dynallocated); + + } else if (n_exponent.info(info_flags::negative)) { + + // (a/b)^-n -> {b^n, a^n} + return (new lst(power(n_basis.op(1), -n_exponent), power(n_basis.op(0), -n_exponent)))->setflag(status_flags::dynallocated); + } + + } else { + + if (n_exponent.info(info_flags::positive)) { + + // (a/b)^x -> {sym((a/b)^x), 1} + return (new lst(replace_with_symbol(power(n_basis.op(0) / n_basis.op(1), n_exponent), repl, rev_lookup), _ex1))->setflag(status_flags::dynallocated); + + } else if (n_exponent.info(info_flags::negative)) { + + if (n_basis.op(1).is_equal(_ex1)) { + + // a^-x -> {1, sym(a^x)} + return (new lst(_ex1, replace_with_symbol(power(n_basis.op(0), -n_exponent), repl, rev_lookup)))->setflag(status_flags::dynallocated); + + } else { + + // (a/b)^-x -> {sym((b/a)^x), 1} + return (new lst(replace_with_symbol(power(n_basis.op(1) / n_basis.op(0), -n_exponent), repl, rev_lookup), _ex1))->setflag(status_flags::dynallocated); + } + } + } + + // (a/b)^x -> {sym((a/b)^x, 1} + return (new lst(replace_with_symbol(power(n_basis.op(0) / n_basis.op(1), n_exponent), repl, rev_lookup), _ex1))->setflag(status_flags::dynallocated); } -/** Implementation of ex::normal() for series. It normalizes each coefficient and - * replaces the series by a temporary symbol. +/** Implementation of ex::normal() for pseries. It normalizes each coefficient + * and replaces the series by a temporary symbol. * @see ex::normal */ -ex series::normal(lst &sym_lst, lst &repl_lst, int level) const +ex pseries::normal(exmap & repl, exmap & rev_lookup, int level) const { - epvector new_seq; - new_seq.reserve(seq.size()); - - epvector::const_iterator it = seq.begin(), itend = seq.end(); - while (it != itend) { - new_seq.push_back(expair(it->rest.normal(), it->coeff)); - it++; - } - - ex n = series(var, point, new_seq); - return replace_with_symbol(n, sym_lst, repl_lst); + epvector newseq; + epvector::const_iterator i = seq.begin(), end = seq.end(); + while (i != end) { + ex restexp = i->rest.normal(); + if (!restexp.is_zero()) + newseq.push_back(expair(restexp, i->coeff)); + ++i; + } + ex n = pseries(relational(var,point), newseq); + return (new lst(replace_with_symbol(n, repl, rev_lookup), _ex1))->setflag(status_flags::dynallocated); } @@ -1466,8 +2165,8 @@ ex series::normal(lst &sym_lst, lst &repl_lst, int level) const * This function converts an expression to its normal form * "numerator/denominator", where numerator and denominator are (relatively * prime) polynomials. Any subexpressions which are not rational functions - * (like non-rational numbers, non-integer powers or functions like Sin(), - * Cos() etc.) are replaced by temporary symbols which are re-substituted by + * (like non-rational numbers, non-integer powers or functions like sin(), + * cos() etc.) are replaced by temporary symbols which are re-substituted by * the (normalized) subexpressions before normal() returns (this way, any * expression can be treated as a rational function). normal() is applied * recursively to arguments of functions etc. @@ -1476,12 +2175,401 @@ ex series::normal(lst &sym_lst, lst &repl_lst, int level) const * @return normalized expression */ ex ex::normal(int level) const { - lst sym_lst, repl_lst; - ex e = bp->normal(sym_lst, repl_lst, level); - if (sym_lst.nops() > 0) - return e.subs(sym_lst, repl_lst); - else - return e; + exmap repl, rev_lookup; + + ex e = bp->normal(repl, rev_lookup, level); + GINAC_ASSERT(is_a(e)); + + // Re-insert replaced symbols + if (!repl.empty()) + e = e.subs(repl, subs_options::no_pattern); + + // Convert {numerator, denominator} form back to fraction + return e.op(0) / e.op(1); +} + +/** Get numerator of an expression. If the expression is not of the normal + * form "numerator/denominator", it is first converted to this form and + * then the numerator is returned. + * + * @see ex::normal + * @return numerator */ +ex ex::numer() const +{ + exmap repl, rev_lookup; + + ex e = bp->normal(repl, rev_lookup, 0); + GINAC_ASSERT(is_a(e)); + + // Re-insert replaced symbols + if (repl.empty()) + return e.op(0); + else + return e.op(0).subs(repl, subs_options::no_pattern); +} + +/** Get denominator of an expression. If the expression is not of the normal + * form "numerator/denominator", it is first converted to this form and + * then the denominator is returned. + * + * @see ex::normal + * @return denominator */ +ex ex::denom() const +{ + exmap repl, rev_lookup; + + ex e = bp->normal(repl, rev_lookup, 0); + GINAC_ASSERT(is_a(e)); + + // Re-insert replaced symbols + if (repl.empty()) + return e.op(1); + else + return e.op(1).subs(repl, subs_options::no_pattern); +} + +/** Get numerator and denominator of an expression. If the expresison is not + * of the normal form "numerator/denominator", it is first converted to this + * form and then a list [numerator, denominator] is returned. + * + * @see ex::normal + * @return a list [numerator, denominator] */ +ex ex::numer_denom() const +{ + exmap repl, rev_lookup; + + ex e = bp->normal(repl, rev_lookup, 0); + GINAC_ASSERT(is_a(e)); + + // Re-insert replaced symbols + if (repl.empty()) + return e; + else + return e.subs(repl, subs_options::no_pattern); +} + + +/** Rationalization of non-rational functions. + * This function converts a general expression to a rational function + * by replacing all non-rational subexpressions (like non-rational numbers, + * non-integer powers or functions like sin(), cos() etc.) to temporary + * symbols. This makes it possible to use functions like gcd() and divide() + * on non-rational functions by applying to_rational() on the arguments, + * calling the desired function and re-substituting the temporary symbols + * in the result. To make the last step possible, all temporary symbols and + * their associated expressions are collected in the map specified by the + * repl parameter, ready to be passed as an argument to ex::subs(). + * + * @param repl collects all temporary symbols and their replacements + * @return rationalized expression */ +ex ex::to_rational(exmap & repl) const +{ + return bp->to_rational(repl); +} + +// GiNaC 1.1 compatibility function +ex ex::to_rational(lst & repl_lst) const +{ + // Convert lst to exmap + exmap m; + for (lst::const_iterator it = repl_lst.begin(); it != repl_lst.end(); ++it) + m.insert(std::make_pair(it->op(0), it->op(1))); + + ex ret = bp->to_rational(m); + + // Convert exmap back to lst + repl_lst.remove_all(); + for (exmap::const_iterator it = m.begin(); it != m.end(); ++it) + repl_lst.append(it->first == it->second); + + return ret; +} + +ex ex::to_polynomial(exmap & repl) const +{ + return bp->to_polynomial(repl); +} + +// GiNaC 1.1 compatibility function +ex ex::to_polynomial(lst & repl_lst) const +{ + // Convert lst to exmap + exmap m; + for (lst::const_iterator it = repl_lst.begin(); it != repl_lst.end(); ++it) + m.insert(std::make_pair(it->op(0), it->op(1))); + + ex ret = bp->to_polynomial(m); + + // Convert exmap back to lst + repl_lst.remove_all(); + for (exmap::const_iterator it = m.begin(); it != m.end(); ++it) + repl_lst.append(it->first == it->second); + + return ret; +} + +/** Default implementation of ex::to_rational(). This replaces the object with + * a temporary symbol. */ +ex basic::to_rational(exmap & repl) const +{ + return replace_with_symbol(*this, repl); +} + +ex basic::to_polynomial(exmap & repl) const +{ + return replace_with_symbol(*this, repl); +} + + +/** Implementation of ex::to_rational() for symbols. This returns the + * unmodified symbol. */ +ex symbol::to_rational(exmap & repl) const +{ + return *this; +} + +/** Implementation of ex::to_polynomial() for symbols. This returns the + * unmodified symbol. */ +ex symbol::to_polynomial(exmap & repl) const +{ + return *this; +} + + +/** Implementation of ex::to_rational() for a numeric. It splits complex + * numbers into re+I*im and replaces I and non-rational real numbers with a + * temporary symbol. */ +ex numeric::to_rational(exmap & repl) const +{ + if (is_real()) { + if (!is_rational()) + return replace_with_symbol(*this, repl); + } else { // complex + numeric re = real(); + numeric im = imag(); + ex re_ex = re.is_rational() ? re : replace_with_symbol(re, repl); + ex im_ex = im.is_rational() ? im : replace_with_symbol(im, repl); + return re_ex + im_ex * replace_with_symbol(I, repl); + } + return *this; +} + +/** Implementation of ex::to_polynomial() for a numeric. It splits complex + * numbers into re+I*im and replaces I and non-integer real numbers with a + * temporary symbol. */ +ex numeric::to_polynomial(exmap & repl) const +{ + if (is_real()) { + if (!is_integer()) + return replace_with_symbol(*this, repl); + } else { // complex + numeric re = real(); + numeric im = imag(); + ex re_ex = re.is_integer() ? re : replace_with_symbol(re, repl); + ex im_ex = im.is_integer() ? im : replace_with_symbol(im, repl); + return re_ex + im_ex * replace_with_symbol(I, repl); + } + return *this; +} + + +/** Implementation of ex::to_rational() for powers. It replaces non-integer + * powers by temporary symbols. */ +ex power::to_rational(exmap & repl) const +{ + if (exponent.info(info_flags::integer)) + return power(basis.to_rational(repl), exponent); + else + return replace_with_symbol(*this, repl); +} + +/** Implementation of ex::to_polynomial() for powers. It replaces non-posint + * powers by temporary symbols. */ +ex power::to_polynomial(exmap & repl) const +{ + if (exponent.info(info_flags::posint)) + return power(basis.to_rational(repl), exponent); + else if (exponent.info(info_flags::negint)) + return power(replace_with_symbol(power(basis, _ex_1), repl), -exponent); + else + return replace_with_symbol(*this, repl); +} + + +/** Implementation of ex::to_rational() for expairseqs. */ +ex expairseq::to_rational(exmap & repl) const +{ + epvector s; + s.reserve(seq.size()); + epvector::const_iterator i = seq.begin(), end = seq.end(); + while (i != end) { + s.push_back(split_ex_to_pair(recombine_pair_to_ex(*i).to_rational(repl))); + ++i; + } + ex oc = overall_coeff.to_rational(repl); + if (oc.info(info_flags::numeric)) + return thisexpairseq(s, overall_coeff); + else + s.push_back(combine_ex_with_coeff_to_pair(oc, _ex1)); + return thisexpairseq(s, default_overall_coeff()); +} + +/** Implementation of ex::to_polynomial() for expairseqs. */ +ex expairseq::to_polynomial(exmap & repl) const +{ + epvector s; + s.reserve(seq.size()); + epvector::const_iterator i = seq.begin(), end = seq.end(); + while (i != end) { + s.push_back(split_ex_to_pair(recombine_pair_to_ex(*i).to_polynomial(repl))); + ++i; + } + ex oc = overall_coeff.to_polynomial(repl); + if (oc.info(info_flags::numeric)) + return thisexpairseq(s, overall_coeff); + else + s.push_back(combine_ex_with_coeff_to_pair(oc, _ex1)); + return thisexpairseq(s, default_overall_coeff()); +} + + +/** Remove the common factor in the terms of a sum 'e' by calculating the GCD, + * and multiply it into the expression 'factor' (which needs to be initialized + * to 1, unless you're accumulating factors). */ +static ex find_common_factor(const ex & e, ex & factor, exmap & repl) +{ + if (is_exactly_a(e)) { + + size_t num = e.nops(); + exvector terms; terms.reserve(num); + ex gc; + + // Find the common GCD + for (size_t i=0; i(x) || is_exactly_a(x)) { + ex f = 1; + x = find_common_factor(x, f, repl); + x *= f; + } + + if (i == 0) + gc = x; + else + gc = gcd(gc, x); + + terms.push_back(x); + } + + if (gc.is_equal(_ex1)) + return e; + + // The GCD is the factor we pull out + factor *= gc; + + // Now divide all terms by the GCD + for (size_t i=0; i(t)) { + for (size_t j=0; jsetflag(status_flags::dynallocated); + goto term_done; + } + } + } + + divide(t, gc, x); + t = x; +term_done: ; + } + return (new add(terms))->setflag(status_flags::dynallocated); + + } else if (is_exactly_a(e)) { + + size_t num = e.nops(); + exvector v; v.reserve(num); + + for (size_t i=0; isetflag(status_flags::dynallocated); + + } else if (is_exactly_a(e)) { + const ex e_exp(e.op(1)); + if (e_exp.info(info_flags::posint)) { + ex eb = e.op(0).to_polynomial(repl); + ex factor_local(_ex1); + ex pre_res = find_common_factor(eb, factor_local, repl); + factor *= power(factor_local, e_exp); + return power(pre_res, e_exp); + + } else + return e.to_polynomial(repl); + + } else + return e; +} + + +/** Collect common factors in sums. This converts expressions like + * 'a*(b*x+b*y)' to 'a*b*(x+y)'. */ +ex collect_common_factors(const ex & e) +{ + if (is_exactly_a(e) || is_exactly_a(e) || is_exactly_a(e)) { + + exmap repl; + ex factor = 1; + ex r = find_common_factor(e, factor, repl); + return factor.subs(repl, subs_options::no_pattern) * r.subs(repl, subs_options::no_pattern); + + } else + return e; +} + + +/** Resultant of two expressions e1,e2 with respect to symbol s. + * Method: Compute determinant of Sylvester matrix of e1,e2,s. */ +ex resultant(const ex & e1, const ex & e2, const ex & s) +{ + const ex ee1 = e1.expand(); + const ex ee2 = e2.expand(); + if (!ee1.info(info_flags::polynomial) || + !ee2.info(info_flags::polynomial)) + throw(std::runtime_error("resultant(): arguments must be polynomials")); + + const int h1 = ee1.degree(s); + const int l1 = ee1.ldegree(s); + const int h2 = ee2.degree(s); + const int l2 = ee2.ldegree(s); + + const int msize = h1 + h2; + matrix m(msize, msize); + + for (int l = h1; l >= l1; --l) { + const ex e = ee1.coeff(s, l); + for (int k = 0; k < h2; ++k) + m(k, k+h1-l) = e; + } + for (int l = h2; l >= l2; --l) { + const ex e = ee2.coeff(s, l); + for (int k = 0; k < h1; ++k) + m(k+h2, k+h2-l) = e; + } + + return m.determinant(); } + } // namespace GiNaC diff --git a/ginac/power.cpp b/ginac/power.cpp index bed6ed2f..ce477e2a 100644 --- a/ginac/power.cpp +++ b/ginac/power.cpp @@ -3,7 +3,7 @@ * Implementation of GiNaC's symbolic exponentiation (basis^exponent). */ /* - * GiNaC Copyright (C) 1999 Johannes Gutenberg University Mainz, Germany + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,422 +17,708 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include +#include #include "power.h" #include "expairseq.h" #include "add.h" #include "mul.h" +#include "ncmul.h" #include "numeric.h" -#include "relational.h" +#include "constant.h" +#include "operators.h" +#include "inifcns_exp.h" // for log() in power::derivative() +#include "matrix.h" +#include "indexed.h" #include "symbol.h" -#include "debugmsg.h" +#include "lst.h" +#include "archive.h" +#include "utils.h" namespace GiNaC { -typedef vector intvector; +GINAC_IMPLEMENT_REGISTERED_CLASS_OPT(power, basic, + print_func(&power::do_print_dflt). + print_func(&power::do_print_latex). + print_func(&power::do_print_csrc). + print_func(&power::do_print_python). + print_func(&power::do_print_python_repr)) + +typedef std::vector intvector; ////////// -// default constructor, destructor, copy constructor assignment operator and helpers +// default constructor ////////// -// public +power::power() : inherited(&power::tinfo_static) { } + +////////// +// other constructors +////////// + +// all inlined + +////////// +// archiving +////////// -power::power() : basic(TINFO_power) +power::power(const archive_node &n, lst &sym_lst) : inherited(n, sym_lst) { - debugmsg("power default constructor",LOGLEVEL_CONSTRUCT); + n.find_ex("basis", basis, sym_lst); + n.find_ex("exponent", exponent, sym_lst); } -power::~power() +void power::archive(archive_node &n) const { - debugmsg("power destructor",LOGLEVEL_DESTRUCT); - destroy(0); + inherited::archive(n); + n.add_ex("basis", basis); + n.add_ex("exponent", exponent); } -power::power(power const & other) +DEFAULT_UNARCHIVE(power) + +////////// +// functions overriding virtual functions from base classes +////////// + +// public + +void power::print_power(const print_context & c, const char *powersymbol, const char *openbrace, const char *closebrace, unsigned level) const { - debugmsg("power copy constructor",LOGLEVEL_CONSTRUCT); - copy(other); + // Ordinary output of powers using '^' or '**' + if (precedence() <= level) + c.s << openbrace << '('; + basis.print(c, precedence()); + c.s << powersymbol; + c.s << openbrace; + exponent.print(c, precedence()); + c.s << closebrace; + if (precedence() <= level) + c.s << ')' << closebrace; } -power const & power::operator=(power const & other) +void power::do_print_dflt(const print_dflt & c, unsigned level) const { - debugmsg("power operator=",LOGLEVEL_ASSIGNMENT); - if (this != &other) { - destroy(1); - copy(other); - } - return *this; -} + if (exponent.is_equal(_ex1_2)) { -// protected + // Square roots are printed in a special way + c.s << "sqrt("; + basis.print(c); + c.s << ')'; -void power::copy(power const & other) -{ - basic::copy(other); - basis=other.basis; - exponent=other.exponent; + } else + print_power(c, "^", "", "", level); } -void power::destroy(bool call_parent) +void power::do_print_latex(const print_latex & c, unsigned level) const { - if (call_parent) basic::destroy(call_parent); -} + if (is_exactly_a(exponent) && ex_to(exponent).is_negative()) { -////////// -// other constructors -////////// + // Powers with negative numeric exponents are printed as fractions + c.s << "\\frac{1}{"; + power(basis, -exponent).eval().print(c); + c.s << '}'; -// public + } else if (exponent.is_equal(_ex1_2)) { -power::power(ex const & lh, ex const & rh) : basic(TINFO_power), basis(lh), exponent(rh) -{ - debugmsg("power constructor from ex,ex",LOGLEVEL_CONSTRUCT); - GINAC_ASSERT(basis.return_type()==return_types::commutative); + // Square roots are printed in a special way + c.s << "\\sqrt{"; + basis.print(c); + c.s << '}'; + + } else + print_power(c, "^", "{", "}", level); } -power::power(ex const & lh, numeric const & rh) : basic(TINFO_power), basis(lh), exponent(rh) +static void print_sym_pow(const print_context & c, const symbol &x, int exp) { - debugmsg("power constructor from ex,numeric",LOGLEVEL_CONSTRUCT); - GINAC_ASSERT(basis.return_type()==return_types::commutative); + // Optimal output of integer powers of symbols to aid compiler CSE. + // C.f. ISO/IEC 14882:1998, section 1.9 [intro execution], paragraph 15 + // to learn why such a parenthesation is really necessary. + if (exp == 1) { + x.print(c); + } else if (exp == 2) { + x.print(c); + c.s << "*"; + x.print(c); + } else if (exp & 1) { + x.print(c); + c.s << "*"; + print_sym_pow(c, x, exp-1); + } else { + c.s << "("; + print_sym_pow(c, x, exp >> 1); + c.s << ")*("; + print_sym_pow(c, x, exp >> 1); + c.s << ")"; + } } -////////// -// functions overriding virtual functions from bases classes -////////// +void power::do_print_csrc(const print_csrc & c, unsigned level) const +{ + // Integer powers of symbols are printed in a special, optimized way + if (exponent.info(info_flags::integer) + && (is_a(basis) || is_a(basis))) { + int exp = ex_to(exponent).to_int(); + if (exp > 0) + c.s << '('; + else { + exp = -exp; + if (is_a(c)) + c.s << "recip("; + else + c.s << "1.0/("; + } + print_sym_pow(c, ex_to(basis), exp); + c.s << ')'; + + // ^-1 is printed as "1.0/" or with the recip() function of CLN + } else if (exponent.is_equal(_ex_1)) { + if (is_a(c)) + c.s << "recip("; + else + c.s << "1.0/("; + basis.print(c); + c.s << ')'; + + // Otherwise, use the pow() or expt() (CLN) functions + } else { + if (is_a(c)) + c.s << "expt("; + else + c.s << "pow("; + basis.print(c); + c.s << ','; + exponent.print(c); + c.s << ')'; + } +} -// public +void power::do_print_python(const print_python & c, unsigned level) const +{ + print_power(c, "**", "", "", level); +} -basic * power::duplicate() const +void power::do_print_python_repr(const print_python_repr & c, unsigned level) const { - debugmsg("power duplicate",LOGLEVEL_DUPLICATE); - return new power(*this); + c.s << class_name() << '('; + basis.print(c); + c.s << ','; + exponent.print(c); + c.s << ')'; } bool power::info(unsigned inf) const { - if (inf==info_flags::polynomial || inf==info_flags::integer_polynomial || inf==info_flags::rational_polynomial) { - return exponent.info(info_flags::nonnegint); - } else if (inf==info_flags::rational_function) { - return exponent.info(info_flags::integer); - } else { - return basic::info(inf); - } + switch (inf) { + case info_flags::polynomial: + case info_flags::integer_polynomial: + case info_flags::cinteger_polynomial: + case info_flags::rational_polynomial: + case info_flags::crational_polynomial: + return exponent.info(info_flags::nonnegint) && + basis.info(inf); + case info_flags::rational_function: + return exponent.info(info_flags::integer) && + basis.info(inf); + case info_flags::algebraic: + return !exponent.info(info_flags::integer) || + basis.info(inf); + } + return inherited::info(inf); } -int power::nops() const +size_t power::nops() const { - return 2; + return 2; } -ex & power::let_op(int const i) +ex power::op(size_t i) const { - GINAC_ASSERT(i>=0); - GINAC_ASSERT(i<2); + GINAC_ASSERT(i<2); - return i==0 ? basis : exponent; + return i==0 ? basis : exponent; } -int power::degree(symbol const & s) const +ex power::map(map_function & f) const { - if (is_exactly_of_type(*exponent.bp,numeric)) { - if ((*basis.bp).compare(s)==0) - return ex_to_numeric(exponent).to_int(); - else - return basis.degree(s) * ex_to_numeric(exponent).to_int(); - } - return 0; + const ex &mapped_basis = f(basis); + const ex &mapped_exponent = f(exponent); + + if (!are_ex_trivially_equal(basis, mapped_basis) + || !are_ex_trivially_equal(exponent, mapped_exponent)) + return (new power(mapped_basis, mapped_exponent))->setflag(status_flags::dynallocated); + else + return *this; } -int power::ldegree(symbol const & s) const +bool power::is_polynomial(const ex & var) const { - if (is_exactly_of_type(*exponent.bp,numeric)) { - if ((*basis.bp).compare(s)==0) - return ex_to_numeric(exponent).to_int(); - else - return basis.ldegree(s) * ex_to_numeric(exponent).to_int(); - } - return 0; + if (exponent.has(var)) + return false; + if (!exponent.info(info_flags::nonnegint)) + return false; + return basis.is_polynomial(var); } -ex power::coeff(symbol const & s, int const n) const +int power::degree(const ex & s) const { - if ((*basis.bp).compare(s)!=0) { - // basis not equal to s - if (n==0) { - return *this; - } else { - return exZERO(); - } - } else if (is_exactly_of_type(*exponent.bp,numeric)&& - (static_cast(*exponent.bp).compare(numeric(n))==0)) { - return exONE(); - } + if (is_equal(ex_to(s))) + return 1; + else if (is_exactly_a(exponent) && ex_to(exponent).is_integer()) { + if (basis.is_equal(s)) + return ex_to(exponent).to_int(); + else + return basis.degree(s) * ex_to(exponent).to_int(); + } else if (basis.has(s)) + throw(std::runtime_error("power::degree(): undefined degree because of non-integer exponent")); + else + return 0; +} - return exZERO(); +int power::ldegree(const ex & s) const +{ + if (is_equal(ex_to(s))) + return 1; + else if (is_exactly_a(exponent) && ex_to(exponent).is_integer()) { + if (basis.is_equal(s)) + return ex_to(exponent).to_int(); + else + return basis.ldegree(s) * ex_to(exponent).to_int(); + } else if (basis.has(s)) + throw(std::runtime_error("power::ldegree(): undefined degree because of non-integer exponent")); + else + return 0; } +ex power::coeff(const ex & s, int n) const +{ + if (is_equal(ex_to(s))) + return n==1 ? _ex1 : _ex0; + else if (!basis.is_equal(s)) { + // basis not equal to s + if (n == 0) + return *this; + else + return _ex0; + } else { + // basis equal to s + if (is_exactly_a(exponent) && ex_to(exponent).is_integer()) { + // integer exponent + int int_exp = ex_to(exponent).to_int(); + if (n == int_exp) + return _ex1; + else + return _ex0; + } else { + // non-integer exponents are treated as zero + if (n == 0) + return *this; + else + return _ex0; + } + } +} + +/** Perform automatic term rewriting rules in this class. In the following + * x, x1, x2,... stand for a symbolic variables of type ex and c, c1, c2... + * stand for such expressions that contain a plain number. + * - ^(x,0) -> 1 (also handles ^(0,0)) + * - ^(x,1) -> x + * - ^(0,c) -> 0 or exception (depending on the real part of c) + * - ^(1,x) -> 1 + * - ^(c1,c2) -> *(c1^n,c1^(c2-n)) (so that 0<(c2-n)<1, try to evaluate roots, possibly in numerator and denominator of c1) + * - ^(^(x,c1),c2) -> ^(x,c1*c2) (c2 integer or -1 < c1 <= 1, case c1=1 should not happen, see below!) + * - ^(*(x,y,z),c) -> *(x^c,y^c,z^c) (if c integer) + * - ^(*(x,c1),c2) -> ^(x,c2)*c1^c2 (c1>0) + * - ^(*(x,c1),c2) -> ^(-x,c2)*c1^c2 (c1<0) + * + * @param level cut-off in recursive evaluation */ ex power::eval(int level) const { - // simplifications: ^(x,0) -> 1 (0^0 handled here) - // ^(x,1) -> x - // ^(0,x) -> 0 (except if x is real and negative, in which case an exception is thrown) - // ^(1,x) -> 1 - // ^(c1,c2) -> *(c1^n,c1^(c2-n)) (c1, c2 numeric(), 0<(c2-n)<1 except if c1,c2 are rational, but c1^c2 is not) - // ^(^(x,c1),c2) -> ^(x,c1*c2) (c1, c2 numeric(), c2 integer or -1 < c1 <= 1, case c1=1 should not happen, see below!) - // ^(*(x,y,z),c1) -> *(x^c1,y^c1,z^c1) (c1 integer) - // ^(*(x,c1),c2) -> ^(x,c2)*c1^c2 (c1, c2 numeric(), c1>0) - // ^(*(x,c1),c2) -> ^(-x,c2)*c1^c2 (c1, c2 numeric(), c1<0) - - debugmsg("power eval",LOGLEVEL_MEMBER_FUNCTION); - - if ((level==1)&&(flags & status_flags::evaluated)) { - return *this; - } else if (level == -max_recursion_level) { - throw(std::runtime_error("max recursion level reached")); - } - - ex const & ebasis = level==1 ? basis : basis.eval(level-1); - ex const & eexponent = level==1 ? exponent : exponent.eval(level-1); - - bool basis_is_numerical=0; - bool exponent_is_numerical=0; - numeric * num_basis; - numeric * num_exponent; - - if (is_exactly_of_type(*ebasis.bp,numeric)) { - basis_is_numerical=1; - num_basis=static_cast(ebasis.bp); - } - if (is_exactly_of_type(*eexponent.bp,numeric)) { - exponent_is_numerical=1; - num_exponent=static_cast(eexponent.bp); - } - - // ^(x,0) -> 1 (0^0 also handled here) - if (eexponent.is_zero()) - return exONE(); - - // ^(x,1) -> x - if (eexponent.is_equal(exONE())) - return ebasis; - - // ^(0,x) -> 0 (except if x is real and negative) - if (ebasis.is_zero()) { - if (exponent_is_numerical && num_exponent->is_negative()) { - throw(std::overflow_error("power::eval(): division by zero")); - } else - return exZERO(); - } - - // ^(1,x) -> 1 - if (ebasis.is_equal(exONE())) - return exONE(); - - if (basis_is_numerical && exponent_is_numerical) { - // ^(c1,c2) -> c1^c2 (c1, c2 numeric(), - // except if c1,c2 are rational, but c1^c2 is not) - bool basis_is_rational = num_basis->is_rational(); - bool exponent_is_rational = num_exponent->is_rational(); - numeric res = (*num_basis).power(*num_exponent); - - if ((!basis_is_rational || !exponent_is_rational) - || res.is_rational()) { - return res; - } - GINAC_ASSERT(!num_exponent->is_integer()); // has been handled by now - // ^(c1,n/m) -> *(c1^q,c1^(n/m-q)), 0<(n/m-h)<1, q integer - if (basis_is_rational && exponent_is_rational - && num_exponent->is_real() - && !num_exponent->is_integer()) { - numeric r, q, n, m; - n = num_exponent->numer(); - m = num_exponent->denom(); - q = iquo(n, m, r); - if (r.is_negative()) { - r = r.add(m); - q = q.sub(numONE()); - } - if (q.is_zero()) // the exponent was in the allowed range 0<(n/m)<1 - return this->hold(); - else { - epvector res(2); - res.push_back(expair(ebasis,r.div(m))); - res.push_back(expair(ex(num_basis->power(q)),exONE())); - return (new mul(res))->setflag(status_flags::dynallocated | status_flags::evaluated); - /*return mul(num_basis->power(q), - power(ex(*num_basis),ex(r.div(m)))).hold(); - */ - /* return (new mul(num_basis->power(q), - power(*num_basis,r.div(m)).hold()))->setflag(status_flags::dynallocated | status_flags::evaluated); - */ - } - } - } - - // ^(^(x,c1),c2) -> ^(x,c1*c2) - // (c1, c2 numeric(), c2 integer or -1 < c1 <= 1, - // case c1=1 should not happen, see below!) - if (exponent_is_numerical && is_ex_exactly_of_type(ebasis,power)) { - power const & sub_power=ex_to_power(ebasis); - ex const & sub_basis=sub_power.basis; - ex const & sub_exponent=sub_power.exponent; - if (is_ex_exactly_of_type(sub_exponent,numeric)) { - numeric const & num_sub_exponent=ex_to_numeric(sub_exponent); - GINAC_ASSERT(num_sub_exponent!=numeric(1)); - if (num_exponent->is_integer() || abs(num_sub_exponent)<1) { - return power(sub_basis,num_sub_exponent.mul(*num_exponent)); - } - } - } - - // ^(*(x,y,z),c1) -> *(x^c1,y^c1,z^c1) (c1 integer) - if (exponent_is_numerical && num_exponent->is_integer() && - is_ex_exactly_of_type(ebasis,mul)) { - return expand_mul(ex_to_mul(ebasis), *num_exponent); - } - - // ^(*(...,x;c1),c2) -> ^(*(...,x;1),c2)*c1^c2 (c1, c2 numeric(), c1>0) - // ^(*(...,x,c1),c2) -> ^(*(...,x;-1),c2)*(-c1)^c2 (c1, c2 numeric(), c1<0) - if (exponent_is_numerical && is_ex_exactly_of_type(ebasis,mul)) { - GINAC_ASSERT(!num_exponent->is_integer()); // should have been handled above - mul const & mulref=ex_to_mul(ebasis); - if (!mulref.overall_coeff.is_equal(exONE())) { - numeric const & num_coeff=ex_to_numeric(mulref.overall_coeff); - if (num_coeff.is_real()) { - if (num_coeff.is_positive()>0) { - mul * mulp=new mul(mulref); - mulp->overall_coeff=exONE(); - mulp->clearflag(status_flags::evaluated); - mulp->clearflag(status_flags::hash_calculated); - return (new mul(power(*mulp,exponent), - power(num_coeff,*num_exponent)))-> - setflag(status_flags::dynallocated); - } else { - GINAC_ASSERT(num_coeff.compare(numZERO())<0); - if (num_coeff.compare(numMINUSONE())!=0) { - mul * mulp=new mul(mulref); - mulp->overall_coeff=exMINUSONE(); - mulp->clearflag(status_flags::evaluated); - mulp->clearflag(status_flags::hash_calculated); - return (new mul(power(*mulp,exponent), - power(abs(num_coeff),*num_exponent)))-> - setflag(status_flags::dynallocated); - } - } - } - } - } - - if (are_ex_trivially_equal(ebasis,basis) && - are_ex_trivially_equal(eexponent,exponent)) { - return this->hold(); - } - return (new power(ebasis, eexponent))->setflag(status_flags::dynallocated | - status_flags::evaluated); + if ((level==1) && (flags & status_flags::evaluated)) + return *this; + else if (level == -max_recursion_level) + throw(std::runtime_error("max recursion level reached")); + + const ex & ebasis = level==1 ? basis : basis.eval(level-1); + const ex & eexponent = level==1 ? exponent : exponent.eval(level-1); + + bool basis_is_numerical = false; + bool exponent_is_numerical = false; + const numeric *num_basis; + const numeric *num_exponent; + + if (is_exactly_a(ebasis)) { + basis_is_numerical = true; + num_basis = &ex_to(ebasis); + } + if (is_exactly_a(eexponent)) { + exponent_is_numerical = true; + num_exponent = &ex_to(eexponent); + } + + // ^(x,0) -> 1 (0^0 also handled here) + if (eexponent.is_zero()) { + if (ebasis.is_zero()) + throw (std::domain_error("power::eval(): pow(0,0) is undefined")); + else + return _ex1; + } + + // ^(x,1) -> x + if (eexponent.is_equal(_ex1)) + return ebasis; + + // ^(0,c1) -> 0 or exception (depending on real value of c1) + if (ebasis.is_zero() && exponent_is_numerical) { + if ((num_exponent->real()).is_zero()) + throw (std::domain_error("power::eval(): pow(0,I) is undefined")); + else if ((num_exponent->real()).is_negative()) + throw (pole_error("power::eval(): division by zero",1)); + else + return _ex0; + } + + // ^(1,x) -> 1 + if (ebasis.is_equal(_ex1)) + return _ex1; + + // power of a function calculated by separate rules defined for this function + if (is_a(ebasis)) + return ex_to(ebasis).power_law(eexponent); + + if (exponent_is_numerical) { + + // ^(c1,c2) -> c1^c2 (c1, c2 numeric(), + // except if c1,c2 are rational, but c1^c2 is not) + if (basis_is_numerical) { + const bool basis_is_crational = num_basis->is_crational(); + const bool exponent_is_crational = num_exponent->is_crational(); + if (!basis_is_crational || !exponent_is_crational) { + // return a plain float + return (new numeric(num_basis->power(*num_exponent)))->setflag(status_flags::dynallocated | + status_flags::evaluated | + status_flags::expanded); + } + + const numeric res = num_basis->power(*num_exponent); + if (res.is_crational()) { + return res; + } + GINAC_ASSERT(!num_exponent->is_integer()); // has been handled by now + + // ^(c1,n/m) -> *(c1^q,c1^(n/m-q)), 0<(n/m-q)<1, q integer + if (basis_is_crational && exponent_is_crational + && num_exponent->is_real() + && !num_exponent->is_integer()) { + const numeric n = num_exponent->numer(); + const numeric m = num_exponent->denom(); + numeric r; + numeric q = iquo(n, m, r); + if (r.is_negative()) { + r += m; + --q; + } + if (q.is_zero()) { // the exponent was in the allowed range 0<(n/m)<1 + if (num_basis->is_rational() && !num_basis->is_integer()) { + // try it for numerator and denominator separately, in order to + // partially simplify things like (5/8)^(1/3) -> 1/2*5^(1/3) + const numeric bnum = num_basis->numer(); + const numeric bden = num_basis->denom(); + const numeric res_bnum = bnum.power(*num_exponent); + const numeric res_bden = bden.power(*num_exponent); + if (res_bnum.is_integer()) + return (new mul(power(bden,-*num_exponent),res_bnum))->setflag(status_flags::dynallocated | status_flags::evaluated); + if (res_bden.is_integer()) + return (new mul(power(bnum,*num_exponent),res_bden.inverse()))->setflag(status_flags::dynallocated | status_flags::evaluated); + } + return this->hold(); + } else { + // assemble resulting product, but allowing for a re-evaluation, + // because otherwise we'll end up with something like + // (7/8)^(4/3) -> 7/8*(1/2*7^(1/3)) + // instead of 7/16*7^(1/3). + ex prod = power(*num_basis,r.div(m)); + return prod*power(*num_basis,q); + } + } + } + + // ^(^(x,c1),c2) -> ^(x,c1*c2) + // (c1, c2 numeric(), c2 integer or -1 < c1 <= 1, + // case c1==1 should not happen, see below!) + if (is_exactly_a(ebasis)) { + const power & sub_power = ex_to(ebasis); + const ex & sub_basis = sub_power.basis; + const ex & sub_exponent = sub_power.exponent; + if (is_exactly_a(sub_exponent)) { + const numeric & num_sub_exponent = ex_to(sub_exponent); + GINAC_ASSERT(num_sub_exponent!=numeric(1)); + if (num_exponent->is_integer() || (abs(num_sub_exponent) - (*_num1_p)).is_negative()) + return power(sub_basis,num_sub_exponent.mul(*num_exponent)); + } + } + + // ^(*(x,y,z),c1) -> *(x^c1,y^c1,z^c1) (c1 integer) + if (num_exponent->is_integer() && is_exactly_a(ebasis)) { + return expand_mul(ex_to(ebasis), *num_exponent, 0); + } + + // ^(*(...,x;c1),c2) -> *(^(*(...,x;1),c2),c1^c2) (c1, c2 numeric(), c1>0) + // ^(*(...,x;c1),c2) -> *(^(*(...,x;-1),c2),(-c1)^c2) (c1, c2 numeric(), c1<0) + if (is_exactly_a(ebasis)) { + GINAC_ASSERT(!num_exponent->is_integer()); // should have been handled above + const mul & mulref = ex_to(ebasis); + if (!mulref.overall_coeff.is_equal(_ex1)) { + const numeric & num_coeff = ex_to(mulref.overall_coeff); + if (num_coeff.is_real()) { + if (num_coeff.is_positive()) { + mul *mulp = new mul(mulref); + mulp->overall_coeff = _ex1; + mulp->clearflag(status_flags::evaluated); + mulp->clearflag(status_flags::hash_calculated); + return (new mul(power(*mulp,exponent), + power(num_coeff,*num_exponent)))->setflag(status_flags::dynallocated); + } else { + GINAC_ASSERT(num_coeff.compare(*_num0_p)<0); + if (!num_coeff.is_equal(*_num_1_p)) { + mul *mulp = new mul(mulref); + mulp->overall_coeff = _ex_1; + mulp->clearflag(status_flags::evaluated); + mulp->clearflag(status_flags::hash_calculated); + return (new mul(power(*mulp,exponent), + power(abs(num_coeff),*num_exponent)))->setflag(status_flags::dynallocated); + } + } + } + } + } + + // ^(nc,c1) -> ncmul(nc,nc,...) (c1 positive integer, unless nc is a matrix) + if (num_exponent->is_pos_integer() && + ebasis.return_type() != return_types::commutative && + !is_a(ebasis)) { + return ncmul(exvector(num_exponent->to_int(), ebasis), true); + } + } + + if (are_ex_trivially_equal(ebasis,basis) && + are_ex_trivially_equal(eexponent,exponent)) { + return this->hold(); + } + return (new power(ebasis, eexponent))->setflag(status_flags::dynallocated | + status_flags::evaluated); } ex power::evalf(int level) const { - debugmsg("power evalf",LOGLEVEL_MEMBER_FUNCTION); - - ex ebasis; - ex eexponent; - - if (level==1) { - ebasis=basis; - eexponent=exponent; - } else if (level == -max_recursion_level) { - throw(std::runtime_error("max recursion level reached")); - } else { - ebasis=basis.evalf(level-1); - eexponent=exponent.evalf(level-1); - } + ex ebasis; + ex eexponent; + + if (level==1) { + ebasis = basis; + eexponent = exponent; + } else if (level == -max_recursion_level) { + throw(std::runtime_error("max recursion level reached")); + } else { + ebasis = basis.evalf(level-1); + if (!is_exactly_a(exponent)) + eexponent = exponent.evalf(level-1); + else + eexponent = exponent; + } + + return power(ebasis,eexponent); +} - return power(ebasis,eexponent); +ex power::evalm() const +{ + const ex ebasis = basis.evalm(); + const ex eexponent = exponent.evalm(); + if (is_a(ebasis)) { + if (is_exactly_a(eexponent)) { + return (new matrix(ex_to(ebasis).pow(eexponent)))->setflag(status_flags::dynallocated); + } + } + return (new power(ebasis, eexponent))->setflag(status_flags::dynallocated); } -ex power::subs(lst const & ls, lst const & lr) const +bool power::has(const ex & other, unsigned options) const { - ex const & subsed_basis=basis.subs(ls,lr); - ex const & subsed_exponent=exponent.subs(ls,lr); + if (!(options & has_options::algebraic)) + return basic::has(other, options); + if (!is_a(other)) + return basic::has(other, options); + if (!exponent.info(info_flags::integer) + || !other.op(1).info(info_flags::integer)) + return basic::has(other, options); + if (exponent.info(info_flags::posint) + && other.op(1).info(info_flags::posint) + && ex_to(exponent).to_int() + > ex_to(other.op(1)).to_int() + && basis.match(other.op(0))) + return true; + if (exponent.info(info_flags::negint) + && other.op(1).info(info_flags::negint) + && ex_to(exponent).to_int() + < ex_to(other.op(1)).to_int() + && basis.match(other.op(0))) + return true; + return basic::has(other, options); +} + +// from mul.cpp +extern bool tryfactsubs(const ex &, const ex &, int &, lst &); + +ex power::subs(const exmap & m, unsigned options) const +{ + const ex &subsed_basis = basis.subs(m, options); + const ex &subsed_exponent = exponent.subs(m, options); + + if (!are_ex_trivially_equal(basis, subsed_basis) + || !are_ex_trivially_equal(exponent, subsed_exponent)) + return power(subsed_basis, subsed_exponent).subs_one_level(m, options); - if (are_ex_trivially_equal(basis,subsed_basis)&& - are_ex_trivially_equal(exponent,subsed_exponent)) { - return *this; - } - - return power(subsed_basis, subsed_exponent); + if (!(options & subs_options::algebraic)) + return subs_one_level(m, options); + + for (exmap::const_iterator it = m.begin(); it != m.end(); ++it) { + int nummatches = std::numeric_limits::max(); + lst repls; + if (tryfactsubs(*this, it->first, nummatches, repls)) + return (ex_to((*this) * power(it->second.subs(ex(repls), subs_options::no_pattern) / it->first.subs(ex(repls), subs_options::no_pattern), nummatches))).subs_one_level(m, options); + } + + return subs_one_level(m, options); +} + +ex power::eval_ncmul(const exvector & v) const +{ + return inherited::eval_ncmul(v); } -ex power::simplify_ncmul(exvector const & v) const +ex power::conjugate() const { - return basic::simplify_ncmul(v); + ex newbasis = basis.conjugate(); + ex newexponent = exponent.conjugate(); + if (are_ex_trivially_equal(basis, newbasis) && are_ex_trivially_equal(exponent, newexponent)) { + return *this; + } + return (new power(newbasis, newexponent))->setflag(status_flags::dynallocated); } // protected -int power::compare_same_type(basic const & other) const +/** Implementation of ex::diff() for a power. + * @see ex::diff */ +ex power::derivative(const symbol & s) const { - GINAC_ASSERT(is_exactly_of_type(other, power)); - power const & o=static_cast(const_cast(other)); + if (is_a(exponent)) { + // D(b^r) = r * b^(r-1) * D(b) (faster than the formula below) + epvector newseq; + newseq.reserve(2); + newseq.push_back(expair(basis, exponent - _ex1)); + newseq.push_back(expair(basis.diff(s), _ex1)); + return mul(newseq, exponent); + } else { + // D(b^e) = b^e * (D(e)*ln(b) + e*D(b)/b) + return mul(*this, + add(mul(exponent.diff(s), log(basis)), + mul(mul(exponent, basis.diff(s)), power(basis, _ex_1)))); + } +} - int cmpval; - cmpval=basis.compare(o.basis); - if (cmpval==0) { - return exponent.compare(o.exponent); - } - return cmpval; +int power::compare_same_type(const basic & other) const +{ + GINAC_ASSERT(is_exactly_a(other)); + const power &o = static_cast(other); + + int cmpval = basis.compare(o.basis); + if (cmpval) + return cmpval; + else + return exponent.compare(o.exponent); } -unsigned power::return_type(void) const +unsigned power::return_type() const { - return basis.return_type(); + return basis.return_type(); } - -unsigned power::return_type_tinfo(void) const + +tinfo_t power::return_type_tinfo() const { - return basis.return_type_tinfo(); + return basis.return_type_tinfo(); } ex power::expand(unsigned options) const { - ex expanded_basis=basis.expand(options); - - if (!is_ex_exactly_of_type(exponent,numeric)|| - !ex_to_numeric(exponent).is_integer()) { - if (are_ex_trivially_equal(basis,expanded_basis)) { - return this->hold(); - } else { - return (new power(expanded_basis,exponent))-> - setflag(status_flags::dynallocated); - } - } - - // integer numeric exponent - numeric const & num_exponent=ex_to_numeric(exponent); - int int_exponent = num_exponent.to_int(); - - if (int_exponent > 0 && is_ex_exactly_of_type(expanded_basis,add)) { - return expand_add(ex_to_add(expanded_basis), int_exponent); - } - - if (is_ex_exactly_of_type(expanded_basis,mul)) { - return expand_mul(ex_to_mul(expanded_basis), num_exponent); - } - - // cannot expand further - if (are_ex_trivially_equal(basis,expanded_basis)) { - return this->hold(); - } else { - return (new power(expanded_basis,exponent))-> - setflag(status_flags::dynallocated); - } + if (options == 0 && (flags & status_flags::expanded)) + return *this; + + const ex expanded_basis = basis.expand(options); + const ex expanded_exponent = exponent.expand(options); + + // x^(a+b) -> x^a * x^b + if (is_exactly_a(expanded_exponent)) { + const add &a = ex_to(expanded_exponent); + exvector distrseq; + distrseq.reserve(a.seq.size() + 1); + epvector::const_iterator last = a.seq.end(); + epvector::const_iterator cit = a.seq.begin(); + while (cit!=last) { + distrseq.push_back(power(expanded_basis, a.recombine_pair_to_ex(*cit))); + ++cit; + } + + // Make sure that e.g. (x+y)^(2+a) expands the (x+y)^2 factor + if (ex_to(a.overall_coeff).is_integer()) { + const numeric &num_exponent = ex_to(a.overall_coeff); + int int_exponent = num_exponent.to_int(); + if (int_exponent > 0 && is_exactly_a(expanded_basis)) + distrseq.push_back(expand_add(ex_to(expanded_basis), int_exponent, options)); + else + distrseq.push_back(power(expanded_basis, a.overall_coeff)); + } else + distrseq.push_back(power(expanded_basis, a.overall_coeff)); + + // Make sure that e.g. (x+y)^(1+a) -> x*(x+y)^a + y*(x+y)^a + ex r = (new mul(distrseq))->setflag(status_flags::dynallocated); + return r.expand(options); + } + + if (!is_exactly_a(expanded_exponent) || + !ex_to(expanded_exponent).is_integer()) { + if (are_ex_trivially_equal(basis,expanded_basis) && are_ex_trivially_equal(exponent,expanded_exponent)) { + return this->hold(); + } else { + return (new power(expanded_basis,expanded_exponent))->setflag(status_flags::dynallocated | (options == 0 ? status_flags::expanded : 0)); + } + } + + // integer numeric exponent + const numeric & num_exponent = ex_to(expanded_exponent); + int int_exponent = num_exponent.to_int(); + + // (x+y)^n, n>0 + if (int_exponent > 0 && is_exactly_a(expanded_basis)) + return expand_add(ex_to(expanded_basis), int_exponent, options); + + // (x*y)^n -> x^n * y^n + if (is_exactly_a(expanded_basis)) + return expand_mul(ex_to(expanded_basis), num_exponent, options, true); + + // cannot expand further + if (are_ex_trivially_equal(basis,expanded_basis) && are_ex_trivially_equal(exponent,expanded_exponent)) + return this->hold(); + else + return (new power(expanded_basis,expanded_exponent))->setflag(status_flags::dynallocated | (options == 0 ? status_flags::expanded : 0)); } ////////// @@ -445,280 +731,209 @@ ex power::expand(unsigned options) const // non-virtual functions in this class ////////// -ex power::expand_add(add const & a, int const n) const +/** expand a^n where a is an add and n is a positive integer. + * @see power::expand */ +ex power::expand_add(const add & a, int n, unsigned options) const { - // expand a^n where a is an add and n is an integer - - if (n==2) { - return expand_add_2(a); - } - - int m=a.nops(); - exvector sum; - sum.reserve((n+1)*(m-1)); - intvector k(m-1); - intvector k_cum(m-1); // k_cum[l]:=sum(i=0,l,k[l]); - intvector upper_limit(m-1); - int l; - - for (int l=0; lsetflag(status_flags::dynallocated)); - - // increment k[] - l=m-2; - while ((l>=0)&&((++k[l])>upper_limit[l])) { - k[l]=0; - l--; - } - if (l<0) break; - - // recalc k_cum[] and upper_limit[] - if (l==0) { - k_cum[0]=k[0]; - } else { - k_cum[l]=k_cum[l-1]+k[l]; - } - for (int i=l+1; isetflag(status_flags::dynallocated); + if (n==2) + return expand_add_2(a, options); + + const size_t m = a.nops(); + exvector result; + // The number of terms will be the number of combinatorial compositions, + // i.e. the number of unordered arrangements of m nonnegative integers + // which sum up to n. It is frequently written as C_n(m) and directly + // related with binomial coefficients: + result.reserve(binomial(numeric(n+m-1), numeric(m-1)).to_int()); + intvector k(m-1); + intvector k_cum(m-1); // k_cum[l]:=sum(i=0,l,k[l]); + intvector upper_limit(m-1); + int l; + + for (size_t l=0; l(b)); + GINAC_ASSERT(!is_exactly_a(b) || + !is_exactly_a(ex_to(b).exponent) || + !ex_to(ex_to(b).exponent).is_pos_integer() || + !is_exactly_a(ex_to(b).basis) || + !is_exactly_a(ex_to(b).basis) || + !is_exactly_a(ex_to(b).basis)); + if (is_exactly_a(b)) + term.push_back(expand_mul(ex_to(b), numeric(k[l]), options, true)); + else + term.push_back(power(b,k[l])); + } + + const ex & b = a.op(l); + GINAC_ASSERT(!is_exactly_a(b)); + GINAC_ASSERT(!is_exactly_a(b) || + !is_exactly_a(ex_to(b).exponent) || + !ex_to(ex_to(b).exponent).is_pos_integer() || + !is_exactly_a(ex_to(b).basis) || + !is_exactly_a(ex_to(b).basis) || + !is_exactly_a(ex_to(b).basis)); + if (is_exactly_a(b)) + term.push_back(expand_mul(ex_to(b), numeric(n-k_cum[m-2]), options, true)); + else + term.push_back(power(b,n-k_cum[m-2])); + + numeric f = binomial(numeric(n),numeric(k[0])); + for (l=1; lsetflag(status_flags::dynallocated)).expand(options)); + + // increment k[] + l = m-2; + while ((l>=0) && ((++k[l])>upper_limit[l])) { + k[l] = 0; + --l; + } + if (l<0) break; + + // recalc k_cum[] and upper_limit[] + k_cum[l] = (l==0 ? k[0] : k_cum[l-1]+k[l]); + + for (size_t i=l+1; isetflag(status_flags::dynallocated | + status_flags::expanded); } -/* -ex power::expand_add_2(add const & a) const -{ - // special case: expand a^2 where a is an add - - epvector sum; - sum.reserve((a.seq.size()*(a.seq.size()+1))/2); - epvector::const_iterator last=a.seq.end(); - - for (epvector::const_iterator cit0=a.seq.begin(); cit0!=last; ++cit0) { - ex const & b=a.recombine_pair_to_ex(*cit0); - GINAC_ASSERT(!is_ex_exactly_of_type(b,add)); - GINAC_ASSERT(!is_ex_exactly_of_type(b,power)|| - !is_ex_exactly_of_type(ex_to_power(b).exponent,numeric)|| - !ex_to_numeric(ex_to_power(b).exponent).is_pos_integer()); - if (is_ex_exactly_of_type(b,mul)) { - sum.push_back(a.split_ex_to_pair(expand_mul(ex_to_mul(b),numTWO()))); - } else { - sum.push_back(a.split_ex_to_pair((new power(b,exTWO()))-> - setflag(status_flags::dynallocated))); - } - for (epvector::const_iterator cit1=cit0+1; cit1!=last; ++cit1) { - sum.push_back(a.split_ex_to_pair((new mul(a.recombine_pair_to_ex(*cit0), - a.recombine_pair_to_ex(*cit1)))-> - setflag(status_flags::dynallocated), - exTWO())); - } - } - - GINAC_ASSERT(sum.size()==(a.seq.size()*(a.seq.size()+1))/2); - - return (new add(sum))->setflag(status_flags::dynallocated); -} -*/ - -ex power::expand_add_2(add const & a) const -{ - // special case: expand a^2 where a is an add - - epvector sum; - unsigned a_nops=a.nops(); - sum.reserve((a_nops*(a_nops+1))/2); - epvector::const_iterator last=a.seq.end(); - - // power(+(x,...,z;c),2)=power(+(x,...,z;0),2)+2*c*+(x,...,z;0)+c*c - // first part: ignore overall_coeff and expand other terms - for (epvector::const_iterator cit0=a.seq.begin(); cit0!=last; ++cit0) { - ex const & r=(*cit0).rest; - ex const & c=(*cit0).coeff; - - GINAC_ASSERT(!is_ex_exactly_of_type(r,add)); - GINAC_ASSERT(!is_ex_exactly_of_type(r,power)|| - !is_ex_exactly_of_type(ex_to_power(r).exponent,numeric)|| - !ex_to_numeric(ex_to_power(r).exponent).is_pos_integer()|| - !is_ex_exactly_of_type(ex_to_power(r).basis,add)|| - !is_ex_exactly_of_type(ex_to_power(r).basis,mul)|| - !is_ex_exactly_of_type(ex_to_power(r).basis,power)); - - if (are_ex_trivially_equal(c,exONE())) { - if (is_ex_exactly_of_type(r,mul)) { - sum.push_back(expair(expand_mul(ex_to_mul(r),numTWO()),exONE())); - } else { - sum.push_back(expair((new power(r,exTWO()))->setflag(status_flags::dynallocated), - exONE())); - } - } else { - if (is_ex_exactly_of_type(r,mul)) { - sum.push_back(expair(expand_mul(ex_to_mul(r),numTWO()), - ex_to_numeric(c).power_dyn(numTWO()))); - } else { - sum.push_back(expair((new power(r,exTWO()))->setflag(status_flags::dynallocated), - ex_to_numeric(c).power_dyn(numTWO()))); - } - } - - for (epvector::const_iterator cit1=cit0+1; cit1!=last; ++cit1) { - ex const & r1=(*cit1).rest; - ex const & c1=(*cit1).coeff; - sum.push_back(a.combine_ex_with_coeff_to_pair((new mul(r,r1))->setflag(status_flags::dynallocated), - numTWO().mul(ex_to_numeric(c)).mul_dyn(ex_to_numeric(c1)))); - } - } - - GINAC_ASSERT(sum.size()==(a.seq.size()*(a.seq.size()+1))/2); - - // second part: add terms coming from overall_factor (if != 0) - if (!a.overall_coeff.is_equal(exZERO())) { - for (epvector::const_iterator cit=a.seq.begin(); cit!=a.seq.end(); ++cit) { - sum.push_back(a.combine_pair_with_coeff_to_pair(*cit,ex_to_numeric(a.overall_coeff).mul_dyn(numTWO()))); - } - sum.push_back(expair(ex_to_numeric(a.overall_coeff).power_dyn(numTWO()),exONE())); - } - - GINAC_ASSERT(sum.size()==(a_nops*(a_nops+1))/2); - - return (new add(sum))->setflag(status_flags::dynallocated); -} - -ex power::expand_mul(mul const & m, numeric const & n) const -{ - // expand m^n where m is a mul and n is and integer - - if (n.is_equal(numZERO())) { - return exONE(); - } - - epvector distrseq; - distrseq.reserve(m.seq.size()); - epvector::const_iterator last=m.seq.end(); - epvector::const_iterator cit=m.seq.begin(); - while (cit!=last) { - if (is_ex_exactly_of_type((*cit).rest,numeric)) { - distrseq.push_back(m.combine_pair_with_coeff_to_pair(*cit,n)); - } else { - // it is safe not to call mul::combine_pair_with_coeff_to_pair() - // since n is an integer - distrseq.push_back(expair((*cit).rest, - ex_to_numeric((*cit).coeff).mul(n))); - } - ++cit; - } - return (new mul(distrseq,ex_to_numeric(m.overall_coeff).power_dyn(n))) - ->setflag(status_flags::dynallocated); -} -/* -ex power::expand_commutative_3(ex const & basis, numeric const & exponent, - unsigned options) const +/** Special case of power::expand_add. Expands a^2 where a is an add. + * @see power::expand_add */ +ex power::expand_add_2(const add & a, unsigned options) const { - // obsolete - - exvector distrseq; - epvector splitseq; - - add const & addref=static_cast(*basis.bp); - - splitseq=addref.seq; - splitseq.pop_back(); - ex first_operands=add(splitseq); - ex last_operand=addref.recombine_pair_to_ex(*(addref.seq.end()-1)); - - int n=exponent.to_int(); - for (int k=0; k<=n; k++) { - distrseq.push_back(binomial(n,k)*power(first_operands,numeric(k))* - power(last_operand,numeric(n-k))); - } - return ex((new add(distrseq))->setflag(status_flags::sub_expanded | - status_flags::expanded | - status_flags::dynallocated )). - expand(options); -} -*/ + epvector sum; + size_t a_nops = a.nops(); + sum.reserve((a_nops*(a_nops+1))/2); + epvector::const_iterator last = a.seq.end(); + + // power(+(x,...,z;c),2)=power(+(x,...,z;0),2)+2*c*+(x,...,z;0)+c*c + // first part: ignore overall_coeff and expand other terms + for (epvector::const_iterator cit0=a.seq.begin(); cit0!=last; ++cit0) { + const ex & r = cit0->rest; + const ex & c = cit0->coeff; + + GINAC_ASSERT(!is_exactly_a(r)); + GINAC_ASSERT(!is_exactly_a(r) || + !is_exactly_a(ex_to(r).exponent) || + !ex_to(ex_to(r).exponent).is_pos_integer() || + !is_exactly_a(ex_to(r).basis) || + !is_exactly_a(ex_to(r).basis) || + !is_exactly_a(ex_to(r).basis)); + + if (c.is_equal(_ex1)) { + if (is_exactly_a(r)) { + sum.push_back(expair(expand_mul(ex_to(r), *_num2_p, options, true), + _ex1)); + } else { + sum.push_back(expair((new power(r,_ex2))->setflag(status_flags::dynallocated), + _ex1)); + } + } else { + if (is_exactly_a(r)) { + sum.push_back(a.combine_ex_with_coeff_to_pair(expand_mul(ex_to(r), *_num2_p, options, true), + ex_to(c).power_dyn(*_num2_p))); + } else { + sum.push_back(a.combine_ex_with_coeff_to_pair((new power(r,_ex2))->setflag(status_flags::dynallocated), + ex_to(c).power_dyn(*_num2_p))); + } + } + + for (epvector::const_iterator cit1=cit0+1; cit1!=last; ++cit1) { + const ex & r1 = cit1->rest; + const ex & c1 = cit1->coeff; + sum.push_back(a.combine_ex_with_coeff_to_pair((new mul(r,r1))->setflag(status_flags::dynallocated), + _num2_p->mul(ex_to(c)).mul_dyn(ex_to(c1)))); + } + } + + GINAC_ASSERT(sum.size()==(a.seq.size()*(a.seq.size()+1))/2); + + // second part: add terms coming from overall_factor (if != 0) + if (!a.overall_coeff.is_zero()) { + epvector::const_iterator i = a.seq.begin(), end = a.seq.end(); + while (i != end) { + sum.push_back(a.combine_pair_with_coeff_to_pair(*i, ex_to(a.overall_coeff).mul_dyn(*_num2_p))); + ++i; + } + sum.push_back(expair(ex_to(a.overall_coeff).power_dyn(*_num2_p),_ex1)); + } + + GINAC_ASSERT(sum.size()==(a_nops*(a_nops+1))/2); + + return (new add(sum))->setflag(status_flags::dynallocated | status_flags::expanded); +} -/* -ex power::expand_noncommutative(ex const & basis, numeric const & exponent, - unsigned options) const +/** Expand factors of m in m^n where m is a mul and n is and integer. + * @see power::expand */ +ex power::expand_mul(const mul & m, const numeric & n, unsigned options, bool from_expand) const { - ex rest_power=ex(power(basis,exponent.add(numMINUSONE()))). - expand(options | expand_options::internal_do_not_expand_power_operands); - - return ex(mul(rest_power,basis),0). - expand(options | expand_options::internal_do_not_expand_mul_operands); + GINAC_ASSERT(n.is_integer()); + + if (n.is_zero()) { + return _ex1; + } + + // Leave it to multiplication since dummy indices have to be renamed + if (get_all_dummy_indices(m).size() > 0 && n.is_positive()) { + ex result = m; + exvector va = get_all_dummy_indices(m); + sort(va.begin(), va.end(), ex_is_less()); + + for (int i=1; i < n.to_int(); i++) + result *= rename_dummy_indices_uniquely(va, m); + return result; + } + + epvector distrseq; + distrseq.reserve(m.seq.size()); + bool need_reexpand = false; + + epvector::const_iterator last = m.seq.end(); + epvector::const_iterator cit = m.seq.begin(); + while (cit!=last) { + if (is_exactly_a(cit->rest)) { + distrseq.push_back(m.combine_pair_with_coeff_to_pair(*cit, n)); + } else { + // it is safe not to call mul::combine_pair_with_coeff_to_pair() + // since n is an integer + numeric new_coeff = ex_to(cit->coeff).mul(n); + if (from_expand && is_exactly_a(cit->rest) && new_coeff.is_pos_integer()) { + // this happens when e.g. (a+b)^(1/2) gets squared and + // the resulting product needs to be reexpanded + need_reexpand = true; + } + distrseq.push_back(expair(cit->rest, new_coeff)); + } + ++cit; + } + + const mul & result = static_cast((new mul(distrseq, ex_to(m.overall_coeff).power_dyn(n)))->setflag(status_flags::dynallocated)); + if (need_reexpand) + return ex(result).expand(options); + if (from_expand) + return result.setflag(status_flags::expanded); + return result; } -*/ - -////////// -// static member variables -////////// - -// protected - -unsigned power::precedence=60; - -////////// -// global constants -////////// - -const power some_power; -type_info const & typeid_power=typeid(some_power); } // namespace GiNaC diff --git a/ginac/pseries.cpp b/ginac/pseries.cpp new file mode 100644 index 00000000..c5b85325 --- /dev/null +++ b/ginac/pseries.cpp @@ -0,0 +1,1177 @@ +/** @file pseries.cpp + * + * Implementation of class for extended truncated power series and + * methods for series expansion. */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "pseries.h" +#include "add.h" +#include "inifcns.h" // for Order function +#include "lst.h" +#include "mul.h" +#include "power.h" +#include "relational.h" +#include "operators.h" +#include "symbol.h" +#include "integral.h" +#include "archive.h" +#include "utils.h" + +namespace GiNaC { + +GINAC_IMPLEMENT_REGISTERED_CLASS_OPT(pseries, basic, + print_func(&pseries::do_print). + print_func(&pseries::do_print_latex). + print_func(&pseries::do_print_tree). + print_func(&pseries::do_print_python). + print_func(&pseries::do_print_python_repr)) + + +/* + * Default constructor + */ + +pseries::pseries() : inherited(&pseries::tinfo_static) { } + + +/* + * Other ctors + */ + +/** Construct pseries from a vector of coefficients and powers. + * expair.rest holds the coefficient, expair.coeff holds the power. + * The powers must be integers (positive or negative) and in ascending order; + * the last coefficient can be Order(_ex1) to represent a truncated, + * non-terminating series. + * + * @param rel_ expansion variable and point (must hold a relational) + * @param ops_ vector of {coefficient, power} pairs (coefficient must not be zero) + * @return newly constructed pseries */ +pseries::pseries(const ex &rel_, const epvector &ops_) : basic(&pseries::tinfo_static), seq(ops_) +{ + GINAC_ASSERT(is_a(rel_)); + GINAC_ASSERT(is_a(rel_.lhs())); + point = rel_.rhs(); + var = rel_.lhs(); +} + + +/* + * Archiving + */ + +pseries::pseries(const archive_node &n, lst &sym_lst) : inherited(n, sym_lst) +{ + for (unsigned int i=0; true; ++i) { + ex rest; + ex coeff; + if (n.find_ex("coeff", rest, sym_lst, i) && n.find_ex("power", coeff, sym_lst, i)) + seq.push_back(expair(rest, coeff)); + else + break; + } + n.find_ex("var", var, sym_lst); + n.find_ex("point", point, sym_lst); +} + +void pseries::archive(archive_node &n) const +{ + inherited::archive(n); + epvector::const_iterator i = seq.begin(), iend = seq.end(); + while (i != iend) { + n.add_ex("coeff", i->rest); + n.add_ex("power", i->coeff); + ++i; + } + n.add_ex("var", var); + n.add_ex("point", point); +} + +DEFAULT_UNARCHIVE(pseries) + +////////// +// functions overriding virtual functions from base classes +////////// + +void pseries::print_series(const print_context & c, const char *openbrace, const char *closebrace, const char *mul_sym, const char *pow_sym, unsigned level) const +{ + if (precedence() <= level) + c.s << '('; + + // objects of type pseries must not have any zero entries, so the + // trivial (zero) pseries needs a special treatment here: + if (seq.empty()) + c.s << '0'; + + epvector::const_iterator i = seq.begin(), end = seq.end(); + while (i != end) { + + // print a sign, if needed + if (i != seq.begin()) + c.s << '+'; + + if (!is_exactly_a(i->rest)) { + + // print 'rest', i.e. the expansion coefficient + if (i->rest.info(info_flags::numeric) && + i->rest.info(info_flags::positive)) { + i->rest.print(c); + } else { + c.s << openbrace << '('; + i->rest.print(c); + c.s << ')' << closebrace; + } + + // print 'coeff', something like (x-1)^42 + if (!i->coeff.is_zero()) { + c.s << mul_sym; + if (!point.is_zero()) { + c.s << openbrace << '('; + (var-point).print(c); + c.s << ')' << closebrace; + } else + var.print(c); + if (i->coeff.compare(_ex1)) { + c.s << pow_sym; + c.s << openbrace; + if (i->coeff.info(info_flags::negative)) { + c.s << '('; + i->coeff.print(c); + c.s << ')'; + } else + i->coeff.print(c); + c.s << closebrace; + } + } + } else + Order(power(var-point,i->coeff)).print(c); + ++i; + } + + if (precedence() <= level) + c.s << ')'; +} + +void pseries::do_print(const print_context & c, unsigned level) const +{ + print_series(c, "", "", "*", "^", level); +} + +void pseries::do_print_latex(const print_latex & c, unsigned level) const +{ + print_series(c, "{", "}", " ", "^", level); +} + +void pseries::do_print_python(const print_python & c, unsigned level) const +{ + print_series(c, "", "", "*", "**", level); +} + +void pseries::do_print_tree(const print_tree & c, unsigned level) const +{ + c.s << std::string(level, ' ') << class_name() << " @" << this + << std::hex << ", hash=0x" << hashvalue << ", flags=0x" << flags << std::dec + << std::endl; + size_t num = seq.size(); + for (size_t i=0; i(other)); + const pseries &o = static_cast(other); + + // first compare the lengths of the series... + if (seq.size()>o.seq.size()) + return 1; + if (seq.size()compare(*o_it); + if (cmpval) + return cmpval; + ++it; + ++o_it; + } + + // so they are equal. + return 0; +} + +/** Return the number of operands including a possible order term. */ +size_t pseries::nops() const +{ + return seq.size(); +} + +/** Return the ith term in the series when represented as a sum. */ +ex pseries::op(size_t i) const +{ + if (i >= seq.size()) + throw (std::out_of_range("op() out of range")); + + if (is_exactly_a(seq[i].rest)) + return Order(power(var-point, seq[i].coeff)); + return seq[i].rest * power(var - point, seq[i].coeff); +} + +/** Return degree of highest power of the series. This is usually the exponent + * of the Order term. If s is not the expansion variable of the series, the + * series is examined termwise. */ +int pseries::degree(const ex &s) const +{ + if (var.is_equal(s)) { + // Return last exponent + if (seq.size()) + return ex_to((seq.end()-1)->coeff).to_int(); + else + return 0; + } else { + epvector::const_iterator it = seq.begin(), itend = seq.end(); + if (it == itend) + return 0; + int max_pow = INT_MIN; + while (it != itend) { + int pow = it->rest.degree(s); + if (pow > max_pow) + max_pow = pow; + ++it; + } + return max_pow; + } +} + +/** Return degree of lowest power of the series. This is usually the exponent + * of the leading term. If s is not the expansion variable of the series, the + * series is examined termwise. If s is the expansion variable but the + * expansion point is not zero the series is not expanded to find the degree. + * I.e.: (1-x) + (1-x)^2 + Order((1-x)^3) has ldegree(x) 1, not 0. */ +int pseries::ldegree(const ex &s) const +{ + if (var.is_equal(s)) { + // Return first exponent + if (seq.size()) + return ex_to((seq.begin())->coeff).to_int(); + else + return 0; + } else { + epvector::const_iterator it = seq.begin(), itend = seq.end(); + if (it == itend) + return 0; + int min_pow = INT_MAX; + while (it != itend) { + int pow = it->rest.ldegree(s); + if (pow < min_pow) + min_pow = pow; + ++it; + } + return min_pow; + } +} + +/** Return coefficient of degree n in power series if s is the expansion + * variable. If the expansion point is nonzero, by definition the n=1 + * coefficient in s of a+b*(s-z)+c*(s-z)^2+Order((s-z)^3) is b (assuming + * the expansion took place in the s in the first place). + * If s is not the expansion variable, an attempt is made to convert the + * series to a polynomial and return the corresponding coefficient from + * there. */ +ex pseries::coeff(const ex &s, int n) const +{ + if (var.is_equal(s)) { + if (seq.empty()) + return _ex0; + + // Binary search in sequence for given power + numeric looking_for = numeric(n); + int lo = 0, hi = seq.size() - 1; + while (lo <= hi) { + int mid = (lo + hi) / 2; + GINAC_ASSERT(is_exactly_a(seq[mid].coeff)); + int cmp = ex_to(seq[mid].coeff).compare(looking_for); + switch (cmp) { + case -1: + lo = mid + 1; + break; + case 0: + return seq[mid].rest; + case 1: + hi = mid - 1; + break; + default: + throw(std::logic_error("pseries::coeff: compare() didn't return -1, 0 or 1")); + } + } + return _ex0; + } else + return convert_to_poly().coeff(s, n); +} + +/** Does nothing. */ +ex pseries::collect(const ex &s, bool distributed) const +{ + return *this; +} + +/** Perform coefficient-wise automatic term rewriting rules in this class. */ +ex pseries::eval(int level) const +{ + if (level == 1) + return this->hold(); + + if (level == -max_recursion_level) + throw (std::runtime_error("pseries::eval(): recursion limit exceeded")); + + // Construct a new series with evaluated coefficients + epvector new_seq; + new_seq.reserve(seq.size()); + epvector::const_iterator it = seq.begin(), itend = seq.end(); + while (it != itend) { + new_seq.push_back(expair(it->rest.eval(level-1), it->coeff)); + ++it; + } + return (new pseries(relational(var,point), new_seq))->setflag(status_flags::dynallocated | status_flags::evaluated); +} + +/** Evaluate coefficients numerically. */ +ex pseries::evalf(int level) const +{ + if (level == 1) + return *this; + + if (level == -max_recursion_level) + throw (std::runtime_error("pseries::evalf(): recursion limit exceeded")); + + // Construct a new series with evaluated coefficients + epvector new_seq; + new_seq.reserve(seq.size()); + epvector::const_iterator it = seq.begin(), itend = seq.end(); + while (it != itend) { + new_seq.push_back(expair(it->rest.evalf(level-1), it->coeff)); + ++it; + } + return (new pseries(relational(var,point), new_seq))->setflag(status_flags::dynallocated | status_flags::evaluated); +} + +ex pseries::conjugate() const +{ + epvector * newseq = conjugateepvector(seq); + ex newvar = var.conjugate(); + ex newpoint = point.conjugate(); + + if (!newseq && are_ex_trivially_equal(newvar, var) && are_ex_trivially_equal(point, newpoint)) { + return *this; + } + + ex result = (new pseries(newvar==newpoint, newseq ? *newseq : seq))->setflag(status_flags::dynallocated); + if (newseq) { + delete newseq; + } + return result; +} + +ex pseries::eval_integ() const +{ + epvector *newseq = NULL; + for (epvector::const_iterator i=seq.begin(); i!=seq.end(); ++i) { + if (newseq) { + newseq->push_back(expair(i->rest.eval_integ(), i->coeff)); + continue; + } + ex newterm = i->rest.eval_integ(); + if (!are_ex_trivially_equal(newterm, i->rest)) { + newseq = new epvector; + newseq->reserve(seq.size()); + for (epvector::const_iterator j=seq.begin(); j!=i; ++j) + newseq->push_back(*j); + newseq->push_back(expair(newterm, i->coeff)); + } + } + + ex newpoint = point.eval_integ(); + if (newseq || !are_ex_trivially_equal(newpoint, point)) + return (new pseries(var==newpoint, *newseq)) + ->setflag(status_flags::dynallocated); + return *this; +} + +ex pseries::subs(const exmap & m, unsigned options) const +{ + // If expansion variable is being substituted, convert the series to a + // polynomial and do the substitution there because the result might + // no longer be a power series + if (m.find(var) != m.end()) + return convert_to_poly(true).subs(m, options); + + // Otherwise construct a new series with substituted coefficients and + // expansion point + epvector newseq; + newseq.reserve(seq.size()); + epvector::const_iterator it = seq.begin(), itend = seq.end(); + while (it != itend) { + newseq.push_back(expair(it->rest.subs(m, options), it->coeff)); + ++it; + } + return (new pseries(relational(var,point.subs(m, options)), newseq))->setflag(status_flags::dynallocated); +} + +/** Implementation of ex::expand() for a power series. It expands all the + * terms individually and returns the resulting series as a new pseries. */ +ex pseries::expand(unsigned options) const +{ + epvector newseq; + epvector::const_iterator i = seq.begin(), end = seq.end(); + while (i != end) { + ex restexp = i->rest.expand(); + if (!restexp.is_zero()) + newseq.push_back(expair(restexp, i->coeff)); + ++i; + } + return (new pseries(relational(var,point), newseq)) + ->setflag(status_flags::dynallocated | (options == 0 ? status_flags::expanded : 0)); +} + +/** Implementation of ex::diff() for a power series. + * @see ex::diff */ +ex pseries::derivative(const symbol & s) const +{ + epvector new_seq; + epvector::const_iterator it = seq.begin(), itend = seq.end(); + + if (s == var) { + + // FIXME: coeff might depend on var + while (it != itend) { + if (is_exactly_a(it->rest)) { + new_seq.push_back(expair(it->rest, it->coeff - 1)); + } else { + ex c = it->rest * it->coeff; + if (!c.is_zero()) + new_seq.push_back(expair(c, it->coeff - 1)); + } + ++it; + } + + } else { + + while (it != itend) { + if (is_exactly_a(it->rest)) { + new_seq.push_back(*it); + } else { + ex c = it->rest.diff(s); + if (!c.is_zero()) + new_seq.push_back(expair(c, it->coeff)); + } + ++it; + } + } + + return pseries(relational(var,point), new_seq); +} + +ex pseries::convert_to_poly(bool no_order) const +{ + ex e; + epvector::const_iterator it = seq.begin(), itend = seq.end(); + + while (it != itend) { + if (is_exactly_a(it->rest)) { + if (!no_order) + e += Order(power(var - point, it->coeff)); + } else + e += it->rest * power(var - point, it->coeff); + ++it; + } + return e; +} + +bool pseries::is_terminating() const +{ + return seq.empty() || !is_exactly_a((seq.end()-1)->rest); +} + +ex pseries::coeffop(size_t i) const +{ + if (i >=nops()) + throw (std::out_of_range("coeffop() out of range")); + return seq[i].rest; +} + +ex pseries::exponop(size_t i) const +{ + if (i >= nops()) + throw (std::out_of_range("exponop() out of range")); + return seq[i].coeff; +} + + +/* + * Implementations of series expansion + */ + +/** Default implementation of ex::series(). This performs Taylor expansion. + * @see ex::series */ +ex basic::series(const relational & r, int order, unsigned options) const +{ + epvector seq; + const symbol &s = ex_to(r.lhs()); + + // default for order-values that make no sense for Taylor expansion + if ((order <= 0) && this->has(s)) { + seq.push_back(expair(Order(_ex1), order)); + return pseries(r, seq); + } + + // do Taylor expansion + numeric fac = 1; + ex deriv = *this; + ex coeff = deriv.subs(r, subs_options::no_pattern); + + if (!coeff.is_zero()) { + seq.push_back(expair(coeff, _ex0)); + } + + int n; + for (n=1; n(r.lhs())); + + if (this->is_equal_same_type(ex_to(r.lhs()))) { + if (order > 0 && !point.is_zero()) + seq.push_back(expair(point, _ex0)); + if (order > 1) + seq.push_back(expair(_ex1, _ex1)); + else + seq.push_back(expair(Order(_ex1), numeric(order))); + } else + seq.push_back(expair(*this, _ex0)); + return pseries(r, seq); +} + + +/** Add one series object to another, producing a pseries object that + * represents the sum. + * + * @param other pseries object to add with + * @return the sum as a pseries */ +ex pseries::add_series(const pseries &other) const +{ + // Adding two series with different variables or expansion points + // results in an empty (constant) series + if (!is_compatible_to(other)) { + epvector nul; + nul.push_back(expair(Order(_ex1), _ex0)); + return pseries(relational(var,point), nul); + } + + // Series addition + epvector new_seq; + epvector::const_iterator a = seq.begin(); + epvector::const_iterator b = other.seq.begin(); + epvector::const_iterator a_end = seq.end(); + epvector::const_iterator b_end = other.seq.end(); + int pow_a = INT_MAX, pow_b = INT_MAX; + for (;;) { + // If a is empty, fill up with elements from b and stop + if (a == a_end) { + while (b != b_end) { + new_seq.push_back(*b); + ++b; + } + break; + } else + pow_a = ex_to((*a).coeff).to_int(); + + // If b is empty, fill up with elements from a and stop + if (b == b_end) { + while (a != a_end) { + new_seq.push_back(*a); + ++a; + } + break; + } else + pow_b = ex_to((*b).coeff).to_int(); + + // a and b are non-empty, compare powers + if (pow_a < pow_b) { + // a has lesser power, get coefficient from a + new_seq.push_back(*a); + if (is_exactly_a((*a).rest)) + break; + ++a; + } else if (pow_b < pow_a) { + // b has lesser power, get coefficient from b + new_seq.push_back(*b); + if (is_exactly_a((*b).rest)) + break; + ++b; + } else { + // Add coefficient of a and b + if (is_exactly_a((*a).rest) || is_exactly_a((*b).rest)) { + new_seq.push_back(expair(Order(_ex1), (*a).coeff)); + break; // Order term ends the sequence + } else { + ex sum = (*a).rest + (*b).rest; + if (!(sum.is_zero())) + new_seq.push_back(expair(sum, numeric(pow_a))); + ++a; + ++b; + } + } + } + return pseries(relational(var,point), new_seq); +} + + +/** Implementation of ex::series() for sums. This performs series addition when + * adding pseries objects. + * @see ex::series */ +ex add::series(const relational & r, int order, unsigned options) const +{ + ex acc; // Series accumulator + + // Get first term from overall_coeff + acc = overall_coeff.series(r, order, options); + + // Add remaining terms + epvector::const_iterator it = seq.begin(); + epvector::const_iterator itend = seq.end(); + for (; it!=itend; ++it) { + ex op; + if (is_exactly_a(it->rest)) + op = it->rest; + else + op = it->rest.series(r, order, options); + if (!it->coeff.is_equal(_ex1)) + op = ex_to(op).mul_const(ex_to(it->coeff)); + + // Series addition + acc = ex_to(acc).add_series(ex_to(op)); + } + return acc; +} + + +/** Multiply a pseries object with a numeric constant, producing a pseries + * object that represents the product. + * + * @param other constant to multiply with + * @return the product as a pseries */ +ex pseries::mul_const(const numeric &other) const +{ + epvector new_seq; + new_seq.reserve(seq.size()); + + epvector::const_iterator it = seq.begin(), itend = seq.end(); + while (it != itend) { + if (!is_exactly_a(it->rest)) + new_seq.push_back(expair(it->rest * other, it->coeff)); + else + new_seq.push_back(*it); + ++it; + } + return pseries(relational(var,point), new_seq); +} + + +/** Multiply one pseries object to another, producing a pseries object that + * represents the product. + * + * @param other pseries object to multiply with + * @return the product as a pseries */ +ex pseries::mul_series(const pseries &other) const +{ + // Multiplying two series with different variables or expansion points + // results in an empty (constant) series + if (!is_compatible_to(other)) { + epvector nul; + nul.push_back(expair(Order(_ex1), _ex0)); + return pseries(relational(var,point), nul); + } + + if (seq.empty() || other.seq.empty()) { + return (new pseries(var==point, epvector())) + ->setflag(status_flags::dynallocated); + } + + // Series multiplication + epvector new_seq; + int a_max = degree(var); + int b_max = other.degree(var); + int a_min = ldegree(var); + int b_min = other.ldegree(var); + int cdeg_min = a_min + b_min; + int cdeg_max = a_max + b_max; + + int higher_order_a = INT_MAX; + int higher_order_b = INT_MAX; + if (is_exactly_a(coeff(var, a_max))) + higher_order_a = a_max + b_min; + if (is_exactly_a(other.coeff(var, b_max))) + higher_order_b = b_max + a_min; + int higher_order_c = std::min(higher_order_a, higher_order_b); + if (cdeg_max >= higher_order_c) + cdeg_max = higher_order_c - 1; + + for (int cdeg=cdeg_min; cdeg<=cdeg_max; ++cdeg) { + ex co = _ex0; + // c(i)=a(0)b(i)+...+a(i)b(0) + for (int i=a_min; cdeg-i>=b_min; ++i) { + ex a_coeff = coeff(var, i); + ex b_coeff = other.coeff(var, cdeg-i); + if (!is_exactly_a(a_coeff) && !is_exactly_a(b_coeff)) + co += a_coeff * b_coeff; + } + if (!co.is_zero()) + new_seq.push_back(expair(co, numeric(cdeg))); + } + if (higher_order_c < INT_MAX) + new_seq.push_back(expair(Order(_ex1), numeric(higher_order_c))); + return pseries(relational(var, point), new_seq); +} + + +/** Implementation of ex::series() for product. This performs series + * multiplication when multiplying series. + * @see ex::series */ +ex mul::series(const relational & r, int order, unsigned options) const +{ + pseries acc; // Series accumulator + + GINAC_ASSERT(is_a(r.lhs())); + const ex& sym = r.lhs(); + + // holds ldegrees of the series of individual factors + std::vector ldegrees; + + // find minimal degrees + const epvector::const_iterator itbeg = seq.begin(); + const epvector::const_iterator itend = seq.end(); + for (epvector::const_iterator it=itbeg; it!=itend; ++it) { + + ex expon = it->coeff; + int factor = 1; + ex buf; + if (expon.info(info_flags::integer)) { + buf = it->rest; + factor = ex_to(expon).to_int(); + } else { + buf = recombine_pair_to_ex(*it); + } + + int real_ldegree = 0; + try { + real_ldegree = buf.expand().ldegree(sym-r.rhs()); + } catch (std::runtime_error) {} + + if (real_ldegree == 0) { + int orderloop = 0; + do { + orderloop++; + real_ldegree = buf.series(r, orderloop, options).ldegree(sym); + } while (real_ldegree == orderloop); + } + + ldegrees.push_back(factor * real_ldegree); + } + + int degsum = std::accumulate(ldegrees.begin(), ldegrees.end(), 0); + + if (degsum >= order) { + epvector epv; + epv.push_back(expair(Order(_ex1), order)); + return (new pseries(r, epv))->setflag(status_flags::dynallocated); + } + + // Multiply with remaining terms + std::vector::const_iterator itd = ldegrees.begin(); + for (epvector::const_iterator it=itbeg; it!=itend; ++it, ++itd) { + + // do series expansion with adjusted order + ex op = recombine_pair_to_ex(*it).series(r, order-degsum+(*itd), options); + + // Series multiplication + if (it == itbeg) + acc = ex_to(op); + else + acc = ex_to(acc.mul_series(ex_to(op))); + } + + return acc.mul_const(ex_to(overall_coeff)); +} + + +/** Compute the p-th power of a series. + * + * @param p power to compute + * @param deg truncation order of series calculation */ +ex pseries::power_const(const numeric &p, int deg) const +{ + // method: + // (due to Leonhard Euler) + // let A(x) be this series and for the time being let it start with a + // constant (later we'll generalize): + // A(x) = a_0 + a_1*x + a_2*x^2 + ... + // We want to compute + // C(x) = A(x)^p + // C(x) = c_0 + c_1*x + c_2*x^2 + ... + // Taking the derivative on both sides and multiplying with A(x) one + // immediately arrives at + // C'(x)*A(x) = p*C(x)*A'(x) + // Multiplying this out and comparing coefficients we get the recurrence + // formula + // c_i = (i*p*a_i*c_0 + ((i-1)*p-1)*a_{i-1}*c_1 + ... + // ... + (p-(i-1))*a_1*c_{i-1})/(a_0*i) + // which can easily be solved given the starting value c_0 = (a_0)^p. + // For the more general case where the leading coefficient of A(x) is not + // a constant, just consider A2(x) = A(x)*x^m, with some integer m and + // repeat the above derivation. The leading power of C2(x) = A2(x)^2 is + // then of course x^(p*m) but the recurrence formula still holds. + + if (seq.empty()) { + // as a special case, handle the empty (zero) series honoring the + // usual power laws such as implemented in power::eval() + if (p.real().is_zero()) + throw std::domain_error("pseries::power_const(): pow(0,I) is undefined"); + else if (p.real().is_negative()) + throw pole_error("pseries::power_const(): division by zero",1); + else + return *this; + } + + const int ldeg = ldegree(var); + if (!(p*ldeg).is_integer()) + throw std::runtime_error("pseries::power_const(): trying to assemble a Puiseux series"); + + // adjust number of coefficients + int numcoeff = deg - (p*ldeg).to_int(); + if (numcoeff <= 0) { + epvector epv; + epv.reserve(1); + epv.push_back(expair(Order(_ex1), deg)); + return (new pseries(relational(var,point), epv)) + ->setflag(status_flags::dynallocated); + } + + // O(x^n)^(-m) is undefined + if (seq.size() == 1 && is_exactly_a(seq[0].rest) && p.real().is_negative()) + throw pole_error("pseries::power_const(): division by zero",1); + + // Compute coefficients of the powered series + exvector co; + co.reserve(numcoeff); + co.push_back(power(coeff(var, ldeg), p)); + for (int i=1; i(c)) { + co.push_back(Order(_ex1)); + break; + } else + sum += (p * j - (i - j)) * co[i - j] * c; + } + co.push_back(sum / coeff(var, ldeg) / i); + } + + // Construct new series (of non-zero coefficients) + epvector new_seq; + bool higher_order = false; + for (int i=0; i(co[i])) { + higher_order = true; + break; + } + } + if (!higher_order) + new_seq.push_back(expair(Order(_ex1), p * ldeg + numcoeff)); + + return pseries(relational(var,point), new_seq); +} + + +/** Return a new pseries object with the powers shifted by deg. */ +pseries pseries::shift_exponents(int deg) const +{ + epvector newseq = seq; + epvector::iterator i = newseq.begin(), end = newseq.end(); + while (i != end) { + i->coeff += deg; + ++i; + } + return pseries(relational(var, point), newseq); +} + + +/** Implementation of ex::series() for powers. This performs Laurent expansion + * of reciprocals of series at singularities. + * @see ex::series */ +ex power::series(const relational & r, int order, unsigned options) const +{ + // If basis is already a series, just power it + if (is_exactly_a(basis)) + return ex_to(basis).power_const(ex_to(exponent), order); + + // Basis is not a series, may there be a singularity? + bool must_expand_basis = false; + try { + basis.subs(r, subs_options::no_pattern); + } catch (pole_error) { + must_expand_basis = true; + } + + // Is the expression of type something^(-int)? + if (!must_expand_basis && !exponent.info(info_flags::negint) + && (!is_a(basis) || !is_a(exponent))) + return basic::series(r, order, options); + + // Is the expression of type 0^something? + if (!must_expand_basis && !basis.subs(r, subs_options::no_pattern).is_zero() + && (!is_a(basis) || !is_a(exponent))) + return basic::series(r, order, options); + + // Singularity encountered, is the basis equal to (var - point)? + if (basis.is_equal(r.lhs() - r.rhs())) { + epvector new_seq; + if (ex_to(exponent).to_int() < order) + new_seq.push_back(expair(_ex1, exponent)); + else + new_seq.push_back(expair(Order(_ex1), exponent)); + return pseries(r, new_seq); + } + + // No, expand basis into series + + numeric numexp; + if (is_a(exponent)) { + numexp = ex_to(exponent); + } else { + numexp = 0; + } + const ex& sym = r.lhs(); + // find existing minimal degree + int real_ldegree = basis.expand().ldegree(sym-r.rhs()); + if (real_ldegree == 0) { + int orderloop = 0; + do { + orderloop++; + real_ldegree = basis.series(r, orderloop, options).ldegree(sym); + } while (real_ldegree == orderloop); + } + + if (!(real_ldegree*numexp).is_integer()) + throw std::runtime_error("pseries::power_const(): trying to assemble a Puiseux series"); + ex e = basis.series(r, (order + real_ldegree*(1-numexp)).to_int(), options); + + ex result; + try { + result = ex_to(e).power_const(numexp, order); + } catch (pole_error) { + epvector ser; + ser.push_back(expair(Order(_ex1), order)); + result = pseries(r, ser); + } + + return result; +} + + +/** Re-expansion of a pseries object. */ +ex pseries::series(const relational & r, int order, unsigned options) const +{ + const ex p = r.rhs(); + GINAC_ASSERT(is_a(r.lhs())); + const symbol &s = ex_to(r.lhs()); + + if (var.is_equal(s) && point.is_equal(p)) { + if (order > degree(s)) + return *this; + else { + epvector new_seq; + epvector::const_iterator it = seq.begin(), itend = seq.end(); + while (it != itend) { + int o = ex_to(it->coeff).to_int(); + if (o >= order) { + new_seq.push_back(expair(Order(_ex1), o)); + break; + } + new_seq.push_back(*it); + ++it; + } + return pseries(r, new_seq); + } + } else + return convert_to_poly().series(r, order, options); +} + +ex integral::series(const relational & r, int order, unsigned options) const +{ + if (x.subs(r) != x) + throw std::logic_error("Cannot series expand wrt dummy variable"); + + // Expanding integrant with r substituted taken in boundaries. + ex fseries = f.series(r, order, options); + epvector fexpansion; + fexpansion.reserve(fseries.nops()); + for (size_t i=0; i(fseries).coeffop(i); + currcoeff = (currcoeff == Order(_ex1)) + ? currcoeff + : integral(x, a.subs(r), b.subs(r), currcoeff); + if (currcoeff != 0) + fexpansion.push_back( + expair(currcoeff, ex_to(fseries).exponop(i))); + } + + // Expanding lower boundary + ex result = (new pseries(r, fexpansion))->setflag(status_flags::dynallocated); + ex aseries = (a-a.subs(r)).series(r, order, options); + fseries = f.series(x == (a.subs(r)), order, options); + for (size_t i=0; i(fseries).coeffop(i); + if (is_exactly_a(currcoeff)) + break; + ex currexpon = ex_to(fseries).exponop(i); + int orderforf = order-ex_to(currexpon).to_int()-1; + currcoeff = currcoeff.series(r, orderforf); + ex term = ex_to(aseries).power_const(ex_to(currexpon+1),order); + term = ex_to(term).mul_const(ex_to(-1/(currexpon+1))); + term = ex_to(term).mul_series(ex_to(currcoeff)); + result = ex_to(result).add_series(ex_to(term)); + } + + // Expanding upper boundary + ex bseries = (b-b.subs(r)).series(r, order, options); + fseries = f.series(x == (b.subs(r)), order, options); + for (size_t i=0; i(fseries).coeffop(i); + if (is_exactly_a(currcoeff)) + break; + ex currexpon = ex_to(fseries).exponop(i); + int orderforf = order-ex_to(currexpon).to_int()-1; + currcoeff = currcoeff.series(r, orderforf); + ex term = ex_to(bseries).power_const(ex_to(currexpon+1),order); + term = ex_to(term).mul_const(ex_to(1/(currexpon+1))); + term = ex_to(term).mul_series(ex_to(currcoeff)); + result = ex_to(result).add_series(ex_to(term)); + } + + return result; +} + + +/** Compute the truncated series expansion of an expression. + * This function returns an expression containing an object of class pseries + * to represent the series. If the series does not terminate within the given + * truncation order, the last term of the series will be an order term. + * + * @param r expansion relation, lhs holds variable and rhs holds point + * @param order truncation order of series calculations + * @param options of class series_options + * @return an expression holding a pseries object */ +ex ex::series(const ex & r, int order, unsigned options) const +{ + ex e; + relational rel_; + + if (is_a(r)) + rel_ = ex_to(r); + else if (is_a(r)) + rel_ = relational(r,_ex0); + else + throw (std::logic_error("ex::series(): expansion point has unknown type")); + + try { + e = bp->series(rel_, order, options); + } catch (std::exception &x) { + throw (std::logic_error(std::string("unable to compute series (") + x.what() + ")")); + } + return e; +} + +} // namespace GiNaC diff --git a/ginac/registrar.cpp b/ginac/registrar.cpp new file mode 100644 index 00000000..13e17362 --- /dev/null +++ b/ginac/registrar.cpp @@ -0,0 +1,46 @@ +/** @file registrar.cpp + * + * GiNaC's class registrar (for class basic and all classes derived from it). */ + +/* + * GiNaC Copyright (C) 1999-2005 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include + +#include "registrar.h" + +namespace GiNaC { + +tinfo_t find_tinfo_key(const std::string &class_name) +{ + return registered_class_info::find(class_name)->options.get_id(); +} + +unarch_func find_unarch_func(const std::string &class_name) +{ + return registered_class_info::find(class_name)->options.get_unarch_func(); +} + +factory_p find_func_factory(const std::string& class_name) +{ + return registered_class_info::find(class_name)->options.get_func_factory(); +} + +} // namespace GiNaC diff --git a/ginac/registrar.h b/ginac/registrar.h new file mode 100644 index 00000000..bcd918eb --- /dev/null +++ b/ginac/registrar.h @@ -0,0 +1,200 @@ +/** @file registrar.h + * + * GiNaC's class registrar (for class basic and all classes derived from it). */ + +/* + * GiNaC Copyright (C) 1999-2006 Johannes Gutenberg University Mainz, Germany + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __GINAC_REGISTRAR_H__ +#define __GINAC_REGISTRAR_H__ + +#include +#include +#include + +#include "class_info.h" +#include "print.h" + +namespace GiNaC { + +class ex; +class archive_node; + +template