AnimationGroup: optimized interpolate() and fixed alpha bug on finish()
Overview: What does this pull request change?
- In
AnimationGroup.build_animations_with_timings(), the start times and end times of each animation are now calculated in a vectorized way: nowAnimationGroup.anims_with_timingsis a NumPy array with custom type(object, float, float), instead of a list of tuples. This allows for vectorization and fancy indexing in the next point. AnimationGroup.interpolate()was vectorized thanks to the previous changes. Not only that: instead of updating all the animations, now only "ongoing" animations (those which have started and not yet finished) are updated. The ongoing animations are fancy-indexed from the now-ndarrayanims_with_timingsattribute. All of this results in a major speedup forAnimationGroup.interpolate().- There was a bug where, if you used a "reverse"
rate_funcwhich intended to make the submobjects reach analpha == 0state, whenAnimationGroup.finish()gets called, it would call all of its subanimations'finish()method which would make all the submobjects be reset to analpha == 1state instead. To fix this, anAnimationGroup.interpolate(1)was added to theAnimationGroup.finish()method.
Motivation and Explanation: Why and how do your changes improve the library?
Consider this scene:
class ChaosGame(Scene):
def construct(self):
L = 8
h = L * np.sqrt(3) / 2
vertices = np.array([[0, h/2, 0], [-L/2, -h/2, 0], [L/2, -h/2, 0]])
N = 1000
points = np.empty((N, 3))
choices = np.random.randint(3, size=N)
colors = [RED, YELLOW, BLUE]
points[-1] = [0, 0, 0]
for i in range(N):
points[i] = (points[i-1] + vertices[choices[i]]) / 2
dots = [Dot(p, color=colors[ch], radius=0.02) for p, ch in zip(points, choices)]
self.wait()
self.play(AnimationGroup(*[FadeIn(dot) for dot in dots], lag_ratio=0.5, run_time=20))
self.wait(5)
When an AnimationGroup has too many subanimations (like in this example), performance gets a hit. Therefore, I optimized AnimationGroup for that scenario.
This specific example was extreme, and so is its corresponding optimization: a speedup of almost 150x in AnimationGroup.interpolate()! Other scenarios may not benefit that much, but it's still a huge advancement.
| Before | After |
|---|---|
Reviewer Checklist
- [ ] The PR title is descriptive enough for the changelog, and the PR is labeled correctly
- [ ] If applicable: newly added non-private functions and classes have a docstring including a short summary and a PARAMETERS section
- [ ] If applicable: newly added functions and classes are tested
I realized that my implementation creates a bug when there are too many subanimations, as self.ongoing_anim_bools and new_ongoing won't always intersect, and there might be animations in between which would be skipped by this.
I noticed that when running my ChaosGame with 5000 mobjects. :(
I'll make this a draft meanwhile, I hope that I can fix this soon.
@behackl, about your AnimationGroup implementation you talked about last night: maybe it would be a good idea to integrate those changes now that we're on it. May I pull your changes into my branch? Or is it possible that you could push them?
I fixed the problem and marked the PR as ready for review again. In the end I reverted my implementation to do the same thing you did, Benjamin: to compute when animations have started and finished, rather than being just "ongoing".
I also made AnimationGroup.update_mobjects() update only the ongoing animations (i.e. started and not finished), just like you did.
I tried to implement the other changes and see what happened. It didn't go well. :(
For my ChaosGame, the FadeIn animations expect their begin() method to be called in the beginning, because that's how they create the target and starting mobject, where the starting mobject is invisible at first. Not doing this right at the beginning (like what you do with AnimationGroup.begin(): it's just a pass) messes everything up, as all mobjects are immediately present on scene and they barely blink when their animation actually begins. (Now that i think of it, it sounds more of an AnimationGroup issue, as in "why are you adding all the Mobjects preemptively")
Implementing all those changes is gonna be more complex than it looks, and it definitely should be done in another PR ;)