manim 0.17.0__py3-none-any.whl → 0.19.1__py3-none-any.whl

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 (163) hide show
  1. manim/__init__.py +11 -6
  2. manim/__main__.py +62 -19
  3. manim/_config/__init__.py +10 -9
  4. manim/_config/cli_colors.py +26 -9
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +23 -13
  7. manim/_config/utils.py +662 -468
  8. manim/animation/animation.py +164 -18
  9. manim/animation/changing.py +34 -23
  10. manim/animation/composition.py +265 -67
  11. manim/animation/creation.py +208 -26
  12. manim/animation/fading.py +16 -18
  13. manim/animation/growing.py +35 -15
  14. manim/animation/indication.py +150 -76
  15. manim/animation/movement.py +56 -22
  16. manim/animation/numbers.py +64 -6
  17. manim/animation/rotation.py +78 -7
  18. manim/animation/specialized.py +6 -7
  19. manim/animation/speedmodifier.py +13 -10
  20. manim/animation/transform.py +14 -11
  21. manim/animation/transform_matching_parts.py +3 -4
  22. manim/animation/updaters/mobject_update_utils.py +152 -30
  23. manim/animation/updaters/update.py +10 -7
  24. manim/camera/camera.py +182 -118
  25. manim/camera/mapping_camera.py +34 -3
  26. manim/camera/moving_camera.py +95 -74
  27. manim/camera/multi_camera.py +23 -15
  28. manim/camera/three_d_camera.py +70 -52
  29. manim/cli/__init__.py +17 -0
  30. manim/cli/cfg/group.py +76 -44
  31. manim/cli/checkhealth/checks.py +192 -0
  32. manim/cli/checkhealth/commands.py +90 -0
  33. manim/cli/default_group.py +158 -25
  34. manim/cli/init/commands.py +33 -25
  35. manim/cli/plugins/commands.py +16 -3
  36. manim/cli/render/commands.py +72 -60
  37. manim/cli/render/ease_of_access_options.py +4 -3
  38. manim/cli/render/global_options.py +59 -17
  39. manim/cli/render/output_options.py +6 -5
  40. manim/cli/render/render_options.py +98 -33
  41. manim/constants.py +109 -59
  42. manim/data_structures.py +31 -0
  43. manim/mobject/frame.py +8 -5
  44. manim/mobject/geometry/__init__.py +1 -0
  45. manim/mobject/geometry/arc.py +277 -135
  46. manim/mobject/geometry/boolean_ops.py +32 -31
  47. manim/mobject/geometry/labeled.py +376 -0
  48. manim/mobject/geometry/line.py +192 -87
  49. manim/mobject/geometry/polygram.py +224 -58
  50. manim/mobject/geometry/shape_matchers.py +61 -25
  51. manim/mobject/geometry/tips.py +122 -48
  52. manim/mobject/graph.py +1027 -419
  53. manim/mobject/graphing/coordinate_systems.py +533 -278
  54. manim/mobject/graphing/functions.py +53 -32
  55. manim/mobject/graphing/number_line.py +123 -65
  56. manim/mobject/graphing/probability.py +88 -62
  57. manim/mobject/graphing/scale.py +33 -19
  58. manim/mobject/logo.py +118 -28
  59. manim/mobject/matrix.py +87 -83
  60. manim/mobject/mobject.py +912 -442
  61. manim/mobject/opengl/dot_cloud.py +16 -5
  62. manim/mobject/opengl/opengl_compatibility.py +4 -2
  63. manim/mobject/opengl/opengl_geometry.py +254 -153
  64. manim/mobject/opengl/opengl_image_mobject.py +3 -1
  65. manim/mobject/opengl/opengl_mobject.py +779 -482
  66. manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
  67. manim/mobject/opengl/opengl_surface.py +14 -92
  68. manim/mobject/opengl/opengl_three_dimensions.py +12 -8
  69. manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
  70. manim/mobject/svg/brace.py +173 -41
  71. manim/mobject/svg/svg_mobject.py +139 -53
  72. manim/mobject/table.py +61 -68
  73. manim/mobject/text/code_mobject.py +193 -539
  74. manim/mobject/text/numbers.py +81 -34
  75. manim/mobject/text/tex_mobject.py +130 -78
  76. manim/mobject/text/text_mobject.py +288 -164
  77. manim/mobject/three_d/polyhedra.py +111 -13
  78. manim/mobject/three_d/three_d_utils.py +17 -8
  79. manim/mobject/three_d/three_dimensions.py +239 -106
  80. manim/mobject/types/image_mobject.py +50 -30
  81. manim/mobject/types/point_cloud_mobject.py +120 -75
  82. manim/mobject/types/vectorized_mobject.py +841 -408
  83. manim/mobject/value_tracker.py +105 -38
  84. manim/mobject/vector_field.py +50 -31
  85. manim/opengl/__init__.py +3 -3
  86. manim/plugins/__init__.py +14 -1
  87. manim/plugins/plugins_flags.py +10 -14
  88. manim/renderer/cairo_renderer.py +65 -50
  89. manim/renderer/opengl_renderer.py +89 -69
  90. manim/renderer/opengl_renderer_window.py +39 -18
  91. manim/renderer/shader.py +123 -87
  92. manim/renderer/shader_wrapper.py +44 -28
  93. manim/renderer/vectorized_mobject_rendering.py +38 -10
  94. manim/scene/moving_camera_scene.py +32 -3
  95. manim/scene/scene.py +507 -242
  96. manim/scene/scene_file_writer.py +371 -220
  97. manim/scene/section.py +20 -16
  98. manim/scene/three_d_scene.py +14 -22
  99. manim/scene/vector_space_scene.py +223 -129
  100. manim/scene/zoomed_scene.py +46 -41
  101. manim/typing.py +990 -0
  102. manim/utils/bezier.py +1823 -371
  103. manim/utils/caching.py +12 -5
  104. manim/utils/color/AS2700.py +236 -0
  105. manim/utils/color/BS381.py +318 -0
  106. manim/utils/color/DVIPSNAMES.py +96 -0
  107. manim/utils/color/SVGNAMES.py +179 -0
  108. manim/utils/color/X11.py +533 -0
  109. manim/utils/color/XKCD.py +952 -0
  110. manim/utils/color/__init__.py +61 -0
  111. manim/utils/color/core.py +1667 -0
  112. manim/utils/color/manim_colors.py +218 -0
  113. manim/utils/commands.py +48 -20
  114. manim/utils/config_ops.py +39 -19
  115. manim/utils/debug.py +8 -7
  116. manim/utils/deprecation.py +86 -39
  117. manim/utils/docbuild/__init__.py +17 -0
  118. manim/utils/docbuild/autoaliasattr_directive.py +236 -0
  119. manim/utils/docbuild/autocolor_directive.py +99 -0
  120. manim/utils/docbuild/manim_directive.py +94 -41
  121. manim/utils/docbuild/module_parsing.py +245 -0
  122. manim/utils/exceptions.py +6 -0
  123. manim/utils/family.py +5 -3
  124. manim/utils/family_ops.py +17 -4
  125. manim/utils/file_ops.py +27 -17
  126. manim/utils/hashing.py +55 -45
  127. manim/utils/images.py +13 -7
  128. manim/utils/ipython_magic.py +13 -7
  129. manim/utils/iterables.py +163 -120
  130. manim/utils/module_ops.py +66 -24
  131. manim/utils/opengl.py +77 -24
  132. manim/utils/parameter_parsing.py +32 -0
  133. manim/utils/paths.py +30 -33
  134. manim/utils/polylabel.py +235 -0
  135. manim/utils/qhull.py +218 -0
  136. manim/utils/rate_functions.py +98 -32
  137. manim/utils/simple_functions.py +25 -33
  138. manim/utils/sounds.py +7 -1
  139. manim/utils/space_ops.py +188 -115
  140. manim/utils/testing/__init__.py +17 -0
  141. manim/utils/testing/_frames_testers.py +13 -8
  142. manim/utils/testing/_show_diff.py +5 -3
  143. manim/utils/testing/_test_class_makers.py +34 -18
  144. manim/utils/testing/frames_comparison.py +37 -19
  145. manim/utils/tex.py +130 -198
  146. manim/utils/tex_file_writing.py +77 -47
  147. manim/utils/tex_templates.py +2 -1
  148. manim/utils/unit.py +6 -5
  149. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
  150. manim-0.19.1.dist-info/RECORD +220 -0
  151. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
  152. manim-0.19.1.dist-info/entry_points.txt +3 -0
  153. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
  154. manim/cli/new/group.py +0 -189
  155. manim/communitycolors.py +0 -9
  156. manim/gui/__init__.py +0 -0
  157. manim/gui/gui.py +0 -82
  158. manim/plugins/import_plugins.py +0 -43
  159. manim/utils/color.py +0 -552
  160. manim-0.17.0.dist-info/RECORD +0 -206
  161. manim-0.17.0.dist-info/entry_points.txt +0 -4
  162. /manim/cli/{new → checkhealth}/__init__.py +0 -0
  163. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
