MF-Algebra 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. mf_algebra-0.5.0/PKG-INFO +70 -0
  2. mf_algebra-0.5.0/README.md +45 -0
  3. mf_algebra-0.5.0/pyproject.toml +32 -0
  4. mf_algebra-0.5.0/src/MF_Algebra/__init__.py +7 -0
  5. mf_algebra-0.5.0/src/MF_Algebra/actions/__init__.py +5 -0
  6. mf_algebra-0.5.0/src/MF_Algebra/actions/action_common.py +249 -0
  7. mf_algebra-0.5.0/src/MF_Algebra/actions/action_core.py +186 -0
  8. mf_algebra-0.5.0/src/MF_Algebra/actions/action_variants.py +97 -0
  9. mf_algebra-0.5.0/src/MF_Algebra/actions/animations.py +23 -0
  10. mf_algebra-0.5.0/src/MF_Algebra/actions/combinations.py +72 -0
  11. mf_algebra-0.5.0/src/MF_Algebra/expressions/__init__.py +8 -0
  12. mf_algebra-0.5.0/src/MF_Algebra/expressions/expression_common.py +63 -0
  13. mf_algebra-0.5.0/src/MF_Algebra/expressions/expression_core.py +380 -0
  14. mf_algebra-0.5.0/src/MF_Algebra/expressions/functions.py +67 -0
  15. mf_algebra-0.5.0/src/MF_Algebra/expressions/numbers.py +89 -0
  16. mf_algebra-0.5.0/src/MF_Algebra/expressions/operations.py +128 -0
  17. mf_algebra-0.5.0/src/MF_Algebra/expressions/relations.py +36 -0
  18. mf_algebra-0.5.0/src/MF_Algebra/expressions/sequences.py +7 -0
  19. mf_algebra-0.5.0/src/MF_Algebra/expressions/variables.py +18 -0
  20. mf_algebra-0.5.0/src/MF_Algebra/extra/calculus/__init__.py +2 -0
  21. mf_algebra-0.5.0/src/MF_Algebra/extra/calculus/calculus_common.py +22 -0
  22. mf_algebra-0.5.0/src/MF_Algebra/extra/calculus/calculus_core.py +70 -0
  23. mf_algebra-0.5.0/src/MF_Algebra/extra/trigonometry/__init__.py +2 -0
  24. mf_algebra-0.5.0/src/MF_Algebra/extra/trigonometry/trigonometry_common.py +14 -0
  25. mf_algebra-0.5.0/src/MF_Algebra/extra/trigonometry/trigonometry_core.py +3 -0
  26. mf_algebra-0.5.0/src/MF_Algebra/timelines/__init__.py +3 -0
  27. mf_algebra-0.5.0/src/MF_Algebra/timelines/timeline_common.py +29 -0
  28. mf_algebra-0.5.0/src/MF_Algebra/timelines/timeline_core.py +167 -0
  29. mf_algebra-0.5.0/src/MF_Algebra/timelines/timeline_variants.py +32 -0
  30. mf_algebra-0.5.0/src/MF_Algebra/utils.py +230 -0
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.3
2
+ Name: MF-Algebra
3
+ Version: 0.5.0
4
+ Summary: Manim plugin which aims to make it much easier to meaningfully transform algebra expressions.
5
+ License: None
6
+ Author: John Connell
7
+ Author-email: johnconnelltutor@gmail.com
8
+ Requires-Python: >=3.8
9
+ Classifier: License :: Other/Proprietary License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Provides-Extra: manimce
18
+ Provides-Extra: manimgl
19
+ Requires-Dist: MF_Tools (>=1.3.3)
20
+ Requires-Dist: manim (>=0.17.0) ; extra == "manimce"
21
+ Requires-Dist: manimgl (>=1.7.0) ; extra == "manimgl"
22
+ Project-URL: Repository, https://github.com/TheMathematicFanatic/MF_Algebra
23
+ Description-Content-Type: text/markdown
24
+
25
+ ## Introduction
26
+
27
+ This is a ManimGL / ManimCE plugin (**still under construction**) for the automated animation of algebra. It consists of a few key components:
28
+ - Expression: These objects contain a tree structure representing algebra expressions/equations, such as `3x^2`, `5+9`, and `sin(y)=14e^x`, as well as a method for producing a corresponding Tex/MathTex mobject.
29
+ - Action: These objects contain methods to convert between expressions/equations, such as adding something to both sides, or substituting a variable for a value. This conversion can be static or animated.
30
+ - Timeline: These objects contain an alternating sequence of expressions and actions, and methods for automatically determining these sequences, and animating them.
31
+
32
+
33
+ ## Expression
34
+
35
+ There are many subclasses of Expression:
36
+ ```
37
+ Expression
38
+ ├── Variable
39
+ ├── Number
40
+ │ ├── Integer
41
+ │ └── Real
42
+ ├── Combiner
43
+ │ ├── Operation
44
+ │ │ ├── Add
45
+ │ │ ├── Sub
46
+ │ │ ├── Mul
47
+ │ │ ├── Div
48
+ │ │ └── Pow
49
+ │ ├── Relation
50
+ │ │ ├── Equation
51
+ │ │ ├── LessThan
52
+ │ │ ├── ...
53
+ │ └── Sequence
54
+ ├── Function
55
+ └── Negative
56
+ ```
57
+ Every Expression contains an attribute called children, which is a list. Sometimes this list is empty, such as for variables and numbers. But often this list contains other Expressions, such as for operations and functions.
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
@@ -0,0 +1,45 @@
1
+ ## Introduction
2
+
3
+ This is a ManimGL / ManimCE plugin (**still under construction**) for the automated animation of algebra. It consists of a few key components:
4
+ - Expression: These objects contain a tree structure representing algebra expressions/equations, such as `3x^2`, `5+9`, and `sin(y)=14e^x`, as well as a method for producing a corresponding Tex/MathTex mobject.
5
+ - Action: These objects contain methods to convert between expressions/equations, such as adding something to both sides, or substituting a variable for a value. This conversion can be static or animated.
6
+ - Timeline: These objects contain an alternating sequence of expressions and actions, and methods for automatically determining these sequences, and animating them.
7
+
8
+
9
+ ## Expression
10
+
11
+ There are many subclasses of Expression:
12
+ ```
13
+ Expression
14
+ ├── Variable
15
+ ├── Number
16
+ │ ├── Integer
17
+ │ └── Real
18
+ ├── Combiner
19
+ │ ├── Operation
20
+ │ │ ├── Add
21
+ │ │ ├── Sub
22
+ │ │ ├── Mul
23
+ │ │ ├── Div
24
+ │ │ └── Pow
25
+ │ ├── Relation
26
+ │ │ ├── Equation
27
+ │ │ ├── LessThan
28
+ │ │ ├── ...
29
+ │ └── Sequence
30
+ ├── Function
31
+ └── Negative
32
+ ```
33
+ Every Expression contains an attribute called children, which is a list. Sometimes this list is empty, such as for variables and numbers. But often this list contains other Expressions, such as for operations and functions.
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
@@ -0,0 +1,32 @@
1
+ [tool.poetry]
2
+ name = "MF-Algebra"
3
+ version = "0.5.0"
4
+ description = "Manim plugin which aims to make it much easier to meaningfully transform algebra expressions."
5
+ authors = ["John Connell <johnconnelltutor@gmail.com>"]
6
+ license = "None"
7
+ repository = "https://github.com/TheMathematicFanatic/MF_Algebra"
8
+ readme = "README.md"
9
+
10
+ packages = [
11
+ { include = "MF_Algebra", from = "src" }
12
+ ]
13
+
14
+ [tool.poetry.dependencies]
15
+ python = ">=3.8"
16
+ MF_Tools = ">=1.3.3"
17
+ manim = { version = ">=0.17.0", optional = true }
18
+ manimgl = { version = ">=1.7.0", optional = true }
19
+
20
+ [tool.poetry.extras]
21
+ manimce = ["manim"]
22
+ manimgl = ["manimgl"]
23
+
24
+ [build-system]
25
+ requires = ["poetry-core"]
26
+ build-backend = "poetry.core.masonry.api"
27
+
28
+ [tool.pytest.ini_options]
29
+ testpaths = ["tests"]
30
+ filterwarnings = [
31
+ "ignore::DeprecationWarning"
32
+ ]
@@ -0,0 +1,7 @@
1
+ __version__ = '0.5.0'
2
+ __author__ = 'John Connell - The Mathematic Fanatic'
3
+ __description__ = 'Manim plugin which aims to make it much easier to meaningfully transform algebra expressions.'
4
+
5
+ from .expressions import *
6
+ from .actions import *
7
+ from .timelines import *
@@ -0,0 +1,5 @@
1
+ from .action_core import *
2
+ from .animations import *
3
+ from .action_variants import *
4
+ from .combinations import *
5
+ from .action_common import *
@@ -0,0 +1,249 @@
1
+ from .action_core import *
2
+ from .action_variants import *
3
+ from .combinations import *
4
+ from ..expressions.operations import *
5
+ from ..expressions.variables import *
6
+ from ..expressions.relations import *
7
+ from MF_Tools.dual_compatibility import PI, FadeIn
8
+
9
+
10
+ class swap_children_(Action):
11
+ def __init__(self, preaddress='', mode="arc", arc_size=0.75*PI, **kwargs):
12
+ self.mode = mode
13
+ self.arc_size = arc_size
14
+ super().__init__(preaddress=preaddress,**kwargs)
15
+
16
+ @preaddressfunc
17
+ def get_output_expression(self, input_expression=None):
18
+ assert len(input_expression.children) == 2, f"Cannot swap children of {input_expression}, must have two children."
19
+ return type(input_expression)(input_expression.children[1], input_expression.children[0])
20
+
21
+ @preaddressmap
22
+ def get_addressmap(self, input_expression=None):
23
+ if self.mode == "arc":
24
+ return [
25
+ ["0", "1", {"path_arc": self.arc_size}],
26
+ ["1", "0", {"path_arc": self.arc_size}]
27
+ ]
28
+ elif self.mode == "straight":
29
+ return [
30
+ ["0", "1"],
31
+ ["1", "0"]
32
+ ]
33
+ else:
34
+ raise ValueError(f"Invalid mode: {self.mode}. Must be 'arc' or 'straight'.")
35
+
36
+
37
+ class apply_operation_(Action):
38
+ def __init__(self, OpClass, other, preaddress='', side="right", introducer=Write, **kwargs):
39
+ self.OpClass = OpClass
40
+ self.other = Smarten(other)
41
+ self.side = side
42
+ self.introducer = introducer
43
+ super().__init__(preaddress=preaddress,**kwargs)
44
+
45
+ @preaddressfunc
46
+ def get_output_expression(self, input_expression):
47
+ if self.side == "right":
48
+ output_expression = self.OpClass(input_expression, self.other) # Putting a .copy() on the input expression fixes the problem. Try to understand this and then probably fold this into the decorator
49
+ elif self.side == "left":
50
+ output_expression = self.OpClass(self.other, input_expression)
51
+ else:
52
+ raise ValueError(f"Invalid side: {self.side}. Must be left or right.")
53
+ return output_expression
54
+
55
+ @preaddressmap
56
+ def get_addressmap(self, input_expression):
57
+ if self.side == "right":
58
+ return [
59
+ ["", "0"],
60
+ [self.introducer, "+", {"delay":0.5}],
61
+ [self.introducer, "1", {"delay":0.6}]
62
+ ]
63
+ elif self.side == "left":
64
+ return [
65
+ ["", "1"],
66
+ [self.introducer, "0", {"delay":0.5}],
67
+ [self.introducer, "+", {"delay":0.6}]
68
+ ]
69
+ else:
70
+ raise ValueError(f"Invalid side: {self.side}. Must be left or right.")
71
+
72
+ def __repr__(self):
73
+ return type(self).__name__ + "(" + str(self.other) + ',' + self.preaddress + ")"
74
+
75
+ class add_(apply_operation_):
76
+ def __init__(self, other, *args, **kwargs):
77
+ super().__init__(Add, other, *args, **kwargs)
78
+
79
+ class sub_(apply_operation_):
80
+ def __init__(self, other, *args, **kwargs):
81
+ super().__init__(Sub, other, *args, **kwargs)
82
+
83
+ class mul_(apply_operation_):
84
+ def __init__(self, other, *args, **kwargs):
85
+ super().__init__(Mul, other, *args, **kwargs)
86
+
87
+ class div_(apply_operation_):
88
+ def __init__(self, other, *args, **kwargs):
89
+ super().__init__(Div, other, *args, **kwargs)
90
+
91
+ class pow_(apply_operation_):
92
+ def __init__(self, other, *args, **kwargs):
93
+ super().__init__(Pow, other, *args, **kwargs)
94
+
95
+ class equals_(apply_operation_):
96
+ def __init__(self, other, *args, **kwargs):
97
+ super().__init__(Equation, other, *args, **kwargs)
98
+
99
+
100
+ class substitute_(Action):
101
+ def __init__(self, sub_dict, preaddress='', mode="transform", arc_size=PI, fade_shift=DOWN*0.2, lag=0, **kwargs):
102
+ self.sub_dict = sub_dict
103
+ self.preaddress = preaddress
104
+ self.mode = mode
105
+ self.arc_size = arc_size
106
+ self.fade_shift = fade_shift
107
+ self.lag = lag #usually looks like shit but can be cool sometimes
108
+ super().__init__(preaddress=preaddress,**kwargs)
109
+
110
+ @preaddressfunc
111
+ def get_output_expression(self, input_expression=None):
112
+ return input_expression.substitute(self.sub_dict)
113
+
114
+ @preaddressmap
115
+ def get_addressmap(self, input_expression=None):
116
+ target_addresses = []
117
+ for var in self.sub_dict:
118
+ target_addresses += input_expression.get_subex(self.preaddress).get_addresses_of_subex(var)
119
+ addressmap = []
120
+ if self.mode == "transform":
121
+ for i,ad in enumerate(target_addresses):
122
+ addressmap.append([ad, ad, {"delay": self.lag*i}])
123
+ return addressmap
124
+ elif self.mode == "swirl":
125
+ for i,ad in enumerate(target_addresses):
126
+ addressmap.append([ad, ad, {"path_arc": self.arc_size, "delay": self.lag*i}])
127
+ return addressmap
128
+ elif self.mode == "fade":
129
+ for i,ad in enumerate(target_addresses):
130
+ addressmap.append([ad, FadeOut, {"shift": self.fade_shift, "delay": self.lag*i}])
131
+ addressmap.append([FadeIn, ad, {"shift": self.fade_shift, "delay": self.lag*i}])
132
+ return addressmap
133
+
134
+ def __repr__(self):
135
+ return type(self).__name__ + "(" + str(self.sub_dict) + (',' + self.preaddress if self.preaddress else '') + ")"
136
+
137
+
138
+ class substitute_into_(Action):
139
+ def __init__(self, outer_expression, substitution_variable=Variable('x'), **kwargs):
140
+ self.outer_expression = outer_expression
141
+ self.substitution_variable = substitution_variable
142
+ super().__init__(**kwargs)
143
+
144
+ @preaddressfunc
145
+ def get_output_expression(self, input_expression=None):
146
+ return self.outer_expression.substitute({self.substitution_variable: input_expression})
147
+
148
+ @preaddressmap
149
+ def get_addressmap(self, input_expression=None):
150
+ addressmap = []
151
+ sub_into_addresses = self.outer_expression.get_addresses_of_subex(self.substitution_variable)
152
+ for ad in sub_into_addresses:
153
+ if not input_expression.parentheses:
154
+ ad = ad + "_"
155
+ addressmap.append(['', ad])
156
+ return addressmap
157
+
158
+ def get_animation(self, *args, **kwargs):
159
+ return super().get_animation(*args, auto_fade=True, auto_resolve_delay=0.75, **kwargs)
160
+
161
+
162
+ class evaluate_(Action):
163
+ def __init__(self, preaddress='', mode="random leaf", **kwargs):
164
+ super().__init__(preaddress=preaddress,**kwargs)
165
+ # if mode == "random leaf":
166
+ # leaf_addresses = input_expression.get_all_leaf_addresses()
167
+ # leaf_address = np.random.choice(leaves)
168
+ # self.preaddress = leaf_address
169
+
170
+ @preaddressfunc
171
+ def get_output_expression(self, input_expression=None):
172
+ return input_expression.evaluate()
173
+
174
+ @preaddressmap
175
+ def get_addressmap(self, input_expression=None):
176
+ return [
177
+ ["", ""] #extension by preaddress is done by decorator!
178
+ ]
179
+
180
+
181
+ class distribute_(Action):
182
+ # Not done yet, multilayer does not work... this is so necessary but rather nontrivial... hm...
183
+ def __init__(self, preaddress='', mode="auto", multilayer=False, **kwargs):
184
+ self.mode = mode #"auto", "left", "right"
185
+ super().__init__(preaddress=preaddress,**kwargs)
186
+
187
+ @preaddressfunc
188
+ def get_output_expression(self, input_expression=None):
189
+ if self.mode == "auto":
190
+ self.determine_direction(input_expression)
191
+ if self.mode == "left":
192
+ new_children = [
193
+ type(input_expression)(input_expression.children[0], child)
194
+ for child in input_expression.children[-1].children
195
+ ]
196
+ return type(input_expression.children[-1])(*new_children)
197
+ elif self.mode == "right":
198
+ new_children = [
199
+ type(input_expression)(child, input_expression.children[-1])
200
+ for child in input_expression.children[0].children
201
+ ]
202
+ return type(input_expression.children[0])(*new_children)
203
+
204
+ def determine_direction(self, input_expression=None):
205
+ if self.mode == "auto":
206
+ if isinstance(input_expression, Mul):
207
+ left_distributable = isinstance(input_expression.children[-1], (Add, Sub))
208
+ right_distributable = isinstance(input_expression.children[0], (Add, Sub))
209
+ if left_distributable and right_distributable:
210
+ raise ValueError("Cannot auto-distribute if both sides are distributable, please set mode manually.")
211
+ elif left_distributable:
212
+ self.mode = "left"
213
+ elif right_distributable:
214
+ self.mode = "right"
215
+ else:
216
+ raise ValueError("Cannot distribute, neither side is distributable.")
217
+ elif isinstance(input_expression, Div):
218
+ right_distributable = isinstance(input_expression.children[0], (Add, Sub))
219
+ if right_distributable:
220
+ self.mode = "right"
221
+ else:
222
+ raise ValueError("Cannot distribute, right side is not distributable.")
223
+ elif isinstance(input_expression, Pow):
224
+ right_distributable = isinstance(input_expression.children[0], (Mul, Div))
225
+ if right_distributable:
226
+ self.mode = "right"
227
+ else:
228
+ raise ValueError("Cannot distribute, right side is not distributable.")
229
+ else:
230
+ raise ValueError("Cannot auto-distribute, must be a multiplication or division.")
231
+
232
+ @preaddressmap
233
+ def get_addressmap(self, input_expression=None):
234
+ return [
235
+ ["", ""] #standin idk what the fuck im doing here
236
+ ]
237
+
238
+
239
+
240
+
241
+
242
+
243
+ from .action_variants import AlgebraicAction
244
+ a = Variable('a')
245
+ b = Variable('b')
246
+ c = Variable('c')
247
+
248
+ square_binomial_ = AlgebraicAction((a+b)**2, a**2 + 2*a*b + b**2)
249
+
@@ -0,0 +1,186 @@
1
+ # actions.py
2
+ from MF_Tools.dual_compatibility import Write, FadeOut
3
+ from ..expressions.expression_core import *
4
+ from ..utils import *
5
+ from .animations import TransformByAddressMap
6
+
7
+
8
+ class Action:
9
+ """
10
+ Transforms Expressions into other Expressions,
11
+ both as static objects and also with an animation.
12
+
13
+ An action is defined by two main things:
14
+ the get_output_expression method, which controls how it acts on static expressions,
15
+ and the get_addressmap method, which controls how it acts as an animation.
16
+ Both set attributes of the corresponding name and return them.
17
+
18
+ It may also have a preaddress parameter/attribute which will determine the subexpression
19
+ address at which the action is applied, and a few other attributes which may adjust some
20
+ specifics.
21
+
22
+ self.input_expression is set to None during __init__. It is critical that actions
23
+ can exist prior to being given expressions, so that they can be combined together.
24
+ When an input expression is received, this attribute is set, and the method
25
+ .get_output_expression is called, setting self.output_expression.
26
+ This is all that is required for static actions, no animations.
27
+
28
+ Now, to create the animation between these expressions:
29
+
30
+ get_addressmap is also unique to each action, and returns something like
31
+ [
32
+ ["00", "01"],
33
+ ["01", "00", {"path_arc":PI/2}],
34
+ [FadeIn, "1"],
35
+ ["1", FadeOut]
36
+ ]
37
+ which contains all the expression-agnostic information about the animation.
38
+ Often this will simply define and return this list with no computation.
39
+
40
+ get_glyphmap combines the input_expression, output_expression, and addressmap
41
+ to create a list like
42
+ [
43
+ ([0,1,2], [5,6]),
44
+ ([3,4,5], [1,2,3], {"path_arc":PI/2}),
45
+ (FadeIn, [8,9]),
46
+ ([6], FadeOut)
47
+ ]
48
+ which tells which glyphs of the mobjects to send to which others, and how.
49
+
50
+ get_animation then simply parses this glyphmap list to create a list of
51
+ animations, probably to be passed to AnimationGroup, like
52
+ [
53
+ ReplacementTransform(A[0][0,1,2], B[0][5,6]),
54
+ ReplacementTransform(A[0][3,4,5], B[0][1,2,3], path_arc=PI/2),
55
+ FadeIn(B[0][8,9]),
56
+ FadeOut(A[0][6]),
57
+ ...
58
+ ]
59
+ or something like that, the syntax is partially made up. The ... is
60
+ ReplacementTransforms of all the individual glyphs not mentioned in the glyphmap,
61
+ whose lengths have to exactly match.
62
+
63
+ Broadly speaking, that's that!
64
+ """
65
+ def __init__(self,
66
+ introducer=Write,
67
+ remover=FadeOut,
68
+ preaddress=''
69
+ ):
70
+ self.introducer = introducer
71
+ self.remover = remover
72
+ self.preaddress = preaddress
73
+
74
+ def get_output_expression(self, input_expression):
75
+ # define in subclasses and decorate with @preaddressfunc
76
+ raise NotImplementedError
77
+
78
+ def get_addressmap(self, input_expression, **kwargs):
79
+ # define in subclasses and decorate with @preaddressmap
80
+ raise NotImplementedError
81
+
82
+ def get_animation(self, **kwargs):
83
+ def animation(input_exp, output_exp=None):
84
+ if output_exp is None:
85
+ output_exp = self.get_output_expression(input_exp)
86
+ return TransformByAddressMap(
87
+ input_exp,
88
+ output_exp,
89
+ *self.get_addressmap(input_exp),
90
+ default_introducer=self.introducer,
91
+ default_remover=self.remover,
92
+ **kwargs
93
+ )
94
+ return animation
95
+
96
+ def __call__(self, expr1, expr2=None, **kwargs):
97
+ return self.get_animation(**kwargs)(expr1, expr2)
98
+
99
+ def __or__(self, other):
100
+ from .combinations import ParallelAction
101
+ if isinstance(other, ParallelAction):
102
+ return ParallelAction(self, *other.actions)
103
+ elif isinstance(other, Action):
104
+ return ParallelAction(self, other)
105
+ else:
106
+ raise ValueError("Can only use | with other ParallelAction or Action")
107
+
108
+ def __rshift__(self, other):
109
+ other = Smarten(other)
110
+ from ..expressions.expression_core import Expression
111
+ from ..timelines.timeline_core import Timeline
112
+ if isinstance(other, Expression):
113
+ timeline = Timeline()
114
+ timeline.add_action_to_end(self).add_expression_to_end(other)
115
+ return timeline
116
+ elif isinstance(other, Action):
117
+ timeline = Timeline()
118
+ timeline.add_action_to_end(self).add_action_to_end(other)
119
+ return timeline
120
+ else:
121
+ return NotImplemented
122
+
123
+ def __rrshift__(self, other):
124
+ return Smarten(other).__rshift__(self)
125
+
126
+ def __repr__(self):
127
+ return type(self).__name__ + "(" + self.preaddress + ")"
128
+
129
+ def copy(self):
130
+ return deepcopy(self)
131
+
132
+ @property
133
+ def both(self, number_of_sides=2):
134
+ # Intended to turn an action on an expression into an action done to both sides of an equation.
135
+ # Can be passed a number to apply to more than 2 sides for, say, a triple equation or inequality.
136
+ from .combinations import ParallelAction
137
+ actions = []
138
+ for i in range(number_of_sides):
139
+ action = self.copy()
140
+ action.preaddress = action.preaddress + str(i)
141
+ actions.append(action)
142
+ return ParallelAction(*actions)
143
+
144
+
145
+
146
+ def preaddressfunc(func):
147
+ def wrapper(action, expr, *args, **kwargs):
148
+ expr = expr.copy()
149
+ preaddress = kwargs.get('preaddress', '') or action.preaddress
150
+ if len(preaddress)==0:
151
+ output_expression = func(action, expr)
152
+ else:
153
+ active_part = expr.get_subex(preaddress)
154
+ result = func(action, active_part)
155
+ output_expression = expr.substitute_at_address(result, preaddress)
156
+ output_expression.reset_parentheses()
157
+ return output_expression
158
+ return wrapper
159
+
160
+ def preaddressmap(getmap):
161
+ def wrapper(action, expr, *args, **kwargs):
162
+ expr = expr.copy()
163
+ preaddress = kwargs.get('preaddress', '') or action.preaddress
164
+ addressmap = getmap(action, expr, *args, **kwargs)
165
+ if preaddress:
166
+ for entry in addressmap:
167
+ for i, ad in enumerate(entry):
168
+ if isinstance(ad, str):
169
+ entry[i] = preaddress + ad
170
+ return addressmap
171
+ return wrapper
172
+
173
+
174
+ class IncompatibleExpression(Exception):
175
+ pass
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
@@ -0,0 +1,97 @@
1
+ from .action_core import *
2
+ from MF_Tools.dual_compatibility import AnimationGroup
3
+ from MF_Tools import TransformByGlyphMap
4
+
5
+
6
+ class AlgebraicAction(Action):
7
+ def __init__(self, template1, template2, var_kwarg_dict={}, **kwargs):
8
+ super().__init__(**kwargs)
9
+ self.template1 = template1
10
+ self.template2 = template2
11
+ self.var_kwarg_dict = var_kwarg_dict #{a:{"path_arc":PI}}
12
+
13
+ def get_output_expression(self, input_expression=None):
14
+ var_dict = match_expressions(self.template1, input_expression)
15
+ return self.template2.substitute(var_dict)
16
+
17
+ def get_addressmap(self, input_expression=None):
18
+ addressmap = []
19
+ def get_var_ad_dict(template):
20
+ template_leaves = {
21
+ template.get_subex(ad)
22
+ for ad in input_expression.get_all_leaf_addresses()
23
+ }
24
+ from ..expressions.variables import Variable
25
+ variables = [var for var in template_leaves if isinstance(var, Variable)]
26
+ return {var: template.get_addresses_of_subex(var) for var in variables}
27
+ self.template1_address_dict = get_var_ad_dict(self.template1)
28
+ self.template2_address_dict = get_var_ad_dict(self.template2)
29
+ variables = self.template1_address_dict.keys() | self.template2_address_dict.keys()
30
+ for var in variables:
31
+ kwargs = self.var_kwarg_dict.get(var, {})
32
+ if len(self.template1_address_dict[var]) == 1:
33
+ addressmap += [[self.template1_address_dict[var][0], t2ad, kwargs] for t2ad in self.template2_address_dict[var]]
34
+ elif len(self.template2_address_dict[var]) == 1:
35
+ addressmap += [[t1ad, self.template2_address_dict[var][0], kwargs] for t1ad in self.template1_address_dict[var]]
36
+ else:
37
+ raise ValueError("I don't know what to do when a variable appears more than once on both sides. Please set addressmap manually.")
38
+ return addressmap
39
+
40
+
41
+ class AddressMapAction(Action):
42
+ def __init__(self, *address_map, extra_animations=[], **kwargs):
43
+ super().__init__(**kwargs)
44
+ self.address_map = address_map
45
+ self.extra_animations = extra_animations
46
+
47
+ def get_animation(self, **kwargs):
48
+ def animation(input_exp, output_exp=None):
49
+ if output_exp is None:
50
+ output_exp = self.get_output_expression(input_exp)
51
+ return AnimationGroup(
52
+ TransformByAddressMap(
53
+ input_exp,
54
+ output_exp,
55
+ *self.address_map,
56
+ **kwargs
57
+ ),
58
+ *self.extra_animations
59
+ )
60
+ return animation
61
+
62
+
63
+ class GlyphMapAction(Action):
64
+ def __init__(self, *glyph_map, extra_animations=[], show_indices=False, **kwargs):
65
+ super().__init__(**kwargs)
66
+ self.glyph_map = glyph_map
67
+ self.extra_animations = extra_animations
68
+ self.show_indices = show_indices
69
+
70
+ def get_animation(self, **kwargs):
71
+ def animation(input_exp, output_exp=None):
72
+ if output_exp is None:
73
+ output_exp = self.get_output_expression(input_exp)
74
+ return AnimationGroup(
75
+ TransformByGlyphMap(
76
+ input_exp.mob,
77
+ output_exp.mob,
78
+ *self.glyph_map,
79
+ show_indices = self.show_indices,
80
+ **kwargs
81
+ ),
82
+ *self.extra_animations
83
+ )
84
+ return animation
85
+
86
+
87
+ class AnimationAction(Action):
88
+ def __init__(self, animation, **kwargs):
89
+ super().__init__(**kwargs)
90
+ self.animation = animation # callable on two mobjects
91
+
92
+ def get_animation(self, **kwargs):
93
+ def animation(self, input_exp, output_exp=None):
94
+ if output_exp is None:
95
+ output_exp = self.get_output_expression(input_exp)
96
+ return self.animation(input_exp.mob, output_exp.mob, **kwargs)
97
+ return animation