Repository: ManimCommunity/manim

Issue:
`Circumscribe` shows shape after animation if `run_time` is passed outside the `Circumscribe` instance and is different than 1
## Description of bug / unexpected behavior
If the `run_time` argument is passed to `Scene.play()` instead of passing it directly to `Circumscribe` AND the run_time is different than 1, the shape will be added after the animation, like it did in #2349 , only that now the shape is removed, but is still visible for some time.

**This works fine**
```py
self.play(Circumscribe(text), run_time=1)
self.play(Circumscribe(text, run_time=2))
self.play(Circumscribe(text, run_time=0.5))
```
**This doesn't**
```py
self.play(Circumscribe(text), run_time=2)
self.play(Circumscribe(text), run_time=0.5)
```
May or may not have to do with #2396. _I haven't checked this_, but I remember this working fine a some months ago.

## Expected behavior
The animation should work properly regardless of where `run_time` is passed.


## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->

<details><summary>Code for reproducing the problem</summary>

```py
class CircumscribeBug(Scene):
    def construct(self):
        text = Tex("Works").set_color(GREEN)

        self.add(text)
        # Works
        self.play(Circumscribe(text), run_time=1)
        self.play(Circumscribe(text, run_time=2))
        self.play(Circumscribe(text, run_time=0.5))
        self.wait(0.5)

        text.set_color(RED)
        # Doesn't
        self.play(Circumscribe(text), run_time=2)
        self.wait()
        self.play(Circumscribe(text), run_time=0.5)
        self.wait(0.5)
```

</details>


## Additional media files
<!-- Paste in the files manim produced on rendering the code above. -->

<details><summary>Images/GIFs</summary>

<!-- PASTE MEDIA HERE -->

https://user-images.githubusercontent.com/68753500/154860142-3b7774e3-57b8-4f43-b65a-e678a0e16635.mp4


</details>


## Logs
<details><summary>Terminal output</summary>
<!-- Add "-v DEBUG" when calling manim to generate more detailed logs -->

```
PASTE HERE OR PROVIDE LINK TO https://pastebin.com/ OR SIMILAR
```

<!-- Insert screenshots here (only when absolutely necessary, we prefer copy/pasted output!) -->

</details>


## System specifications

<details><summary>System Details</summary>

- OS (with version, e.g., Windows 10 v2004 or macOS 10.15 (Catalina)):
- RAM:
- Python version (`python/py/python3 --version`):
- Installed modules (provide output from `pip list`):
```
PASTE HERE
```
</details>

<details><summary>LaTeX details</summary>

+ LaTeX distribution (e.g. TeX Live 2020):
+ Installed LaTeX packages:
<!-- output of `tlmgr list --only-installed` for TeX Live or a screenshot of the Packages page for MikTeX -->
</details>

<details><summary>FFMPEG</summary>

Output of `ffmpeg -version`:

```
PASTE HERE
```
</details>

## Additional comments
<!-- Add further context that you think might be relevant for this issue here. -->

`Succession` not working properly with `run_time` argument of `Scene.play()`
## Description of bug / unexpected behavior
The `Succession` animation group doesn't behave as expected with the `play_time` keyword argument to `Scene.play()`. Instead of adjusting the speed of the animation, each animation sent to `Succession` will have a `run_time` of `1`, and setting `run_time=x` in `Scene.play()` will result in an animation that plays the first `x` animations passed to the `Succession` in `x` seconds (including playing the last of those animations partially if `x` is not an integer), and then skip immediately to the end of the animation.

## Expected behavior
I expect a `Succession` to adjust the play speed of each of its components according to `run_time` so that each animation passed to the `Succession` is played, in order, each taking an equal amount of time, totalling the time passed as `run_time`


## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->

<details><summary>Code for reproducing the problem</summary>

```py
class Test(Scene):
  def construct(self):
    s1 = Square(color=RED).shift(LEFT)
    s2 = Square(color=WHITE).shift(RIGHT)
    s3 = Square(color=BLUE).shift(UP)
    s4 = Square(color=GREEN).shift(DOWN)
    a = Succession(Create(s1), Create(s2), Create(s3), Create(s4))
    self.play(a, run_time=1.5)
    self.wait(2)
```

</details>