manim/utils/qhull.py ADDED
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env python
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING
5
+
6
+ import numpy as np
7
+
8
+ if TYPE_CHECKING:
9
+ from manim.typing import PointND, PointND_Array
10
+
11
+
12
+ class QuickHullPoint:
13
+ def __init__(self, coordinates: PointND_Array) -> None:
14
+ self.coordinates = coordinates
15
+
16
+ def __hash__(self) -> int:
17
+ return hash(self.coordinates.tobytes())
18
+
19
+ def __eq__(self, other: object) -> bool:
20
+ if not isinstance(other, QuickHullPoint):
21
+ raise ValueError
22
+ are_coordinates_equal: bool = np.array_equal(
23
+ self.coordinates, other.coordinates
24
+ )
25
+ return are_coordinates_equal
26
+
27
+
28
+ class SubFacet:
29
+ def __init__(self, coordinates: PointND_Array) -> None:
30
+ self.coordinates = coordinates
31
+ self.points = frozenset(QuickHullPoint(c) for c in coordinates)
32
+
33
+ def __hash__(self) -> int:
34
+ return hash(self.points)
35
+
36
+ def __eq__(self, other: object) -> bool:
37
+ if not isinstance(other, SubFacet):
38
+ raise ValueError
39
+ return self.points == other.points
40
+
41
+
42
+ class Facet:
43
+ def __init__(self, coordinates: PointND_Array, internal: PointND) -> None:
44
+ self.coordinates = coordinates
45
+ self.center: PointND = np.mean(coordinates, axis=0)
46
+ self.normal = self.compute_normal(internal)
47
+ self.subfacets = frozenset(
48
+ SubFacet(np.delete(self.coordinates, i, axis=0))
49
+ for i in range(self.coordinates.shape[0])
50
+ )
51
+
52
+ def compute_normal(self, internal: PointND) -> PointND:
53
+ centered = self.coordinates - self.center
54
+ _, _, vh = np.linalg.svd(centered)
55
+ normal: PointND = vh[-1, :]
56
+ normal /= np.linalg.norm(normal)
57
+
58
+ # If the normal points towards the internal point, flip it!
59
+ if np.dot(normal, self.center - internal) < 0:
60
+ normal *= -1
61
+
62
+ return normal
63
+
64
+ def __hash__(self) -> int:
65
+ return hash(self.subfacets)
66
+
67
+ def __eq__(self, other: object) -> bool:
68
+ if not isinstance(other, Facet):
69
+ raise ValueError
70
+ return self.subfacets == other.subfacets
71
+
72
+
73
+ class Horizon:
74
+ def __init__(self) -> None:
75
+ self.facets: set[Facet] = set()
76
+ self.boundary: list[SubFacet] = []
77
+
78
+
79
+ class QuickHull:
80
+ """
81
+ QuickHull algorithm for constructing a convex hull from a set of points.
82
+
83
+ Parameters
84
+ ----------
85
+ tolerance
86
+ A tolerance threshold for determining when points lie on the convex hull (default is 1e-5).
87
+
88
+ Attributes
89
+ ----------
90
+ facets
91
+ List of facets considered.
92
+ removed
93
+ Set of internal facets that have been removed from the hull during the construction process.
94
+ outside
95
+ Dictionary mapping each facet to its outside points and eye point.
96
+ neighbors
97
+ Mapping of subfacets to their neighboring facets. Each subfacet links precisely two neighbors.
98
+ unclaimed
99
+ Points that have not yet been classified as inside or outside the current hull.
100
+ internal
101
+ An internal point (i.e., the center of the initial simplex) used as a reference during hull construction.
102
+ tolerance
103
+ The tolerance used to determine if points are considered outside the current hull.
104
+ """
105
+
106
+ def __init__(self, tolerance: float = 1e-5) -> None:
107
+ self.facets: list[Facet] = []
108
+ self.removed: set[Facet] = set()
109
+ self.outside: dict[Facet, tuple[PointND_Array | None, PointND | None]] = {}
110
+ self.neighbors: dict[SubFacet, set[Facet]] = {}
111
+ self.unclaimed: PointND_Array | None = None
112
+ self.internal: PointND | None = None
113
+ self.tolerance = tolerance
114
+
115
+ def initialize(self, points: PointND_Array) -> None:
116
+ # Sample Points
117
+ simplex = points[
118
+ np.random.choice(points.shape[0], points.shape[1] + 1, replace=False)
119
+ ]
120
+ self.unclaimed = points
121
+ new_internal: PointND = np.mean(simplex, axis=0)
122
+ self.internal = new_internal
123
+
124
+ # Build Simplex
125
+ for c in range(simplex.shape[0]):
126
+ facet = Facet(np.delete(simplex, c, axis=0), internal=new_internal)
127
+ self.classify(facet)
128
+ self.facets.append(facet)
129
+
130
+ # Attach Neighbors
131
+ for f in self.facets:
132
+ for sf in f.subfacets:
133
+ self.neighbors.setdefault(sf, set()).add(f)
134
+
135
+ def classify(self, facet: Facet) -> None:
136
+ assert self.unclaimed is not None, (
137
+ "Call .initialize() before using .classify()."
138
+ )
139
+
140
+ if not self.unclaimed.size:
141
+ self.outside[facet] = (None, None)
142
+ return
143
+
144
+ # Compute Projections
145
+ projections = (self.unclaimed - facet.center) @ facet.normal
146
+ arg = np.argmax(projections)
147
+ mask = projections > self.tolerance
148
+
149
+ # Identify Eye and Outside Set
150
+ eye = self.unclaimed[arg] if projections[arg] > self.tolerance else None
151
+ outside = self.unclaimed[mask]
152
+ self.outside[facet] = (outside, eye)
153
+ self.unclaimed = self.unclaimed[~mask]
154
+
155
+ def compute_horizon(self, eye: PointND, start_facet: Facet) -> Horizon:
156
+ horizon = Horizon()
157
+ self._recursive_horizon(eye, start_facet, horizon)
158
+ return horizon
159
+
160
+ def _recursive_horizon(self, eye: PointND, facet: Facet, horizon: Horizon) -> bool:
161
+ visible = np.dot(facet.normal, eye - facet.center) > 0
162
+ if not visible:
163
+ return False
164
+
165
+ # If the eye is visible from the facet:
166
+ # Label the facet as visible and cross each edge
167
+ horizon.facets.add(facet)
168
+ for subfacet in facet.subfacets:
169
+ neighbor = (self.neighbors[subfacet] - {facet}).pop()
170
+ # If the neighbor is not visible, then the edge shared must be on the boundary
171
+ if neighbor not in horizon.facets and not self._recursive_horizon(
172
+ eye, neighbor, horizon
173
+ ):
174
+ horizon.boundary.append(subfacet)
175
+ return True
176
+
177
+ def build(self, points: PointND_Array) -> None:
178
+ num, dim = points.shape
179
+ if (dim == 0) or (num < dim + 1):
180
+ raise ValueError("Not enough points supplied to build Convex Hull!")
181
+ if dim == 1:
182
+ raise ValueError("The Convex Hull of 1D data is its min-max!")
183
+
184
+ self.initialize(points)
185
+
186
+ # This helps the type checker.
187
+ assert self.unclaimed is not None
188
+ assert self.internal is not None
189
+
190
+ while True:
191
+ updated = False
192
+ for facet in self.facets:
193
+ if facet in self.removed:
194
+ continue
195
+ outside, eye = self.outside[facet]
196
+ if eye is not None:
197
+ updated = True
198
+ horizon = self.compute_horizon(eye, facet)
199
+ for f in horizon.facets:
200
+ points_to_append = self.outside[f][0]
201
+ # TODO: is this always true?
202
+ assert points_to_append is not None
203
+ self.unclaimed = np.vstack((self.unclaimed, points_to_append))
204
+ self.removed.add(f)
205
+ for sf in f.subfacets:
206
+ self.neighbors[sf].discard(f)
207
+ if self.neighbors[sf] == set():
208
+ del self.neighbors[sf]
209
+ for sf in horizon.boundary:
210
+ nf = Facet(
211
+ np.vstack((sf.coordinates, eye)), internal=self.internal
212
+ )
213
+ self.classify(nf)
214
+ self.facets.append(nf)
215
+ for nsf in nf.subfacets:
216
+ self.neighbors.setdefault(nsf, set()).add(nf)
217
+ if not updated:
218
+ break
@@ -83,12 +83,14 @@ There are primarily 3 kinds of standard easing functions:
83
83
  self.wait()
