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.
- mf_algebra-0.5.0/PKG-INFO +70 -0
- mf_algebra-0.5.0/README.md +45 -0
- mf_algebra-0.5.0/pyproject.toml +32 -0
- mf_algebra-0.5.0/src/MF_Algebra/__init__.py +7 -0
- mf_algebra-0.5.0/src/MF_Algebra/actions/__init__.py +5 -0
- mf_algebra-0.5.0/src/MF_Algebra/actions/action_common.py +249 -0
- mf_algebra-0.5.0/src/MF_Algebra/actions/action_core.py +186 -0
- mf_algebra-0.5.0/src/MF_Algebra/actions/action_variants.py +97 -0
- mf_algebra-0.5.0/src/MF_Algebra/actions/animations.py +23 -0
- mf_algebra-0.5.0/src/MF_Algebra/actions/combinations.py +72 -0
- mf_algebra-0.5.0/src/MF_Algebra/expressions/__init__.py +8 -0
- mf_algebra-0.5.0/src/MF_Algebra/expressions/expression_common.py +63 -0
- mf_algebra-0.5.0/src/MF_Algebra/expressions/expression_core.py +380 -0
- mf_algebra-0.5.0/src/MF_Algebra/expressions/functions.py +67 -0
- mf_algebra-0.5.0/src/MF_Algebra/expressions/numbers.py +89 -0
- mf_algebra-0.5.0/src/MF_Algebra/expressions/operations.py +128 -0
- mf_algebra-0.5.0/src/MF_Algebra/expressions/relations.py +36 -0
- mf_algebra-0.5.0/src/MF_Algebra/expressions/sequences.py +7 -0
- mf_algebra-0.5.0/src/MF_Algebra/expressions/variables.py +18 -0
- mf_algebra-0.5.0/src/MF_Algebra/extra/calculus/__init__.py +2 -0
- mf_algebra-0.5.0/src/MF_Algebra/extra/calculus/calculus_common.py +22 -0
- mf_algebra-0.5.0/src/MF_Algebra/extra/calculus/calculus_core.py +70 -0
- mf_algebra-0.5.0/src/MF_Algebra/extra/trigonometry/__init__.py +2 -0
- mf_algebra-0.5.0/src/MF_Algebra/extra/trigonometry/trigonometry_common.py +14 -0
- mf_algebra-0.5.0/src/MF_Algebra/extra/trigonometry/trigonometry_core.py +3 -0
- mf_algebra-0.5.0/src/MF_Algebra/timelines/__init__.py +3 -0
- mf_algebra-0.5.0/src/MF_Algebra/timelines/timeline_common.py +29 -0
- mf_algebra-0.5.0/src/MF_Algebra/timelines/timeline_core.py +167 -0
- mf_algebra-0.5.0/src/MF_Algebra/timelines/timeline_variants.py +32 -0
- 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,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
|