static ex atan_series(const ex &arg, const relational &rel, int order, unsigned options) { GINAC_ASSERT(is_a<symbol>(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)) throw do_taylor(); // Re(x) != 0 if ((I*arg_pt).info(info_flags::real) && abs(I*arg_pt)<_ex1) throw do_taylor(); // Re(x) == 0, but abs(x)<1 // 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<symbol>(rel.lhs()); const ex &point = rel.rhs(); const symbol foo; const ex replarg = series(atan(arg), 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; if (order > 0) { seq.reserve(2); seq.push_back(expair(Order0correction, _ex0)); } seq.push_back(expair(Order(_ex1), order)); return series(replarg - pseries(rel, std::move(seq)), rel, order); } throw do_taylor(); }
static ex tan_series(const ex &x, const relational &rel, int order, unsigned options) { GINAC_ASSERT(is_a<symbol>(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 = x.subs(rel, subs_options::no_pattern); if (!(2*x_pt/Pi).info(info_flags::odd)) throw do_taylor(); // caught by function::series() // if we got here we have to care for a simple pole return (sin(x)/cos(x)).series(rel, order, options); }
static ex zeta1_series(const ex& m, const relational& rel, int order, unsigned options) { // use taylor expansion everywhere except at the singularity at 1 const numeric val = ex_to<numeric>(m.subs(rel, subs_options::no_pattern)); if (val != 1) throw do_taylor(); // caught by function::series() // at 1, use the expansion with the stieltjes-constants ex ser = 1/(m-1); numeric fac = 1; for (int n = 0; n <= order; ++n) { fac = fac.mul(n+1); ser += pow(-1, n) * stieltjes(n) * pow(m-1, n) * fac.inverse(); } return ser.series(rel, order, options); }
static ex log_series(const ex &arg, const relational &rel, int order, unsigned options) { GINAC_ASSERT(is_a<symbol>(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 (arg.diff(ex_to<symbol>(rel.lhs())).is_zero()) { throw do_taylor(); } 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<pseries>(arg.series(rel, order+extra_ord, options)); ++extra_ord; } while (!argser.is_terminating() && argser.nops()==1); const symbol &s = ex_to<symbol>(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) { ex rest = pseries(rel, epvector{expair(-1, _ex0), expair(Order(_ex1), order)}).add_series(argser); ex acc = dynallocate<pseries>(rel, epvector()); for (int i = order-1; i>0; --i) { epvector cterm { expair(i%2 ? _ex1/i : _ex_1/i, _ex0) }; acc = pseries(rel, std::move(cterm)).add_series(ex_to<pseries>(acc)); acc = (ex_to<pseries>(rest)).mul_series(ex_to<pseries>(acc)); } return acc; } const ex newarg = ex_to<pseries>((arg/coeff).series(rel, order+n, options)).shift_exponents(-n).convert_to_poly(true); return pseries(rel, std::move(seq)).add_series(ex_to<pseries>(log(newarg).series(rel, order, options))); } else // it was a monomial return pseries(rel, std::move(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<symbol>(rel.lhs()); const ex &point = rel.rhs(); const symbol foo; const ex replarg = series(log(arg), s==foo, order).subs(foo==point, subs_options::no_pattern); epvector seq; if (order > 0) { seq.reserve(2); seq.push_back(expair(-I*csgn(arg*I)*Pi, _ex0)); } seq.push_back(expair(Order(_ex1), order)); return series(replarg - I*Pi + pseries(rel, std::move(seq)), rel, order); } throw do_taylor(); // caught by function::series() }
static ex Li2_series(const ex &x, const relational &rel, int order, unsigned options) { const ex x_pt = x.subs(rel, subs_options::no_pattern); if (x_pt.info(info_flags::numeric)) { // First special case: x==0 (derivatives have poles) if (x_pt.is_zero()) { // method: // The problem is that in d/dx Li2(x==0) == -log(1-x)/x we cannot // simply substitute x==0. The limit, however, exists: it is 1. // We also know all higher derivatives' limits: // (d/dx)^n Li2(x) == n!/n^2. // So the primitive series expansion is // Li2(x==0) == x + x^2/4 + x^3/9 + ... // and so on. // We first construct such a primitive series expansion manually in // a dummy symbol s and then insert the argument's series expansion // for s. Reexpanding the resulting series returns the desired // result. const symbol s; ex ser; // manually construct the primitive expansion for (int i=1; i<order; ++i) ser += pow(s,i) / pow(numeric(i), *_num2_p); // substitute the argument's series expansion ser = ser.subs(s==x.series(rel, order), subs_options::no_pattern); // maybe that was terminating, so add a proper order term epvector nseq { expair(Order(_ex1), order) }; ser += pseries(rel, std::move(nseq)); // reexpanding it will collapse the series again return ser.series(rel, order); // NB: Of course, this still does not allow us to compute anything // like sin(Li2(x)).series(x==0,2), since then this code here is // not reached and the derivative of sin(Li2(x)) doesn't allow the // substitution x==0. Probably limits *are* needed for the general // cases. In case L'Hospital's rule is implemented for limits and // basic::series() takes care of this, this whole block is probably // obsolete! } // 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<order; ++i) ser += pow(1-s,i) * (numeric(1,i)*(I*Pi+log(s-1)) - numeric(1,i*i)); // substitute the argument's series expansion ser = ser.subs(s==x.series(rel, order), subs_options::no_pattern); // maybe that was terminating, so add a proper order term epvector nseq { expair(Order(_ex1), order) }; ser += pseries(rel, std::move(nseq)); // reexpanding it will collapse the series again return ser.series(rel, order); } // third special case: x real, >=1 (branch cut) if (!(options & series_options::suppress_branchcut) && ex_to<numeric>(x_pt).is_real() && ex_to<numeric>(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<symbol>(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 = series(Li2(x), s==foo, order); for (size_t i=1; i<replarg.nops()-1; ++i) seq.push_back(expair((replarg.op(i)/power(s-foo,i)).series(foo==point,1,options).op(0).subs(foo==s, subs_options::no_pattern),i)); // append an order term: seq.push_back(expair(Order(_ex1), replarg.nops()-1)); return pseries(rel, std::move(seq)); } } // all other cases should be safe, by now: throw do_taylor(); // caught by function::series() }