84
84
  """
85
85
 
86
-
87
86
  from __future__ import annotations
88
87
 
89
88
  __all__ = [
90
89
  "linear",
91
90
  "smooth",
91
+ "smoothstep",
92
+ "smootherstep",
93
+ "smoothererstep",
92
94
  "rush_into",
93
95
  "rush_from",
94
96
  "slow_into",
@@ -103,21 +105,25 @@ __all__ = [
103
105
  "exponential_decay",
104
106
  ]
105
107
 
106
- import typing
107
108
  from functools import wraps
108
109
  from math import sqrt
110
+ from typing import Any, Protocol
109
111
 
110
112
  import numpy as np
111
113
 
112
- from ..utils.bezier import bezier
113
- from ..utils.simple_functions import sigmoid
114
+ from manim.utils.simple_functions import sigmoid
115
+
116
+
117
+ # TODO: rewrite this to use ParamSpec when Python 3.9 is out of life
118
+ class RateFunction(Protocol):
119
+ def __call__(self, t: float, *args: Any, **kwargs: Any) -> float: ...
114
120
 
115
121
 
116
122
  # This is a decorator that makes sure any function it's used on will
117
123
  # return 0 if t<0 and 1 if t>1.
118
- def unit_interval(function):
124
+ def unit_interval(function: RateFunction) -> RateFunction:
119
125
  @wraps(function)
120
- def wrapper(t, *args, **kwargs):
126
+ def wrapper(t: float, *args: Any, **kwargs: Any) -> float:
121
127
  if 0 <= t <= 1:
122
128
  return function(t, *args, **kwargs)
123
129
  elif t < 0:
@@ -130,9 +136,9 @@ def unit_interval(function):
130
136
 
131
137
  # This is a decorator that makes sure any function it's used on will
132
138
  # return 0 if t<0 or t>1.
133
- def zero(function):
139
+ def zero(function: RateFunction) -> RateFunction:
134
140
  @wraps(function)
135
- def wrapper(t, *args, **kwargs):
141
+ def wrapper(t: float, *args: Any, **kwargs: Any) -> float:
136
142
  if 0 <= t <= 1:
137
143
  return function(t, *args, **kwargs)
138
144
  else:
@@ -155,6 +161,33 @@ def smooth(t: float, inflection: float = 10.0) -> float:
155
161
  )
156
162
 
157
163
 
164
+ @unit_interval
165
+ def smoothstep(t: float) -> float:
166
+ """Implementation of the 1st order SmoothStep sigmoid function.
167
+ The 1st derivative (speed) is zero at the endpoints.
168
+ https://en.wikipedia.org/wiki/Smoothstep
169
+ """
170
+ return 3 * t**2 - 2 * t**3
171
+
172
+
173
+ @unit_interval
174
+ def smootherstep(t: float) -> float:
175
+ """Implementation of the 2nd order SmoothStep sigmoid function.
176
+ The 1st and 2nd derivatives (speed and acceleration) are zero at the endpoints.
177
+ https://en.wikipedia.org/wiki/Smoothstep
178
+ """
179
+ return 6 * t**5 - 15 * t**4 + 10 * t**3
180
+
181
+
182
+ @unit_interval
183
+ def smoothererstep(t: float) -> float:
184
+ """Implementation of the 3rd order SmoothStep sigmoid function.
185
+ The 1st, 2nd and 3rd derivatives (speed, acceleration and jerk) are zero at the endpoints.
186
+ https://en.wikipedia.org/wiki/Smoothstep
187
+ """
188
+ return 35 * t**4 - 84 * t**5 + 70 * t**6 - 20 * t**7
189
+
190
+
158
191
  @unit_interval
159
192
  def rush_into(t: float, inflection: float = 10.0) -> float:
160
193
  return 2 * smooth(t / 2.0, inflection)
@@ -167,7 +200,8 @@ def rush_from(t: float, inflection: float = 10.0) -> float:
167
200
 
168
201
  @unit_interval
169
202
  def slow_into(t: float) -> float:
170
- return np.sqrt(1 - (1 - t) * (1 - t))
203
+ val: float = np.sqrt(1 - (1 - t) * (1 - t))
204
+ return val
171
205
 
172
206
 
173
207
  @unit_interval
@@ -186,7 +220,7 @@ def there_and_back(t: float, inflection: float = 10.0) -> float:
186
220
 
187
221
  @zero
188
222
  def there_and_back_with_pause(t: float, pause_ratio: float = 1.0 / 3) -> float:
189
- a = 1.0 / pause_ratio
223
+ a = 2.0 / (1.0 - pause_ratio)
190
224
  if t < 0.5 - pause_ratio / 2:
191
225
  return smooth(a * t)
192
226
  elif t < 0.5 + pause_ratio / 2:
@@ -199,40 +233,60 @@ def there_and_back_with_pause(t: float, pause_ratio: float = 1.0 / 3) -> float:
199
233
  def running_start(
200
234
  t: float,
201
235
  pull_factor: float = -0.5,
202
- ) -> typing.Iterable: # what is func return type?
203
- return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t)
236
+ ) -> float:
237
+ t2 = t * t
238
+ t3 = t2 * t
239
+ t4 = t3 * t
240
+ t5 = t4 * t
241
+ t6 = t5 * t
242
+ mt = 1 - t
243
+ mt2 = mt * mt
244
+ mt3 = mt2 * mt
245
+ mt4 = mt3 * mt
246
+
247
+ # This is equivalent to creating a Bézier with [0, 0, pull_factor, pull_factor, 1, 1, 1]
248
+ # and evaluating it at t.
249
+ return (
250
+ 15 * t2 * mt4 * pull_factor
251
+ + 20 * t3 * mt3 * pull_factor
252
+ + 15 * t4 * mt2
253
+ + 6 * t5 * mt
254
+ + t6
255
+ )
204
256
 
205
257
 
206
258
  def not_quite_there(
207
- func: typing.Callable[[float], float] = smooth,
259
+ func: RateFunction = smooth,
208
260
  proportion: float = 0.7,
209
- ) -> typing.Callable[[float], float]:
210
- def result(t):
211
- return proportion * func(t)
261
+ ) -> RateFunction:
262
+ def result(t: float, *args: Any, **kwargs: Any) -> float:
263
+ return proportion * func(t, *args, **kwargs)
212
264
 
213
265
  return result
214
266
 
215
267
 
216
268
  @zero
217
269
  def wiggle(t: float, wiggles: float = 2) -> float:
218
- return there_and_back(t) * np.sin(wiggles * np.pi * t)
270
+ val: float = np.sin(wiggles * np.pi * t)
271
+ return there_and_back(t) * val
219
272
 
220
273
 
221
274
  def squish_rate_func(
222
- func: typing.Callable[[float], float],
275
+ func: RateFunction,
223
276
  a: float = 0.4,
224
277
  b: float = 0.6,
225
- ) -> typing.Callable[[float], float]:
226
- def result(t):
278
+ ) -> RateFunction:
279
+ def result(t: float, *args: Any, **kwargs: Any) -> float:
227
280
  if a == b:
228
281
  return a
229
282
 
230
283
  if t < a:
231
- return func(0)
284
+ new_t = 0.0
232
285
  elif t > b:
233
- return func(1)
286
+ new_t = 1.0
234
287
  else:
235
- return func((t - a) / (b - a))
288
+ new_t = (t - a) / (b - a)
289
+ return func(new_t, *args, **kwargs)
236
290
 
237
291
  return result
238
292
 
@@ -245,29 +299,37 @@ def squish_rate_func(
245
299
 
246
300
  @unit_interval
247
301
  def lingering(t: float) -> float:
248
- return squish_rate_func(lambda t: t, 0, 0.8)(t)
302
+ def identity(t: float) -> float:
303
+ return t
304
+
305
+ # TODO: Isn't this just 0.8 * t?
306
+ return squish_rate_func(identity, 0, 0.8)(t)
249
307
 
250
308
 
251
309
  @unit_interval
252
310
  def exponential_decay(t: float, half_life: float = 0.1) -> float:
253
311
  # The half-life should be rather small to minimize
254
312
  # the cut-off error at the end
255
- return 1 - np.exp(-t / half_life)
313
+ val: float = 1 - np.exp(-t / half_life)
314
+ return val
256
315
 
257
316
 
258
317
  @unit_interval
259
318
  def ease_in_sine(t: float) -> float:
260
- return 1 - np.cos((t * np.pi) / 2)
319
+ val: float = 1 - np.cos((t * np.pi) / 2)
320
+ return val
261
321
 
262
322
 
263
323
  @unit_interval
264
324
  def ease_out_sine(t: float) -> float:
265
- return np.sin((t * np.pi) / 2)
325
+ val: float = np.sin((t * np.pi) / 2)
326
+ return val
266
327
 
267
328
 
268
329
  @unit_interval
269
330
  def ease_in_out_sine(t: float) -> float:
270
- return -(np.cos(np.pi * t) - 1) / 2
331
+ val: float = -(np.cos(np.pi * t) - 1) / 2
332
+ return val
271
333
 
272
334
 
273
335
  @unit_interval
@@ -404,7 +466,8 @@ def ease_in_elastic(t: float) -> float:
404
466
  elif t == 1:
405
467
  return 1
406
468
  else:
407
- return -pow(2, 10 * t - 10) * np.sin((t * 10 - 10.75) * c4)
469
+ val: float = -pow(2, 10 * t - 10) * np.sin((t * 10 - 10.75) * c4)
470
+ return val
408
471
 
409
472
 
410
473
  @unit_interval
@@ -415,7 +478,8 @@ def ease_out_elastic(t: float) -> float:
415
478
  elif t == 1:
416
479
  return 1
417
480
  else:
418
- return pow(2, -10 * t) * np.sin((t * 10 - 0.75) * c4) + 1
481
+ val: float = pow(2, -10 * t) * np.sin((t * 10 - 0.75) * c4) + 1
482
+ return val
419
483
 
420
484
 
421
485
  @unit_interval
@@ -426,9 +490,11 @@ def ease_in_out_elastic(t: float) -> float:
426
490
  elif t == 1:
427
491
  return 1
428
492
  elif t < 0.5:
429
- return -(pow(2, 20 * t - 10) * np.sin((20 * t - 11.125) * c5)) / 2
493
+ val: float = -(pow(2, 20 * t - 10) * np.sin((20 * t - 11.125) * c5)) / 2
494
+ return val
430
495
  else:
431
- return (pow(2, -20 * t + 10) * np.sin((20 * t - 11.125) * c5)) / 2 + 1
496
+ val = (pow(2, -20 * t + 10) * np.sin((20 * t - 11.125) * c5)) / 2 + 1
497
+ return val
432
498
 
433
499
 
434
500
  @unit_interval
@@ -6,27 +6,25 @@ __all__ = [
6
6
  "binary_search",
7
7
  "choose",
8
8
  "clip",
9
- "get_parameters",
10
9
  "sigmoid",
11
10
  ]
12
11
 
13
12
 
14
- import inspect
13
+ from collections.abc import Callable
15
14
  from functools import lru_cache
16
- from types import MappingProxyType
17
- from typing import Callable
15
+ from typing import Any, Protocol, TypeVar
18
16
 
19
17
  import numpy as np
20
18
  from scipy import special
21
19
 
22
20
 
23
21
  def binary_search(
24
- function: Callable[[int | float], int | float],
25
- target: int | float,
26
- lower_bound: int | float,
27
- upper_bound: int | float,
28
- tolerance: int | float = 1e-4,
29
- ) -> int | float | None:
22
+ function: Callable[[float], float],
23
+ target: float,
24
+ lower_bound: float,
25
+ upper_bound: float,
26
+ tolerance: float = 1e-4,
27
+ ) -> float | None:
30
28
  """Searches for a value in a range by repeatedly dividing the range in half.
