manim 0.18.0.post0__py3-none-any.whl → 0.19.0__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.

Potentially problematic release.


This version of manim might be problematic. Click here for more details.

Files changed (146) hide show
  1. manim/__init__.py +3 -6
  2. manim/__main__.py +61 -20
  3. manim/_config/__init__.py +6 -3
  4. manim/_config/cli_colors.py +16 -8
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +14 -8
  7. manim/_config/utils.py +651 -472
  8. manim/animation/animation.py +152 -5
  9. manim/animation/composition.py +80 -39
  10. manim/animation/creation.py +196 -14
  11. manim/animation/fading.py +5 -9
  12. manim/animation/indication.py +103 -47
  13. manim/animation/movement.py +22 -5
  14. manim/animation/rotation.py +3 -2
  15. manim/animation/specialized.py +4 -6
  16. manim/animation/speedmodifier.py +10 -5
  17. manim/animation/transform.py +4 -5
  18. manim/animation/transform_matching_parts.py +1 -1
  19. manim/animation/updaters/mobject_update_utils.py +17 -14
  20. manim/camera/camera.py +15 -6
  21. manim/cli/__init__.py +17 -0
  22. manim/cli/cfg/group.py +70 -44
  23. manim/cli/checkhealth/checks.py +93 -75
  24. manim/cli/checkhealth/commands.py +14 -5
  25. manim/cli/default_group.py +157 -25
  26. manim/cli/init/commands.py +32 -24
  27. manim/cli/plugins/commands.py +16 -3
  28. manim/cli/render/commands.py +72 -60
  29. manim/cli/render/ease_of_access_options.py +4 -3
  30. manim/cli/render/global_options.py +51 -15
  31. manim/cli/render/output_options.py +6 -5
  32. manim/cli/render/render_options.py +97 -32
  33. manim/constants.py +65 -19
  34. manim/gui/gui.py +2 -0
  35. manim/mobject/frame.py +0 -1
  36. manim/mobject/geometry/arc.py +112 -78
  37. manim/mobject/geometry/boolean_ops.py +32 -25
  38. manim/mobject/geometry/labeled.py +300 -77
  39. manim/mobject/geometry/line.py +132 -64
  40. manim/mobject/geometry/polygram.py +126 -30
  41. manim/mobject/geometry/shape_matchers.py +35 -15
  42. manim/mobject/geometry/tips.py +38 -29
  43. manim/mobject/graph.py +414 -133
  44. manim/mobject/graphing/coordinate_systems.py +126 -64
  45. manim/mobject/graphing/functions.py +25 -15
  46. manim/mobject/graphing/number_line.py +24 -10
  47. manim/mobject/graphing/probability.py +2 -10
  48. manim/mobject/graphing/scale.py +6 -5
  49. manim/mobject/matrix.py +17 -19
  50. manim/mobject/mobject.py +314 -165
  51. manim/mobject/opengl/opengl_compatibility.py +2 -0
  52. manim/mobject/opengl/opengl_geometry.py +30 -9
  53. manim/mobject/opengl/opengl_image_mobject.py +2 -0
  54. manim/mobject/opengl/opengl_mobject.py +509 -343
  55. manim/mobject/opengl/opengl_point_cloud_mobject.py +5 -7
  56. manim/mobject/opengl/opengl_surface.py +3 -2
  57. manim/mobject/opengl/opengl_three_dimensions.py +2 -0
  58. manim/mobject/opengl/opengl_vectorized_mobject.py +46 -79
  59. manim/mobject/svg/brace.py +63 -13
  60. manim/mobject/svg/svg_mobject.py +4 -3
  61. manim/mobject/table.py +11 -13
  62. manim/mobject/text/code_mobject.py +186 -548
  63. manim/mobject/text/numbers.py +9 -7
  64. manim/mobject/text/tex_mobject.py +23 -14
  65. manim/mobject/text/text_mobject.py +70 -24
  66. manim/mobject/three_d/polyhedra.py +98 -1
  67. manim/mobject/three_d/three_d_utils.py +4 -4
  68. manim/mobject/three_d/three_dimensions.py +62 -34
  69. manim/mobject/types/image_mobject.py +42 -24
  70. manim/mobject/types/point_cloud_mobject.py +105 -67
  71. manim/mobject/types/vectorized_mobject.py +496 -228
  72. manim/mobject/value_tracker.py +5 -4
  73. manim/mobject/vector_field.py +5 -5
  74. manim/opengl/__init__.py +3 -3
  75. manim/plugins/__init__.py +14 -1
  76. manim/plugins/plugins_flags.py +14 -8
  77. manim/renderer/cairo_renderer.py +20 -10
  78. manim/renderer/opengl_renderer.py +21 -23
  79. manim/renderer/opengl_renderer_window.py +2 -0
  80. manim/renderer/shader.py +2 -3
  81. manim/renderer/shader_wrapper.py +5 -2
  82. manim/renderer/vectorized_mobject_rendering.py +5 -0
  83. manim/scene/moving_camera_scene.py +23 -0
  84. manim/scene/scene.py +90 -43
  85. manim/scene/scene_file_writer.py +316 -165
  86. manim/scene/section.py +17 -15
  87. manim/scene/three_d_scene.py +13 -21
  88. manim/scene/vector_space_scene.py +22 -9
  89. manim/typing.py +830 -70
  90. manim/utils/bezier.py +1667 -399
  91. manim/utils/caching.py +13 -5
  92. manim/utils/color/AS2700.py +2 -0
  93. manim/utils/color/BS381.py +3 -0
  94. manim/utils/color/DVIPSNAMES.py +96 -0
  95. manim/utils/color/SVGNAMES.py +179 -0
  96. manim/utils/color/X11.py +3 -0
  97. manim/utils/color/XKCD.py +3 -0
  98. manim/utils/color/__init__.py +8 -5
  99. manim/utils/color/core.py +844 -309
  100. manim/utils/color/manim_colors.py +7 -9
  101. manim/utils/commands.py +48 -20
  102. manim/utils/config_ops.py +18 -13
  103. manim/utils/debug.py +8 -7
  104. manim/utils/deprecation.py +90 -40
  105. manim/utils/docbuild/__init__.py +17 -0
  106. manim/utils/docbuild/autoaliasattr_directive.py +234 -0
  107. manim/utils/docbuild/autocolor_directive.py +21 -17
  108. manim/utils/docbuild/manim_directive.py +50 -35
  109. manim/utils/docbuild/module_parsing.py +245 -0
  110. manim/utils/exceptions.py +6 -0
  111. manim/utils/family.py +5 -3
  112. manim/utils/family_ops.py +17 -4
  113. manim/utils/file_ops.py +26 -16
  114. manim/utils/hashing.py +9 -7
  115. manim/utils/images.py +10 -4
  116. manim/utils/ipython_magic.py +14 -8
  117. manim/utils/iterables.py +161 -119
  118. manim/utils/module_ops.py +57 -19
  119. manim/utils/opengl.py +83 -24
  120. manim/utils/parameter_parsing.py +32 -0
  121. manim/utils/paths.py +21 -23
  122. manim/utils/polylabel.py +168 -0
  123. manim/utils/qhull.py +218 -0
  124. manim/utils/rate_functions.py +74 -39
  125. manim/utils/simple_functions.py +24 -15
  126. manim/utils/sounds.py +7 -1
  127. manim/utils/space_ops.py +125 -69
  128. manim/utils/testing/__init__.py +17 -0
  129. manim/utils/testing/_frames_testers.py +13 -8
  130. manim/utils/testing/_show_diff.py +5 -3
  131. manim/utils/testing/_test_class_makers.py +33 -18
  132. manim/utils/testing/frames_comparison.py +27 -19
  133. manim/utils/tex.py +127 -197
  134. manim/utils/tex_file_writing.py +47 -45
  135. manim/utils/tex_templates.py +2 -1
  136. manim/utils/unit.py +6 -5
  137. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE.community +1 -1
  138. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/METADATA +40 -39
  139. manim-0.19.0.dist-info/RECORD +221 -0
  140. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
  141. manim/cli/new/__init__.py +0 -0
  142. manim/cli/new/group.py +0 -189
  143. manim/plugins/import_plugins.py +0 -43
  144. manim-0.18.0.post0.dist-info/RECORD +0 -217
  145. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
  146. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/entry_points.txt +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,7 +83,6 @@ 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__ = [
@@ -106,21 +105,25 @@ __all__ = [
106
105
  "exponential_decay",
107
106
  ]
108
107
 
109
- import typing
110
108
  from functools import wraps
111
109
  from math import sqrt
110
+ from typing import Any, Protocol
112
111
 
113
112
  import numpy as np
114
113
 
115
- from ..utils.bezier import bezier
116
- 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: ...
117
120
 
118
121
 
119
122
  # This is a decorator that makes sure any function it's used on will
120
123
  # return 0 if t<0 and 1 if t>1.
121
- def unit_interval(function):
124
+ def unit_interval(function: RateFunction) -> RateFunction:
122
125
  @wraps(function)
123
- def wrapper(t, *args, **kwargs):
126
+ def wrapper(t: float, *args: Any, **kwargs: Any) -> float:
124
127
  if 0 <= t <= 1:
125
128
  return function(t, *args, **kwargs)
126
129
  elif t < 0:
@@ -133,9 +136,9 @@ def unit_interval(function):
133
136
 
134
137
  # This is a decorator that makes sure any function it's used on will
135
138
  # return 0 if t<0 or t>1.
136
- def zero(function):
139
+ def zero(function: RateFunction) -> RateFunction:
137
140
  @wraps(function)
138
- def wrapper(t, *args, **kwargs):
141
+ def wrapper(t: float, *args: Any, **kwargs: Any) -> float:
139
142
  if 0 <= t <= 1:
140
143
  return function(t, *args, **kwargs)
141
144
  else:
@@ -179,13 +182,12 @@ def smoothererstep(t: float) -> float:
179
182
  The 1st, 2nd and 3rd derivatives (speed, acceleration and jerk) are zero at the endpoints.
180
183
  https://en.wikipedia.org/wiki/Smoothstep
181
184
  """
182
- return (
183
- 0
184
- if t <= 0
185
- else 35 * t**4 - 84 * t**5 + 70 * t**6 - 20 * t**7
186
- if t < 1
187
- else 1
188
- )
185
+ alpha: float = 0
186
+ if 0 < t < 1:
187
+ alpha = 35 * t**4 - 84 * t**5 + 70 * t**6 - 20 * t**7
188
+ elif t >= 1:
189
+ alpha = 1
190
+ return alpha
189
191
 
190
192
 
191
193
  @unit_interval
@@ -200,7 +202,8 @@ def rush_from(t: float, inflection: float = 10.0) -> float:
200
202
 
201
203
  @unit_interval
202
204
  def slow_into(t: float) -> float:
203
- return np.sqrt(1 - (1 - t) * (1 - t))
205
+ val: float = np.sqrt(1 - (1 - t) * (1 - t))
206
+ return val
204
207
 
205
208
 
206
209
  @unit_interval
@@ -219,7 +222,7 @@ def there_and_back(t: float, inflection: float = 10.0) -> float:
219
222
 
220
223
  @zero
221
224
  def there_and_back_with_pause(t: float, pause_ratio: float = 1.0 / 3) -> float:
222
- a = 1.0 / pause_ratio
225
+ a = 2.0 / (1.0 - pause_ratio)
223
226
  if t < 0.5 - pause_ratio / 2:
224
227
  return smooth(a * t)
225
228
  elif t < 0.5 + pause_ratio / 2:
@@ -232,40 +235,60 @@ def there_and_back_with_pause(t: float, pause_ratio: float = 1.0 / 3) -> float:
232
235
  def running_start(
233
236
  t: float,
234
237
  pull_factor: float = -0.5,
235
- ) -> typing.Iterable: # what is func return type?
236
- return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t)
238
+ ) -> float:
239
+ t2 = t * t
240
+ t3 = t2 * t
241
+ t4 = t3 * t
242
+ t5 = t4 * t
243
+ t6 = t5 * t
244
+ mt = 1 - t
245
+ mt2 = mt * mt
246
+ mt3 = mt2 * mt
247
+ mt4 = mt3 * mt
248
+
249
+ # This is equivalent to creating a Bézier with [0, 0, pull_factor, pull_factor, 1, 1, 1]
250
+ # and evaluating it at t.
251
+ return (
252
+ 15 * t2 * mt4 * pull_factor
253
+ + 20 * t3 * mt3 * pull_factor
254
+ + 15 * t4 * mt2
255
+ + 6 * t5 * mt
256
+ + t6
257
+ )
237
258
 
238
259
 
239
260
  def not_quite_there(
240
- func: typing.Callable[[float], float] = smooth,
261
+ func: RateFunction = smooth,
241
262
  proportion: float = 0.7,
242
- ) -> typing.Callable[[float], float]:
243
- def result(t):
244
- return proportion * func(t)
263
+ ) -> RateFunction:
264
+ def result(t: float, *args: Any, **kwargs: Any) -> float:
265
+ return proportion * func(t, *args, **kwargs)
245
266
 
246
267
  return result
247
268
 
248
269
 
249
270
  @zero
250
271
  def wiggle(t: float, wiggles: float = 2) -> float:
251
- return there_and_back(t) * np.sin(wiggles * np.pi * t)
272
+ val: float = np.sin(wiggles * np.pi * t)
273
+ return there_and_back(t) * val
252
274
 
253
275
 
254
276
  def squish_rate_func(
255
- func: typing.Callable[[float], float],
277
+ func: RateFunction,
256
278
  a: float = 0.4,
257
279
  b: float = 0.6,
258
- ) -> typing.Callable[[float], float]:
259
- def result(t):
280
+ ) -> RateFunction:
281
+ def result(t: float, *args: Any, **kwargs: Any) -> float:
260
282
  if a == b:
261
283
  return a
262
284
 
263
285
  if t < a:
264
- return func(0)
286
+ new_t = 0.0
265
287
  elif t > b:
266
- return func(1)
288
+ new_t = 1.0
267
289
  else:
268
- return func((t - a) / (b - a))
290
+ new_t = (t - a) / (b - a)
291
+ return func(new_t, *args, **kwargs)
269
292
 
270
293
  return result
271
294
 
@@ -278,29 +301,37 @@ def squish_rate_func(
278
301
 
279
302
  @unit_interval
280
303
  def lingering(t: float) -> float:
281
- return squish_rate_func(lambda t: t, 0, 0.8)(t)
304
+ def identity(t: float) -> float:
305
+ return t
306
+
307
+ # TODO: Isn't this just 0.8 * t?
308
+ return squish_rate_func(identity, 0, 0.8)(t)
282
309
 
283
310
 
284
311
  @unit_interval
285
312
  def exponential_decay(t: float, half_life: float = 0.1) -> float:
286
313
  # The half-life should be rather small to minimize
287
314
  # the cut-off error at the end
288
- return 1 - np.exp(-t / half_life)
315
+ val: float = 1 - np.exp(-t / half_life)
316
+ return val
289
317
 
290
318
 
291
319
  @unit_interval
292
320
  def ease_in_sine(t: float) -> float:
293
- return 1 - np.cos((t * np.pi) / 2)
321
+ val: float = 1 - np.cos((t * np.pi) / 2)
322
+ return val
294
323
 
295
324
 
296
325
  @unit_interval
297
326
  def ease_out_sine(t: float) -> float:
298
- return np.sin((t * np.pi) / 2)
327
+ val: float = np.sin((t * np.pi) / 2)
328
+ return val
299
329
 
300
330
 
301
331
  @unit_interval
302
332
  def ease_in_out_sine(t: float) -> float:
303
- return -(np.cos(np.pi * t) - 1) / 2
333
+ val: float = -(np.cos(np.pi * t) - 1) / 2
334
+ return val
304
335
 
305
336
 
306
337
  @unit_interval
@@ -437,7 +468,8 @@ def ease_in_elastic(t: float) -> float:
437
468
  elif t == 1:
438
469
  return 1
439
470
  else:
440
- return -pow(2, 10 * t - 10) * np.sin((t * 10 - 10.75) * c4)
471
+ val: float = -pow(2, 10 * t - 10) * np.sin((t * 10 - 10.75) * c4)
472
+ return val
441
473
 
442
474
 
443
475
  @unit_interval
@@ -448,7 +480,8 @@ def ease_out_elastic(t: float) -> float:
448
480
  elif t == 1:
449
481
  return 1
450
482
  else:
451
- return pow(2, -10 * t) * np.sin((t * 10 - 0.75) * c4) + 1
483
+ val: float = pow(2, -10 * t) * np.sin((t * 10 - 0.75) * c4) + 1
484
+ return val
452
485
 
453
486
 
454
487
  @unit_interval
@@ -459,9 +492,11 @@ def ease_in_out_elastic(t: float) -> float:
459
492
  elif t == 1:
460
493
  return 1
461
494
  elif t < 0.5:
462
- return -(pow(2, 20 * t - 10) * np.sin((20 * t - 11.125) * c5)) / 2
495
+ val: float = -(pow(2, 20 * t - 10) * np.sin((20 * t - 11.125) * c5)) / 2
496
+ return val
463
497
  else:
464
- return (pow(2, -20 * t + 10) * np.sin((20 * t - 11.125) * c5)) / 2 + 1
498
+ val = (pow(2, -20 * t + 10) * np.sin((20 * t - 11.125) * c5)) / 2 + 1
499
+ return val
465
500
 
466
501
 
467
502
  @unit_interval
@@ -10,22 +10,20 @@ __all__ = [
10
10
  ]
11
11
 
12
12
 
13
- import inspect
14
13
  from functools import lru_cache
15
- from types import MappingProxyType
16
- from typing import Callable
14
+ from typing import Any, Callable, Protocol, TypeVar
17
15
 
18
16
  import numpy as np
19
17
  from scipy import special
20
18
 
21
19
 
22
20
  def binary_search(
23
- function: Callable[[int | float], int | float],
24
- target: int | float,
25
- lower_bound: int | float,
26
- upper_bound: int | float,
27
- tolerance: int | float = 1e-4,
28
- ) -> int | float | None:
21
+ function: Callable[[float], float],
22
+ target: float,
23
+ lower_bound: float,
24
+ upper_bound: float,
25
+ tolerance: float = 1e-4,
26
+ ) -> float | None:
29
27
  """Searches for a value in a range by repeatedly dividing the range in half.
30
28
 
31
29
  To be more precise, performs numerical binary search to determine the
@@ -42,10 +40,10 @@ def binary_search(
42
40
  ::
43
41
 
44
42
  >>> solution = binary_search(lambda x: x**2 + 3*x + 1, 11, 0, 5)
45
- >>> abs(solution - 2) < 1e-4
43
+ >>> bool(abs(solution - 2) < 1e-4)
46
44
  True
47
45
  >>> solution = binary_search(lambda x: x**2 + 3*x + 1, 11, 0, 5, tolerance=0.01)
48
- >>> abs(solution - 2) < 0.01
46
+ >>> bool(abs(solution - 2) < 0.01)
49
47
  True
50
48
 
51
49
  Searching in the interval :math:`[0, 5]` for a target value of :math:`71`
@@ -56,7 +54,7 @@ def binary_search(
56
54
  """
57
55
  lh = lower_bound
58
56
  rh = upper_bound
59
- mh = np.mean(np.array([lh, rh]))
57
+ mh: float = np.mean(np.array([lh, rh]))
60
58
  while abs(rh - lh) > tolerance:
61
59
  mh = np.mean(np.array([lh, rh]))
62
60
  lx, mx, rx = (function(h) for h in (lh, mh, rh))
@@ -90,10 +88,20 @@ def choose(n: int, k: int) -> int:
90
88
  - https://en.wikipedia.org/wiki/Combination
91
89
  - https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.comb.html
92
90
  """
93
- return special.comb(n, k, exact=True)
91
+ value: int = special.comb(n, k, exact=True)
92
+ return value
94
93
 
95
94
 
96
- def clip(a, min_a, max_a):
95
+ class Comparable(Protocol):
96
+ def __lt__(self, other: Any) -> bool: ...
97
+
98
+ def __gt__(self, other: Any) -> bool: ...
99
+
100
+
101
+ ComparableT = TypeVar("ComparableT", bound=Comparable) # noqa: Y001
102
+
103
+
104
+ def clip(a: ComparableT, min_a: ComparableT, max_a: ComparableT) -> ComparableT:
97
105
  """Clips ``a`` to the interval [``min_a``, ``max_a``].
98
106
 
99
107
  Accepts any comparable objects (i.e. those that support <, >).
@@ -127,4 +135,5 @@ def sigmoid(x: float) -> float:
127
135
  - https://en.wikipedia.org/wiki/Sigmoid_function
128
136
  - https://en.wikipedia.org/wiki/Logistic_function
129
137
  """
130
- return 1.0 / (1 + np.exp(-x))
138
+ value: float = 1.0 / (1 + np.exp(-x))
139
+ 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"),