Skip to content

Commit 22cf4cd

Browse files
authored
Implement Distribute (#1850)
First round of implementing `Distribute[]`. The 5-argument form is currently not supported, but what's used in Combinatorica is. Fixes #1728
1 parent f912619 commit 22cf4cd

9 files changed

Lines changed: 519 additions & 80 deletions

File tree

SYMBOLS_MANIFEST.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ System`Disk
349349
System`DiskBox
350350
System`DiskMatrix
351351
System`Dispatch
352+
System`Distribute
352353
System`Divide
353354
System`DivideBy
354355
System`Divisible
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
"""
2-
Expression Structure
1+
r"""
2+
Structural Operations on Expressions
3+
4+
Here we have functions which work purely on \Mathics3 Expressions transforming them in some way.
35
"""
46

57
# This tells documentation how to sort this module
6-
sort_order = "mathics.builtin.expression-structure"
8+
sort_order = "mathics.builtin.structual-operations-on-expressions"
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"""
2+
Miscellaneous Structural Operations on Expressions
3+
"""
4+
5+
from mathics.core.attributes import A_PROTECTED
6+
from mathics.core.builtin import Builtin
7+
from mathics.core.evaluation import Evaluation
8+
from mathics.core.expression import Expression
9+
from mathics.core.systemsymbols import SymbolIdentity
10+
from mathics.eval.exp_structure import eval_Distribute
11+
from mathics.eval.tensors import eval_Outer
12+
13+
14+
class Distribute(Builtin):
15+
"""
16+
<url>:WMA link:https://reference.wolfram.com/language/ref/Distribute.html</url>
17+
18+
<dl>
19+
<dt>'Distribute'[$expr$]
20+
<dd>distributes $expr$ over 'Plus' (addition).
21+
<dt>'Distribute'[$expr$, $operator$]
22+
<dd>distributes $expr$ over the specified $operator$.
23+
<dt>'Distribute'[$expr$, $operator$, $f$]
24+
<dd>applies $f$ to each component of the result.
25+
26+
## <dt>'Distribute'[$expr$, $operator$, $f$, $gp$, $fp$]
27+
## <dd>distributes $expr$ over $operator$, replacing outer function with $gp$ and inner function with $fp$.
28+
</dl>
29+
30+
Distribute multiplication over addition:
31+
>> Distribute[a(b + c)]
32+
= a b + a c
33+
34+
>> Distribute[(a + b)(c + d)]
35+
= a c + a d + b c + b d
36+
37+
Using a custom target head:
38+
>> Distribute[f[a + b, c], Plus]
39+
= f[a, c] + f[b, c]
40+
41+
Distribute can also work with lists:
42+
>> Distribute[{a(b + c), d(e + f)}]
43+
= {a b + a c, d e + d f}
44+
45+
## Applying a function to results:
46+
## >> Distribute[a(b + c), Plus, Square]
47+
## = Square[a b] + Square[a c]
48+
49+
Special forms:
50+
>> Distribute[f[g[a + b]]]
51+
= f[g[a]] + f[g[b]]
52+
53+
Distribute $f$ over $g$:
54+
>> Distribute[f[g[a, b], g[c, d, e]], g]
55+
= g[f[a, c], f[a, d], f[a, e], f[b, c], f[b, d], f[b, e]]
56+
57+
## Using a custom operator and functions:
58+
## >> Distribute[f[g[a, b], g[c, d, e]], g, f, gp, fp]
59+
## = gp[fp[a, c], fp[a, d], fp[a, e], fp[b, c], fp[b, d], fp[b, e]]
60+
"""
61+
62+
attributes = A_PROTECTED
63+
64+
eval_error = Builtin.generic_argument_error
65+
expected_args = range(1, 6)
66+
67+
rules = {
68+
"Distribute[expr_]": "Distribute[expr, Plus]",
69+
"Distribute[expr_, operator_]": "Distribute[expr, operator, Identity]",
70+
}
71+
72+
summary_text = "distribute functions over a head"
73+
74+
def eval(self, expr, operator, filt, evaluation: Evaluation):
75+
"Distribute[expr_, operator_, filt_]"
76+
77+
# Handle Identity filter
78+
if filt is SymbolIdentity:
79+
filt = None
80+
81+
result = eval_Distribute(expr, operator, evaluation)
82+
83+
if result is None:
84+
return expr
85+
86+
if filt:
87+
return Expression(filt, result)
88+
89+
return result
90+
91+
# def eval_with_function_replacement(
92+
# self, expr, operator, f, g, gp, fp, evaluation: Evaluation
93+
# ):
94+
# "Distribute[expr_, f_, g_, gp_, fp_]"
95+
96+
# result = eval_Distribute_with_replacement(expr, f, g, gp, fp, evaluation)
97+
98+
# if result is None:
99+
# return expr
100+
101+
# return result
102+
103+
104+
class Outer(Builtin):
105+
"""
106+
<url>:Outer product:https://en.wikipedia.org/wiki/Outer_product</url> \
107+
(<url>:WMA link: https://reference.wolfram.com/language/ref/Outer.html</url>)
108+
109+
<dl>
110+
<dt>'Outer'[$f$, $x$, $y$]
111+
<dd>computes a generalised outer product of $x$ and $y$, using the function $f$ in place of multiplication.
112+
</dl>
113+
114+
>> Outer[f, {a, b}, {1, 2, 3}]
115+
= {{f[a, 1], f[a, 2], f[a, 3]}, {f[b, 1], f[b, 2], f[b, 3]}}
116+
117+
Outer product of two matrices:
118+
>> Outer[Times, {{a, b}, {c, d}}, {{1, 2}, {3, 4}}]
119+
= {{{{a, 2 a}, {3 a, 4 a}}, {{b, 2 b}, {3 b, 4 b}}}, {{{c, 2 c}, {3 c, 4 c}}, {{d, 2 d}, {3 d, 4 d}}}}
120+
121+
Outer product of two sparse arrays:
122+
>> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]]
123+
= SparseArray[Automatic, {2, 2, 2, 2}, 0, {{1, 2, 1, 2} ⇾ a c, {1, 2, 2, 1} ⇾ a d, {2, 1, 1, 2} ⇾ b c, {2, 1, 2, 1} ⇾ b d}]
124+
125+
'Outer' of multiple lists:
126+
>> Outer[f, {a, b}, {x, y, z}, {1, 2}]
127+
= {{{f[a, x, 1], f[a, x, 2]}, {f[a, y, 1], f[a, y, 2]}, {f[a, z, 1], f[a, z, 2]}}, {{f[b, x, 1], f[b, x, 2]}, {f[b, y, 1], f[b, y, 2]}, {f[b, z, 1], f[b, z, 2]}}}
128+
129+
'Outer' converts input sparse arrays to lists if f=!=Times, or if the input is a mixture of sparse arrays and lists:
130+
>> Outer[f, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]]
131+
= {{{{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}, {{f[a, 0], f[a, c]}, {f[a, d], f[a, 0]}}}, {{{f[b, 0], f[b, c]}, {f[b, d], f[b, 0]}}, {{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}}}
132+
133+
>> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], {c, d}]
134+
= {{{0, 0}, {a c, a d}}, {{b c, b d}, {0, 0}}}
135+
136+
Arrays can be ragged:
137+
>> Outer[Times, {{1, 2}}, {{a, b}, {c, d, e}}]
138+
= {{{{a, b}, {c, d, e}}, {{2 a, 2 b}, {2 c, 2 d, 2 e}}}}
139+
140+
Word combinations:
141+
>> Outer[StringJoin, {"", "re", "un"}, {"cover", "draw", "wind"}, {"", "ing", "s"}] // InputForm
142+
= {{{"cover", "covering", "covers"}, {"draw", "drawing", "draws"}, {"wind", "winding", "winds"}}, {{"recover", "recovering", "recovers"}, {"redraw", "redrawing", "redraws"}, {"rewind", "rewinding", "rewinds"}}, {{"uncover", "uncovering", "uncovers"}, {"undraw", "undrawing", "undraws"}, {"unwind", "unwinding", "unwinds"}}}
143+
144+
Compositions of trigonometric functions:
145+
>> trigs = Outer[Composition, {Sin, Cos, Tan}, {ArcSin, ArcCos, ArcTan}]
146+
= {{Composition[Sin, ArcSin], Composition[Sin, ArcCos], Composition[Sin, ArcTan]}, {Composition[Cos, ArcSin], Composition[Cos, ArcCos], Composition[Cos, ArcTan]}, {Composition[Tan, ArcSin], Composition[Tan, ArcCos], Composition[Tan, ArcTan]}}
147+
Evaluate at 0:
148+
>> Map[#[0] &, trigs, {2}]
149+
= {{0, 1, 0}, {1, 0, 1}, {0, ComplexInfinity, 0}}
150+
"""
151+
152+
summary_text = "generalized outer product"
153+
154+
def eval(self, f, lists, evaluation: Evaluation):
155+
"Outer[f_, lists__]"
156+
157+
return eval_Outer(f, lists, evaluation)

mathics/builtin/numbers/algebra.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from mathics.core.systemsymbols import (
4747
SymbolAssumptions,
4848
SymbolEqual,
49+
SymbolIdentity,
4950
SymbolIndeterminate,
5051
SymbolLess,
5152
SymbolRule,
@@ -550,7 +551,7 @@ class Collect(Builtin):
550551

551552
def eval_var_filter(self, expr, varlist, filt, evaluation):
552553
"""Collect[expr_, varlist_, filt_]"""
553-
if filt is Symbol("Identity"):
554+
if filt is SymbolIdentity:
554555
filt = None
555556
if isinstance(varlist, Symbol):
556557
var_exprs = [varlist]
@@ -845,9 +846,9 @@ def eval(self, expr, evaluation: Evaluation, options: dict):
845846
return expand_polynomial(expr, False, True, **kwargs)
846847

847848

848-
## Our expand_polynomial routine and SymPy's do not match
849-
## what WMA is doing. Failing a good reason to get this working,
850-
## I, rocky, do not thing it is worth the effort.
849+
# Our expand_polynomial routine and SymPy's do not match
850+
# what WMA is doing. Failing a good reason to get this working,
851+
# I, rocky, do not thing it is worth the effort.
851852
#
852853
# class ExpandNumerator(_Expand):
853854
# """

mathics/builtin/tensors.py

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
from mathics.eval.tensors import (
2727
eval_Inner,
2828
eval_LeviCivitaTensor,
29-
eval_Outer,
3029
eval_Transpose,
3130
get_dimensions,
3231
)
@@ -168,62 +167,6 @@ def eval(self, f, list1, list2, g, evaluation: Evaluation):
168167
return eval_Inner(f, list1, list2, g, evaluation)
169168

170169

171-
class Outer(Builtin):
172-
"""
173-
<url>:Outer product:https://en.wikipedia.org/wiki/Outer_product</url> \
174-
(<url>:WMA link: https://reference.wolfram.com/language/ref/Outer.html</url>)
175-
176-
<dl>
177-
<dt>'Outer'[$f$, $x$, $y$]
178-
<dd>computes a generalised outer product of $x$ and $y$, using the function $f$ in place of multiplication.
179-
</dl>
180-
181-
>> Outer[f, {a, b}, {1, 2, 3}]
182-
= {{f[a, 1], f[a, 2], f[a, 3]}, {f[b, 1], f[b, 2], f[b, 3]}}
183-
184-
Outer product of two matrices:
185-
>> Outer[Times, {{a, b}, {c, d}}, {{1, 2}, {3, 4}}]
186-
= {{{{a, 2 a}, {3 a, 4 a}}, {{b, 2 b}, {3 b, 4 b}}}, {{{c, 2 c}, {3 c, 4 c}}, {{d, 2 d}, {3 d, 4 d}}}}
187-
188-
Outer product of two sparse arrays:
189-
>> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]]
190-
= SparseArray[Automatic, {2, 2, 2, 2}, 0, {{1, 2, 1, 2} ⇾ a c, {1, 2, 2, 1} ⇾ a d, {2, 1, 1, 2} ⇾ b c, {2, 1, 2, 1} ⇾ b d}]
191-
192-
'Outer' of multiple lists:
193-
>> Outer[f, {a, b}, {x, y, z}, {1, 2}]
194-
= {{{f[a, x, 1], f[a, x, 2]}, {f[a, y, 1], f[a, y, 2]}, {f[a, z, 1], f[a, z, 2]}}, {{f[b, x, 1], f[b, x, 2]}, {f[b, y, 1], f[b, y, 2]}, {f[b, z, 1], f[b, z, 2]}}}
195-
196-
'Outer' converts input sparse arrays to lists if f=!=Times, or if the input is a mixture of sparse arrays and lists:
197-
>> Outer[f, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]]
198-
= {{{{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}, {{f[a, 0], f[a, c]}, {f[a, d], f[a, 0]}}}, {{{f[b, 0], f[b, c]}, {f[b, d], f[b, 0]}}, {{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}}}
199-
200-
>> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], {c, d}]
201-
= {{{0, 0}, {a c, a d}}, {{b c, b d}, {0, 0}}}
202-
203-
Arrays can be ragged:
204-
>> Outer[Times, {{1, 2}}, {{a, b}, {c, d, e}}]
205-
= {{{{a, b}, {c, d, e}}, {{2 a, 2 b}, {2 c, 2 d, 2 e}}}}
206-
207-
Word combinations:
208-
>> Outer[StringJoin, {"", "re", "un"}, {"cover", "draw", "wind"}, {"", "ing", "s"}] // InputForm
209-
= {{{"cover", "covering", "covers"}, {"draw", "drawing", "draws"}, {"wind", "winding", "winds"}}, {{"recover", "recovering", "recovers"}, {"redraw", "redrawing", "redraws"}, {"rewind", "rewinding", "rewinds"}}, {{"uncover", "uncovering", "uncovers"}, {"undraw", "undrawing", "undraws"}, {"unwind", "unwinding", "unwinds"}}}
210-
211-
Compositions of trigonometric functions:
212-
>> trigs = Outer[Composition, {Sin, Cos, Tan}, {ArcSin, ArcCos, ArcTan}]
213-
= {{Composition[Sin, ArcSin], Composition[Sin, ArcCos], Composition[Sin, ArcTan]}, {Composition[Cos, ArcSin], Composition[Cos, ArcCos], Composition[Cos, ArcTan]}, {Composition[Tan, ArcSin], Composition[Tan, ArcCos], Composition[Tan, ArcTan]}}
214-
Evaluate at 0:
215-
>> Map[#[0] &, trigs, {2}]
216-
= {{0, 1, 0}, {1, 0, 1}, {0, ComplexInfinity, 0}}
217-
"""
218-
219-
summary_text = "generalized outer product"
220-
221-
def eval(self, f, lists, evaluation: Evaluation):
222-
"Outer[f_, lists__]"
223-
224-
return eval_Outer(f, lists, evaluation)
225-
226-
227170
class RotationTransform(Builtin):
228171
"""
229172
<url>:WMA link: https://reference.wolfram.com/language/ref/RotationTransform.html</url>

mathics/core/systemsymbols.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@
163163
SymbolHoldPattern = Symbol("System`HoldPattern")
164164
SymbolHue = Symbol("System`Hue")
165165
SymbolI = Symbol("System`I")
166+
SymbolIdentity = Symbol("System`Identity")
166167
SymbolIf = Symbol("System`If")
167168
SymbolIm = Symbol("System`Im")
168169
SymbolImage = Symbol("System`Image")
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"Evaluation methods for builtin functions of mathics.builtin.exp_structure"
2+
3+
from mathics.eval.exp_structure.distribute import eval_Distribute
4+
5+
# from mathics.eval.exp_structure.outer import eval_Outer
6+
7+
# TODO: add FlattenAt
8+
# from mathics.eval.exp_structure.flattenAt import eval_At
9+
__all__ = [
10+
"eval_Distribute",
11+
# "eval_FlattenAt",
12+
# "eval_Outer",
13+
]

0 commit comments

Comments
 (0)