31
29
 
32
30
  To be more precise, performs numerical binary search to determine the
@@ -43,10 +41,10 @@ def binary_search(
43
41
  ::
44
42
 
45
43
  >>> solution = binary_search(lambda x: x**2 + 3*x + 1, 11, 0, 5)
46
- >>> abs(solution - 2) < 1e-4
44
+ >>> bool(abs(solution - 2) < 1e-4)
47
45
  True
48
46
  >>> solution = binary_search(lambda x: x**2 + 3*x + 1, 11, 0, 5, tolerance=0.01)
49
- >>> abs(solution - 2) < 0.01
47
+ >>> bool(abs(solution - 2) < 0.01)
50
48
  True
51
49
 
52
50
  Searching in the interval :math:`[0, 5]` for a target value of :math:`71`
@@ -57,7 +55,7 @@ def binary_search(
57
55
  """
58
56
  lh = lower_bound
59
57
  rh = upper_bound
60
- mh = np.mean(np.array([lh, rh]))
58
+ mh: float = np.mean(np.array([lh, rh]))
61
59
  while abs(rh - lh) > tolerance:
62
60
  mh = np.mean(np.array([lh, rh]))
63
61
  lx, mx, rx = (function(h) for h in (lh, mh, rh))
@@ -91,10 +89,20 @@ def choose(n: int, k: int) -> int:
91
89
  - https://en.wikipedia.org/wiki/Combination
92
90
  - https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.comb.html
93
91
  """
94
- return special.comb(n, k, exact=True)
92
+ value: int = special.comb(n, k, exact=True)
93
+ return value
95
94
 
96
95
 
97
- def clip(a, min_a, max_a):
96
+ class Comparable(Protocol):
97
+ def __lt__(self, other: Any) -> bool: ...
98
+
99
+ def __gt__(self, other: Any) -> bool: ...
100
+
101
+
102
+ ComparableT = TypeVar("ComparableT", bound=Comparable)
103
+
104
+
105
+ def clip(a: ComparableT, min_a: ComparableT, max_a: ComparableT) -> ComparableT:
98
106
  """Clips ``a`` to the interval [``min_a``, ``max_a``].
99
107
 
100
108
  Accepts any comparable objects (i.e. those that support <, >).
@@ -117,23 +125,6 @@ def clip(a, min_a, max_a):
117
125
  return a
118
126
 
119
127
 
120
- def get_parameters(function: Callable) -> MappingProxyType[str, inspect.Parameter]:
121
- """Return the parameters of ``function`` as an ordered mapping of parameters'
122
- names to their corresponding ``Parameter`` objects.
123
-
124
- Examples
125
- --------
126
- ::
127
-
128
- >>> get_parameters(get_parameters)
129
- mappingproxy(OrderedDict([('function', <Parameter "function: 'Callable'">)]))
130
-
131
- >>> tuple(get_parameters(choose))
132
- ('n', 'k')
133
- """
134
- return inspect.signature(function).parameters
135
-
136
-
137
128
  def sigmoid(x: float) -> float:
138
129
  r"""Returns the output of the logistic function.
139
130
 
@@ -145,4 +136,5 @@ def sigmoid(x: float) -> float:
145
136
  - https://en.wikipedia.org/wiki/Sigmoid_function
146
137
  - https://en.wikipedia.org/wiki/Logistic_function
147
138
  """
148
- return 1.0 / (1 + np.exp(-x))
139
+ value: float = 1.0 / (1 + np.exp(-x))
140
+ return value
manim/utils/sounds.py CHANGED
@@ -6,13 +6,19 @@ __all__ = [
6
6
  "get_full_sound_file_path",
7
7
  ]
8
8
 
9
+ from typing import TYPE_CHECKING
9
10
 
10
11
  from .. import config
11
12
  from ..utils.file_ops import seek_full_path_from_defaults
12
13
 
14
+ if TYPE_CHECKING:
15
+ from pathlib import Path
16
+
17
+ from manim.typing import StrPath
18
+
13
19
 
14
20
  # Still in use by add_sound() function in scene_file_writer.py
15
- def get_full_sound_file_path(sound_file_name):
21
+ def get_full_sound_file_path(sound_file_name: StrPath) -> Path:
16
22
  return seek_full_path_from_defaults(
17
23
  sound_file_name,
18
24
  default_dir=config.get_dir("assets_dir"),