## Additional media files
<!-- Paste in the files manim produced on rendering the code above. -->

<details><summary>GIF</summary>

![<!-- PASTE MEDIA HERE -->](https://i.imgur.com/AnHKD32.gif)

</details>


## Logs
<details><summary>Terminal output</summary>
<!-- Add "-v DEBUG" when calling manim to generate more detailed logs -->

```
Manim Community v0.15.2

[05/07/22 06:08:26] DEBUG    Hashing ...                          hashing.py:343
                    DEBUG    Hashing done in 0.019071 s.          hashing.py:355
                    DEBUG    Hash generated :                     hashing.py:358
                             3163782288_966638411_2358810818                    
                    INFO     Animation 0 : Using cached     cairo_renderer.py:75
                             data (hash : 3163782288_966638                     
                             411_2358810818)                                    
                    DEBUG    List of the first few          cairo_renderer.py:84
                             animation hashes of the scene:                     
                             ['3163782288_966638411_2358810                     
                             818']                                              
                    DEBUG    Animation with empty mobject       animation.py:173
                    DEBUG    Hashing ...                          hashing.py:343
                    DEBUG    Hashing done in 0.009540 s.          hashing.py:355
                    DEBUG    Hash generated :                     hashing.py:358
                             2201830969_3375341780_1355695238                   
                    INFO     Animation 1 : Using cached     cairo_renderer.py:75
                             data (hash : 2201830969_337534                     
                             1780_1355695238)                                   
                    DEBUG    List of the first few          cairo_renderer.py:84
                             animation hashes of the scene:                     
                             ['3163782288_966638411_2358810                     
                             818', '2201830969_3375341780_1                     
                             355695238']                                        
                    INFO     Combining to Movie file.   scene_file_writer.py:597
                    DEBUG    Partial movie files to     scene_file_writer.py:538
                             combine (2 files): ['/User                         
                             s/uname/python/test                         
                             /media/videos/test                         
                             /480p15/partial_movie_file                         
                             s/Test/3163782288_96663841                         
                             1_2358810818.mp4', '/Users                         
                             /uname/python/test                        
                             /media/videos/test/                         
                             480p15/partial_movie_files                         
                             /Test/2201830969_337534178                         
                             0_1355695238.mp4']                                 
[05/07/22 06:08:27] INFO                                scene_file_writer.py:718
                             File ready at '/Users/uname                       
                             /python/test/me                         
                             dia/videos/test/480p1                         
                             5/Test_ManimCE_v0.15.2.gif                         
                             '                                                  
                                                                                
                    INFO     Rendered Test                          scene.py:240
                             Played 2 animations                                
                    INFO     Previewed File at: '/Users/uname       file_ops.py:213
                             /python/test/media/videos/test            
                             /480p15/Test_ManimCE_v0.15.2                
                             .gif'                                              


```

<!-- Insert screenshots here (only when absolutely necessary, we prefer copy/pasted output!) -->

</details>


## System specifications

<details><summary>System Details</summary>

- OS (with version, e.g., Windows 10 v2004 or macOS 10.15 (Catalina)): macOS 12.3.1 
- RAM: 8GB
- Python version (`python/py/python3 --version`): 3.10.0
- Installed modules (provide output from `pip list`):
```
Package                   Version
------------------------- -----------
backports.cached-property 1.0.1
certifi                   2021.10.8
charset-normalizer        2.0.12
click                     8.0.4
click-default-group       1.2.2
cloup                     0.13.0
colour                    0.1.5
commonmark                0.9.1
Cython                    0.29.28
decorator                 5.1.1
glcontext                 2.3.5
idna                      3.3
isosurfaces               0.1.0
manim                     0.15.2
ManimPango                0.4.0.post2
mapbox-earcut             0.12.11
moderngl                  5.6.4
moderngl-window           2.4.1
multipledispatch          0.6.0
networkx                  2.7.1
numpy                     1.22.2
Pillow                    9.0.1
pip                       22.0.4
pycairo                   1.21.0
pydub                     0.25.1
pyglet                    1.5.23
Pygments                  2.11.2
pyobjc-core               8.4.1
pyobjc-framework-Cocoa    8.4.1
pyrr                      0.10.3
requests                  2.27.1
rich                      12.3.0
scipy                     1.8.0
screeninfo                0.8
setuptools                57.4.0
six                       1.16.0
skia-pathops              0.7.2
srt                       3.5.2
tqdm                      4.63.0
urllib3                   1.26.9
watchdog                  2.1.6
```
</details>

<details><summary>FFMPEG</summary>

Output of `ffmpeg -version`:

```
ffmpeg version 5.0.1 Copyright (c) 2000-2022 the FFmpeg developers
built with Apple clang version 13.0.0 (clang-1300.0.29.30)
configuration: --prefix=/usr/local/Cellar/ffmpeg/5.0.1 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox
libavutil      57. 17.100 / 57. 17.100
libavcodec     59. 18.100 / 59. 18.100
libavformat    59. 16.100 / 59. 16.100
libavdevice    59.  4.100 / 59.  4.100
libavfilter     8. 24.100 /  8. 24.100
libswscale      6.  4.100 /  6.  4.100
libswresample   4.  3.100 /  4.  3.100
libpostproc    56.  3.100 / 56.  3.100```
</details>




Discussion:

Thank you for your report! I just investigated a bit: The reason why this happens is due to the, admittedly, very strange way of `AnimationGroup` of handling a change of `run_time`. Instead of rewriting the animation timing table (in the `anims_with_timings` attribute) when the global runtime of an `AnimationGroup` is changed, the time in `AnimationGroup.interpolate` is scaled using the original run time determined at initialization.

While `Succession` inherits from `AnimationGroup`,  it overrides `interpolate` and uses its own mechanism which scales the time with the specified `run_time` (which does not necessarily fit to the timings determined initially).

I see three possible ways of resolving this:

1. The implementation is changed in the spirit of the solution in `AnimationGroup` and the time in `Succession.interpolate` should be scaled with the (initially determined) end time of the final animation, or
2. We change the way timings in `anims_with_timings` are stored to be independent of the animation run length, everything should be made relative, or
3. We turn `run_time` into a property of `AnimationGroup` whose setter recomputes the `anims_with_timings` attribute, then the time in `interpolate` can be scaled with `self.run_time` in any instance.

To be fair, there *is* a fourth solution as well: getting rid of `Succession` entirely because it is (or at least should be) just a glorified version of `AnimationGroup` with `lag_ratio` defaulting to 1. Looking at the code, I'm somewhat sure that `lag_ratio` smaller than 1 breaks the implementation anyways (there is always only exactly one active animation). I can remember there were some special cases where `Succession` behaved nicer than `AnimationGroup`, but perhaps these have been fixed in the meantime...

Resolving patch:
diff --git a/manim/animation/composition.py b/manim/animation/composition.py
--- a/manim/animation/composition.py
+++ b/manim/animation/composition.py
@@ -166,7 +166,7 @@ def next_animation(self) -> None:
         self.update_active_animation(self.active_index + 1)
 
     def interpolate(self, alpha: float) -> None:
-        current_time = self.rate_func(alpha) * self.run_time
+        current_time = self.rate_func(alpha) * self.max_end_time
         while self.active_end_time is not None and current_time >= self.active_end_time:
             self.next_animation()
         if self.active_animation is not None and self.active_start_time is not None:


Test patch:
diff --git a/tests/module/animation/test_composition.py b/tests/module/animation/test_composition.py
--- a/tests/module/animation/test_composition.py
+++ b/tests/module/animation/test_composition.py
@@ -90,6 +90,26 @@ def test_succession_in_succession_timing():
     assert nested_succession.active_animation is None
 
 
+def test_timescaled_succession():
+    s1, s2, s3 = Square(), Square(), Square()
+    anim = Succession(
+        FadeIn(s1, run_time=2),
+        FadeIn(s2),
+        FadeIn(s3),
+    )
+    anim.scene = MagicMock()
+    anim.run_time = 42
+    anim.begin()
+    anim.interpolate(0.2)
+    assert anim.active_index == 0
+    anim.interpolate(0.4)
+    assert anim.active_index == 0
+    anim.interpolate(0.6)
+    assert anim.active_index == 1
+    anim.interpolate(0.8)
+    assert anim.active_index == 2
+
+
 def test_animationbuilder_in_group():
     sqr = Square()
     circ = Circle()