Skip to content

Commit bcd6b7f

Browse files
Release Managervbraun
Release Manager
authored andcommitted
Trac #22937: Implement a "distribute" method
Motivation : some "obvious" transformations, sometimes helpful in routine calculations, cannot be obtained via the current methods. Some (elementary, i. e. high-school- or undergrad-level) exemples : given the declarations : {{{ var("a,b") var("j,p", domain="integer") f,g,X=function("f,g,X") }}} the following equalities, true under no or mild conditions, cannot be deduced currently : {{{ sum(X(j)+Y(j),j,1,p)==sum(X(j),j,1,p)+sum(Y(j),j,1,p) ## (1) prod(X(j)*Y(j),j,1,p)==prod(X(j),j,1,p)*prod(Y(j),j,1,p) ## (2) integrate(f(x)+g(x),x,a,b)==integrate(f(x),x,a,b)+integrate(g(x),x,a,b) ## (3) }}} Similarly, if A, B and C are matrices over a suitable ring and of suitable dimensions, the following equalities cannot be straightforwardly obtained. {{{ (A+B).trace()==A.trace()+B.trace() ## (4) (A*B).det()==A.det()*B.det() ## (5) }}} (Curiously, {{{(f(x)+g(x)).diff(x)}}} ''does'' return {{{diff(f(x),x)+diff(g(x),x)}}} (and a way to return to the original expression does not seem to exist currently)). The ability to generate the right-hand form from the left-hand form of these various examples is sometimes useful in (low-level) examples. The examples (1) and/or (2) arise naturally when deriving the maximum-likelihood estimators of the mean and variance of a given distribution. The third one is often encountered in elementary calculus exercises. In all cases, an operator is "distributed" over another one : (1) : symbolic_sum is distributed over addition. (2) : symbolic product is distributed over multiplication. (3) : integration is ditributed over addition. (4) : trace is distributed over (matrix) addition. (5) : determinant is distributed over (matrix) multiplication. Implementing (1) as an extension of the {{{expand()}}} method of Expression is tempting (see #22915). However, there are situations where this expansion is '''not''' useful. So, creating a method for such situations seems useful. One should note that such "distributions" are not systematically valid : we have a collection of special cases rather than a general mechanism. So this method shoud either limit itself to a few recognized cases or take keyword argument(s) specifying what should be distributed over what. Another design choice is to know if these distributions should be recursive or run only on the top level of a given expression (possibly controlled by another keyword argument). The present ticket aims at implementing this method for {{{sage.symbolic.expression.Expression}}}, at least in cases (1) to (3) (case (2) needs an implementation of the symbolic products, see #17505). Further suggestions for other classes are welcome... URL: https://trac.sagemath.org/22937 Reported by: charpent Ticket author(s): Emmanuel Charpentier, Ralf Stephan Reviewer(s): Travis Scrimshaw
2 parents 5a15fb0 + 7aee739 commit bcd6b7f

File tree

3 files changed

+97
-10
lines changed

3 files changed

+97
-10
lines changed

src/sage/functions/other.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -2615,9 +2615,9 @@ def _print_latex_(self, x, var, a, b):
26152615
26162616
sage: from sage.functions.other import symbolic_sum as ssum
26172617
sage: latex(ssum(x^2, x, 1, 10))
2618-
\sum_{x=1}^{10} x^2
2618+
{\sum_{x=1}^{10} x^2}
26192619
"""
2620-
return r"\sum_{{{}={}}}^{{{}}} {}".format(var, a, b, x)
2620+
return r"{{\sum_{{{}={}}}^{{{}}} {}}}".format(var, a, b, x)
26212621

26222622
symbolic_sum = Function_sum()
26232623

@@ -2662,8 +2662,8 @@ def _print_latex_(self, x, var, a, b):
26622662
26632663
sage: from sage.functions.other import symbolic_product as sprod
26642664
sage: latex(sprod(x^2, x, 1, 10))
2665-
\prod_{x=1}^{10} x^2
2665+
{\prod_{x=1}^{10} x^2}
26662666
"""
2667-
return r"\prod_{{{}={}}}^{{{}}} {}".format(var, a, b, x)
2667+
return r"{{\prod_{{{}={}}}^{{{}}} {}}}".format(var, a, b, x)
26682668

26692669
symbolic_product = Function_prod()

src/sage/interfaces/maxima_lib.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -902,17 +902,14 @@ def sr_sum(self,*args):
902902

903903
def sr_prod(self,*args):
904904
"""
905-
Helper function to wrap calculus use of Maxima's summation.
905+
Helper function to wrap calculus use of Maxima's product.
906906
"""
907907
try:
908908
return max_to_sr(maxima_eval([[max_ratsimp],[[max_simplify_prod],([max_prod],[sr_to_max(SR(a)) for a in args])]]));
909909
except RuntimeError as error:
910910
s = str(error)
911911
if "divergent" in s:
912-
# in pexpect interface, one looks for this;
913-
# could not find an example where 'Pole encountered' occurred, though
914-
# if "divergent" in s or 'Pole encountered' in s:
915-
raise ValueError("Sum is divergent.")
912+
raise ValueError("Product is divergent.")
916913
elif "Is" in s: # Maxima asked for a condition
917914
self._missing_assumption(s)
918915
else:

src/sage/symbolic/expression.pyx

+91-1
Original file line numberDiff line numberDiff line change
@@ -10382,6 +10382,97 @@ cdef class Expression(CommutativeRingElement):
1038210382

1038310383
log_expand = expand_log
1038410384

10385+
def distribute(self, recursive=True):
10386+
"""
10387+
Distribute some indexed operators over similar operators in
10388+
order to allow further groupings or simplifications.
10389+
10390+
Implemented cases (so far) :
10391+
10392+
- Symbolic sum of a sum ==> sum of symbolic sums
10393+
10394+
- Integral (definite or not) of a sum ==> sum of integrals.
10395+
10396+
- Symbolic product of a product ==> product of symbolic products.
10397+
10398+
INPUT:
10399+
10400+
- ``recursive`` -- (default : True) the distribution proceeds
10401+
along the subtrees of the expression.
10402+
10403+
TESTS:
10404+
10405+
sage: var("j,k,p,q", domain="integer")
10406+
(j, k, p, q)
10407+
sage: X,Y,Z,f,g=function("X,Y,Z,f,g")
10408+
sage: var("x,a,b")
10409+
(x, a, b)
10410+
sage: sum(X(j)+Y(j),j,1,p)
10411+
sum(X(j) + Y(j), j, 1, p)
10412+
sage: sum(X(j)+Y(j),j,1,p).distribute()
10413+
sum(X(j), j, 1, p) + sum(Y(j), j, 1, p)
10414+
sage: integrate(f(x)+g(x),x)
10415+
integrate(f(x) + g(x), x)
10416+
sage: integrate(f(x)+g(x),x).distribute()
10417+
integrate(f(x), x) + integrate(g(x), x)
10418+
sage: integrate(f(x)+g(x),x,a,b)
10419+
integrate(f(x) + g(x), x, a, b)
10420+
sage: integrate(f(x)+g(x),x,a,b).distribute()
10421+
integrate(f(x), x, a, b) + integrate(g(x), x, a, b)
10422+
sage: sum(X(j)+sum(Y(k)+Z(k),k,1,q),j,1,p)
10423+
sum(X(j) + sum(Y(k) + Z(k), k, 1, q), j, 1, p)
10424+
sage: sum(X(j)+sum(Y(k)+Z(k),k,1,q),j,1,p).distribute()
10425+
sum(sum(Y(k), k, 1, q) + sum(Z(k), k, 1, q), j, 1, p) + sum(X(j), j, 1, p)
10426+
sage: sum(X(j)+sum(Y(k)+Z(k),k,1,q),j,1,p).distribute(recursive=False)
10427+
sum(X(j), j, 1, p) + sum(sum(Y(k) + Z(k), k, 1, q), j, 1, p)
10428+
sage: maxima("product(X(j)*Y(j),j,1,p)").sage()
10429+
product(X(j)*Y(j), j, 1, p)
10430+
sage: maxima("product(X(j)*Y(j),j,1,p)").sage().distribute()
10431+
product(X(j), j, 1, p)*product(Y(j), j, 1, p)
10432+
10433+
10434+
AUTHORS:
10435+
10436+
- Emmanuel Charpentier, Ralf Stephan (05-2017)
10437+
"""
10438+
from sage.functions.other import symbolic_sum as opsum, \
10439+
symbolic_product as opprod
10440+
from sage.symbolic.integration.integral \
10441+
import indefinite_integral as opii, definite_integral as opdi
10442+
from sage.symbolic.operators import add_vararg as opadd, \
10443+
mul_vararg as opmul
10444+
from sage.all import prod
10445+
def treat_term(op, term, args):
10446+
l=sage.all.copy(args)
10447+
l.insert(0, term)
10448+
return(apply(op, l))
10449+
if self.parent() is not sage.all.SR: return self
10450+
op = self.operator()
10451+
if op is None : return self
10452+
if op in {opsum, opdi, opii}:
10453+
sa = self.operands()[0].expand()
10454+
op1 = sa.operator()
10455+
if op1 is opadd:
10456+
la = self.operands()[1:]
10457+
aa = sa.operands()
10458+
if recursive:
10459+
return sum(treat_term(op, t.distribute(), la) for t in aa)
10460+
return sum(treat_term(op, t, la) for t in aa)
10461+
return self
10462+
if op is opprod:
10463+
sa = self.operands()[0].expand()
10464+
op1 = sa.operator()
10465+
if op1 is opmul:
10466+
la = self.operands()[1:]
10467+
aa = sa.operands()
10468+
if recursive:
10469+
return prod(treat_term(op, t.distribute(), la) for t in aa)
10470+
return prod(treat_term(op, t, la) for t in aa)
10471+
return self
10472+
if recursive:
10473+
return apply(op, map(lambda t:t.distribute(), self.operands()))
10474+
return self
10475+
1038510476

1038610477
def factor(self, dontfactor=[]):
1038710478
"""
@@ -12421,4 +12512,3 @@ cdef operators compatible_relation(operators lop, operators rop) except <operato
1242112512
return greater
1242212513
else:
1242312514
raise TypeError("incompatible relations")
12424-

0 commit comments

Comments
 (0)