chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
@@ -0,0 +1,496 @@
|
||||
"""Algorithms for partial fraction decomposition of rational functions. """
|
||||
|
||||
|
||||
from sympy.core import S, Add, sympify, Function, Lambda, Dummy
|
||||
from sympy.core.traversal import preorder_traversal
|
||||
from sympy.polys import Poly, RootSum, cancel, factor
|
||||
from sympy.polys.polyerrors import PolynomialError
|
||||
from sympy.polys.polyoptions import allowed_flags, set_defaults
|
||||
from sympy.polys.polytools import parallel_poly_from_expr
|
||||
from sympy.utilities import numbered_symbols, take, xthreaded, public
|
||||
|
||||
|
||||
@xthreaded
|
||||
@public
|
||||
def apart(f, x=None, full=False, **options):
|
||||
"""
|
||||
Compute partial fraction decomposition of a rational function.
|
||||
|
||||
Given a rational function ``f``, computes the partial fraction
|
||||
decomposition of ``f``. Two algorithms are available: One is based on the
|
||||
undetermined coefficients method, the other is Bronstein's full partial
|
||||
fraction decomposition algorithm.
|
||||
|
||||
The undetermined coefficients method (selected by ``full=False``) uses
|
||||
polynomial factorization (and therefore accepts the same options as
|
||||
factor) for the denominator. Per default it works over the rational
|
||||
numbers, therefore decomposition of denominators with non-rational roots
|
||||
(e.g. irrational, complex roots) is not supported by default (see options
|
||||
of factor).
|
||||
|
||||
Bronstein's algorithm can be selected by using ``full=True`` and allows a
|
||||
decomposition of denominators with non-rational roots. A human-readable
|
||||
result can be obtained via ``doit()`` (see examples below).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.polys.partfrac import apart
|
||||
>>> from sympy.abc import x, y
|
||||
|
||||
By default, using the undetermined coefficients method:
|
||||
|
||||
>>> apart(y/(x + 2)/(x + 1), x)
|
||||
-y/(x + 2) + y/(x + 1)
|
||||
|
||||
The undetermined coefficients method does not provide a result when the
|
||||
denominators roots are not rational:
|
||||
|
||||
>>> apart(y/(x**2 + x + 1), x)
|
||||
y/(x**2 + x + 1)
|
||||
|
||||
You can choose Bronstein's algorithm by setting ``full=True``:
|
||||
|
||||
>>> apart(y/(x**2 + x + 1), x, full=True)
|
||||
RootSum(_w**2 + _w + 1, Lambda(_a, (-2*_a*y/3 - y/3)/(-_a + x)))
|
||||
|
||||
Calling ``doit()`` yields a human-readable result:
|
||||
|
||||
>>> apart(y/(x**2 + x + 1), x, full=True).doit()
|
||||
(-y/3 - 2*y*(-1/2 - sqrt(3)*I/2)/3)/(x + 1/2 + sqrt(3)*I/2) + (-y/3 -
|
||||
2*y*(-1/2 + sqrt(3)*I/2)/3)/(x + 1/2 - sqrt(3)*I/2)
|
||||
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
apart_list, assemble_partfrac_list
|
||||
"""
|
||||
allowed_flags(options, [])
|
||||
|
||||
f = sympify(f)
|
||||
|
||||
if f.is_Atom:
|
||||
return f
|
||||
else:
|
||||
P, Q = f.as_numer_denom()
|
||||
|
||||
_options = options.copy()
|
||||
options = set_defaults(options, extension=True)
|
||||
try:
|
||||
(P, Q), opt = parallel_poly_from_expr((P, Q), x, **options)
|
||||
except PolynomialError as msg:
|
||||
if f.is_commutative:
|
||||
raise PolynomialError(msg)
|
||||
# non-commutative
|
||||
if f.is_Mul:
|
||||
c, nc = f.args_cnc(split_1=False)
|
||||
nc = f.func(*nc)
|
||||
if c:
|
||||
c = apart(f.func._from_args(c), x=x, full=full, **_options)
|
||||
return c*nc
|
||||
else:
|
||||
return nc
|
||||
elif f.is_Add:
|
||||
c = []
|
||||
nc = []
|
||||
for i in f.args:
|
||||
if i.is_commutative:
|
||||
c.append(i)
|
||||
else:
|
||||
try:
|
||||
nc.append(apart(i, x=x, full=full, **_options))
|
||||
except NotImplementedError:
|
||||
nc.append(i)
|
||||
return apart(f.func(*c), x=x, full=full, **_options) + f.func(*nc)
|
||||
else:
|
||||
reps = []
|
||||
pot = preorder_traversal(f)
|
||||
next(pot)
|
||||
for e in pot:
|
||||
try:
|
||||
reps.append((e, apart(e, x=x, full=full, **_options)))
|
||||
pot.skip() # this was handled successfully
|
||||
except NotImplementedError:
|
||||
pass
|
||||
return f.xreplace(dict(reps))
|
||||
|
||||
if P.is_multivariate:
|
||||
fc = f.cancel()
|
||||
if fc != f:
|
||||
return apart(fc, x=x, full=full, **_options)
|
||||
|
||||
raise NotImplementedError(
|
||||
"multivariate partial fraction decomposition")
|
||||
|
||||
common, P, Q = P.cancel(Q)
|
||||
|
||||
poly, P = P.div(Q, auto=True)
|
||||
P, Q = P.rat_clear_denoms(Q)
|
||||
|
||||
if Q.degree() <= 1:
|
||||
partial = P/Q
|
||||
else:
|
||||
if not full:
|
||||
partial = apart_undetermined_coeffs(P, Q)
|
||||
else:
|
||||
partial = apart_full_decomposition(P, Q)
|
||||
|
||||
terms = S.Zero
|
||||
|
||||
for term in Add.make_args(partial):
|
||||
if term.has(RootSum):
|
||||
terms += term
|
||||
else:
|
||||
terms += factor(term)
|
||||
|
||||
return common*(poly.as_expr() + terms)
|
||||
|
||||
|
||||
def apart_undetermined_coeffs(P, Q):
|
||||
"""Partial fractions via method of undetermined coefficients. """
|
||||
X = numbered_symbols(cls=Dummy)
|
||||
partial, symbols = [], []
|
||||
|
||||
_, factors = Q.factor_list()
|
||||
|
||||
for f, k in factors:
|
||||
n, q = f.degree(), Q
|
||||
|
||||
for i in range(1, k + 1):
|
||||
coeffs, q = take(X, n), q.quo(f)
|
||||
partial.append((coeffs, q, f, i))
|
||||
symbols.extend(coeffs)
|
||||
|
||||
dom = Q.get_domain().inject(*symbols)
|
||||
F = Poly(0, Q.gen, domain=dom)
|
||||
|
||||
for i, (coeffs, q, f, k) in enumerate(partial):
|
||||
h = Poly(coeffs, Q.gen, domain=dom)
|
||||
partial[i] = (h, f, k)
|
||||
q = q.set_domain(dom)
|
||||
F += h*q
|
||||
|
||||
system, result = [], S.Zero
|
||||
|
||||
for (k,), coeff in F.terms():
|
||||
system.append(coeff - P.nth(k))
|
||||
|
||||
from sympy.solvers import solve
|
||||
solution = solve(system, symbols)
|
||||
|
||||
for h, f, k in partial:
|
||||
h = h.as_expr().subs(solution)
|
||||
result += h/f.as_expr()**k
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def apart_full_decomposition(P, Q):
|
||||
"""
|
||||
Bronstein's full partial fraction decomposition algorithm.
|
||||
|
||||
Given a univariate rational function ``f``, performing only GCD
|
||||
operations over the algebraic closure of the initial ground domain
|
||||
of definition, compute full partial fraction decomposition with
|
||||
fractions having linear denominators.
|
||||
|
||||
Note that no factorization of the initial denominator of ``f`` is
|
||||
performed. The final decomposition is formed in terms of a sum of
|
||||
:class:`RootSum` instances.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] [Bronstein93]_
|
||||
|
||||
"""
|
||||
return assemble_partfrac_list(apart_list(P/Q, P.gens[0]))
|
||||
|
||||
|
||||
@public
|
||||
def apart_list(f, x=None, dummies=None, **options):
|
||||
"""
|
||||
Compute partial fraction decomposition of a rational function
|
||||
and return the result in structured form.
|
||||
|
||||
Given a rational function ``f`` compute the partial fraction decomposition
|
||||
of ``f``. Only Bronstein's full partial fraction decomposition algorithm
|
||||
is supported by this method. The return value is highly structured and
|
||||
perfectly suited for further algorithmic treatment rather than being
|
||||
human-readable. The function returns a tuple holding three elements:
|
||||
|
||||
* The first item is the common coefficient, free of the variable `x` used
|
||||
for decomposition. (It is an element of the base field `K`.)
|
||||
|
||||
* The second item is the polynomial part of the decomposition. This can be
|
||||
the zero polynomial. (It is an element of `K[x]`.)
|
||||
|
||||
* The third part itself is a list of quadruples. Each quadruple
|
||||
has the following elements in this order:
|
||||
|
||||
- The (not necessarily irreducible) polynomial `D` whose roots `w_i` appear
|
||||
in the linear denominator of a bunch of related fraction terms. (This item
|
||||
can also be a list of explicit roots. However, at the moment ``apart_list``
|
||||
never returns a result this way, but the related ``assemble_partfrac_list``
|
||||
function accepts this format as input.)
|
||||
|
||||
- The numerator of the fraction, written as a function of the root `w`
|
||||
|
||||
- The linear denominator of the fraction *excluding its power exponent*,
|
||||
written as a function of the root `w`.
|
||||
|
||||
- The power to which the denominator has to be raised.
|
||||
|
||||
On can always rebuild a plain expression by using the function ``assemble_partfrac_list``.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
A first example:
|
||||
|
||||
>>> from sympy.polys.partfrac import apart_list, assemble_partfrac_list
|
||||
>>> from sympy.abc import x, t
|
||||
|
||||
>>> f = (2*x**3 - 2*x) / (x**2 - 2*x + 1)
|
||||
>>> pfd = apart_list(f)
|
||||
>>> pfd
|
||||
(1,
|
||||
Poly(2*x + 4, x, domain='ZZ'),
|
||||
[(Poly(_w - 1, _w, domain='ZZ'), Lambda(_a, 4), Lambda(_a, -_a + x), 1)])
|
||||
|
||||
>>> assemble_partfrac_list(pfd)
|
||||
2*x + 4 + 4/(x - 1)
|
||||
|
||||
Second example:
|
||||
|
||||
>>> f = (-2*x - 2*x**2) / (3*x**2 - 6*x)
|
||||
>>> pfd = apart_list(f)
|
||||
>>> pfd
|
||||
(-1,
|
||||
Poly(2/3, x, domain='QQ'),
|
||||
[(Poly(_w - 2, _w, domain='ZZ'), Lambda(_a, 2), Lambda(_a, -_a + x), 1)])
|
||||
|
||||
>>> assemble_partfrac_list(pfd)
|
||||
-2/3 - 2/(x - 2)
|
||||
|
||||
Another example, showing symbolic parameters:
|
||||
|
||||
>>> pfd = apart_list(t/(x**2 + x + t), x)
|
||||
>>> pfd
|
||||
(1,
|
||||
Poly(0, x, domain='ZZ[t]'),
|
||||
[(Poly(_w**2 + _w + t, _w, domain='ZZ[t]'),
|
||||
Lambda(_a, -2*_a*t/(4*t - 1) - t/(4*t - 1)),
|
||||
Lambda(_a, -_a + x),
|
||||
1)])
|
||||
|
||||
>>> assemble_partfrac_list(pfd)
|
||||
RootSum(_w**2 + _w + t, Lambda(_a, (-2*_a*t/(4*t - 1) - t/(4*t - 1))/(-_a + x)))
|
||||
|
||||
This example is taken from Bronstein's original paper:
|
||||
|
||||
>>> f = 36 / (x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2)
|
||||
>>> pfd = apart_list(f)
|
||||
>>> pfd
|
||||
(1,
|
||||
Poly(0, x, domain='ZZ'),
|
||||
[(Poly(_w - 2, _w, domain='ZZ'), Lambda(_a, 4), Lambda(_a, -_a + x), 1),
|
||||
(Poly(_w**2 - 1, _w, domain='ZZ'), Lambda(_a, -3*_a - 6), Lambda(_a, -_a + x), 2),
|
||||
(Poly(_w + 1, _w, domain='ZZ'), Lambda(_a, -4), Lambda(_a, -_a + x), 1)])
|
||||
|
||||
>>> assemble_partfrac_list(pfd)
|
||||
-4/(x + 1) - 3/(x + 1)**2 - 9/(x - 1)**2 + 4/(x - 2)
|
||||
|
||||
See also
|
||||
========
|
||||
|
||||
apart, assemble_partfrac_list
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] [Bronstein93]_
|
||||
|
||||
"""
|
||||
allowed_flags(options, [])
|
||||
|
||||
f = sympify(f)
|
||||
|
||||
if f.is_Atom:
|
||||
return f
|
||||
else:
|
||||
P, Q = f.as_numer_denom()
|
||||
|
||||
options = set_defaults(options, extension=True)
|
||||
(P, Q), opt = parallel_poly_from_expr((P, Q), x, **options)
|
||||
|
||||
if P.is_multivariate:
|
||||
raise NotImplementedError(
|
||||
"multivariate partial fraction decomposition")
|
||||
|
||||
common, P, Q = P.cancel(Q)
|
||||
|
||||
poly, P = P.div(Q, auto=True)
|
||||
P, Q = P.rat_clear_denoms(Q)
|
||||
|
||||
polypart = poly
|
||||
|
||||
if dummies is None:
|
||||
def dummies(name):
|
||||
d = Dummy(name)
|
||||
while True:
|
||||
yield d
|
||||
|
||||
dummies = dummies("w")
|
||||
|
||||
rationalpart = apart_list_full_decomposition(P, Q, dummies)
|
||||
|
||||
return (common, polypart, rationalpart)
|
||||
|
||||
|
||||
def apart_list_full_decomposition(P, Q, dummygen):
|
||||
"""
|
||||
Bronstein's full partial fraction decomposition algorithm.
|
||||
|
||||
Given a univariate rational function ``f``, performing only GCD
|
||||
operations over the algebraic closure of the initial ground domain
|
||||
of definition, compute full partial fraction decomposition with
|
||||
fractions having linear denominators.
|
||||
|
||||
Note that no factorization of the initial denominator of ``f`` is
|
||||
performed. The final decomposition is formed in terms of a sum of
|
||||
:class:`RootSum` instances.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] [Bronstein93]_
|
||||
|
||||
"""
|
||||
P_orig, Q_orig, x, U = P, Q, P.gen, []
|
||||
|
||||
u = Function('u')(x)
|
||||
a = Dummy('a')
|
||||
|
||||
partial = []
|
||||
|
||||
for d, n in Q.sqf_list_include(all=True):
|
||||
b = d.as_expr()
|
||||
U += [ u.diff(x, n - 1) ]
|
||||
|
||||
h = cancel(P_orig/Q_orig.quo(d**n)) / u**n
|
||||
|
||||
H, subs = [h], []
|
||||
|
||||
for j in range(1, n):
|
||||
H += [ H[-1].diff(x) / j ]
|
||||
|
||||
for j in range(1, n + 1):
|
||||
subs += [ (U[j - 1], b.diff(x, j) / j) ]
|
||||
|
||||
for j in range(0, n):
|
||||
P, Q = cancel(H[j]).as_numer_denom()
|
||||
|
||||
for i in range(0, j + 1):
|
||||
P = P.subs(*subs[j - i])
|
||||
|
||||
Q = Q.subs(*subs[0])
|
||||
|
||||
P = Poly(P, x)
|
||||
Q = Poly(Q, x)
|
||||
|
||||
G = P.gcd(d)
|
||||
D = d.quo(G)
|
||||
|
||||
B, g = Q.half_gcdex(D)
|
||||
b = (P * B.quo(g)).rem(D)
|
||||
|
||||
Dw = D.subs(x, next(dummygen))
|
||||
numer = Lambda(a, b.as_expr().subs(x, a))
|
||||
denom = Lambda(a, (x - a))
|
||||
exponent = n-j
|
||||
|
||||
partial.append((Dw, numer, denom, exponent))
|
||||
|
||||
return partial
|
||||
|
||||
|
||||
@public
|
||||
def assemble_partfrac_list(partial_list):
|
||||
r"""Reassemble a full partial fraction decomposition
|
||||
from a structured result obtained by the function ``apart_list``.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
This example is taken from Bronstein's original paper:
|
||||
|
||||
>>> from sympy.polys.partfrac import apart_list, assemble_partfrac_list
|
||||
>>> from sympy.abc import x
|
||||
|
||||
>>> f = 36 / (x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2)
|
||||
>>> pfd = apart_list(f)
|
||||
>>> pfd
|
||||
(1,
|
||||
Poly(0, x, domain='ZZ'),
|
||||
[(Poly(_w - 2, _w, domain='ZZ'), Lambda(_a, 4), Lambda(_a, -_a + x), 1),
|
||||
(Poly(_w**2 - 1, _w, domain='ZZ'), Lambda(_a, -3*_a - 6), Lambda(_a, -_a + x), 2),
|
||||
(Poly(_w + 1, _w, domain='ZZ'), Lambda(_a, -4), Lambda(_a, -_a + x), 1)])
|
||||
|
||||
>>> assemble_partfrac_list(pfd)
|
||||
-4/(x + 1) - 3/(x + 1)**2 - 9/(x - 1)**2 + 4/(x - 2)
|
||||
|
||||
If we happen to know some roots we can provide them easily inside the structure:
|
||||
|
||||
>>> pfd = apart_list(2/(x**2-2))
|
||||
>>> pfd
|
||||
(1,
|
||||
Poly(0, x, domain='ZZ'),
|
||||
[(Poly(_w**2 - 2, _w, domain='ZZ'),
|
||||
Lambda(_a, _a/2),
|
||||
Lambda(_a, -_a + x),
|
||||
1)])
|
||||
|
||||
>>> pfda = assemble_partfrac_list(pfd)
|
||||
>>> pfda
|
||||
RootSum(_w**2 - 2, Lambda(_a, _a/(-_a + x)))/2
|
||||
|
||||
>>> pfda.doit()
|
||||
-sqrt(2)/(2*(x + sqrt(2))) + sqrt(2)/(2*(x - sqrt(2)))
|
||||
|
||||
>>> from sympy import Dummy, Poly, Lambda, sqrt
|
||||
>>> a = Dummy("a")
|
||||
>>> pfd = (1, Poly(0, x, domain='ZZ'), [([sqrt(2),-sqrt(2)], Lambda(a, a/2), Lambda(a, -a + x), 1)])
|
||||
|
||||
>>> assemble_partfrac_list(pfd)
|
||||
-sqrt(2)/(2*(x + sqrt(2))) + sqrt(2)/(2*(x - sqrt(2)))
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
apart, apart_list
|
||||
"""
|
||||
# Common factor
|
||||
common = partial_list[0]
|
||||
|
||||
# Polynomial part
|
||||
polypart = partial_list[1]
|
||||
pfd = polypart.as_expr()
|
||||
|
||||
# Rational parts
|
||||
for r, nf, df, ex in partial_list[2]:
|
||||
if isinstance(r, Poly):
|
||||
# Assemble in case the roots are given implicitly by a polynomials
|
||||
an, nu = nf.variables, nf.expr
|
||||
ad, de = df.variables, df.expr
|
||||
# Hack to make dummies equal because Lambda created new Dummies
|
||||
de = de.subs(ad[0], an[0])
|
||||
func = Lambda(tuple(an), nu/de**ex)
|
||||
pfd += RootSum(r, func, auto=False, quadratic=False)
|
||||
else:
|
||||
# Assemble in case the roots are given explicitly by a list of algebraic numbers
|
||||
for root in r:
|
||||
pfd += nf(root)/df(root)**ex
|
||||
|
||||
return common*pfd
|
||||
Reference in New Issue
Block a user