ManimCommunity / manim

A community-maintained Python framework for creating mathematical animations.
https://www.manim.community
MIT License
26.81k stars 1.84k forks source link

Hallucinated or dropped edges in Transform of Graph #3946

Open niplav opened 1 month ago

niplav commented 1 month ago

Description of bug / unexpected behavior

When Transforming a Graph into another graph with a different number of edges, the resulting animated graph has the same number of edges as the original graph.

For example, when transforming the graph Graph(["A", "B", "C"], [("A", "B"), ("A", "C")]) to Graph(["A", "B", "C"], [("A", "B"), ("A", "C"), ("B", "C")]), the resulting graph should be a triangle, but one edge is dropped.

Expected behavior

The graph that is the result of a transformation has the number of edges that is specified in the code, not the number of edges the predecessor had.

How to reproduce the issue

Code for reproducing the problem ```py from manim import * class BugDemo(Scene): def construct(self): # Create a graph with two edges graph1 = Graph(["A", "B", "C"], [("A", "B"), ("A", "C")]) # Create another graph with three edges graph2 = Graph(["A", "B", "C"], [("A", "B"), ("A", "C"), ("B", "C")]) # Display the first graph self.play(Create(graph1)) self.wait(1) # Attempt to transform graph1 into graph2 self.play(Transform(graph1, graph2)) self.wait(1) ```

Additional media files

Images/GIFs https://github.com/user-attachments/assets/f54ab219-5e51-4817-beb3-ec9b262329c0

Logs

Terminal output ``` Manim Community v0.18.1 [10/05/24 20:26:24] DEBUG Hashing ... hashing.py:352 DEBUG Hashing done in 0.006551 s. hashing.py:364 DEBUG Hash generated : 1185818338_2661024192_223132457 hashing.py:367 DEBUG List of the first few animation hashes of the scene: ['1185818338_2661024192_223132457'] cairo_renderer.py:97 INFO Animation 0 : Partial movie file written in scene_file_writer.py:527 '/home/niplav/proj/site/anim/turning/media/videos/bug/480p15/partial_movie_files/BugDemo /1185818338_2661024192_223132457.mp4' DEBUG Animation with empty mobject animation.py:175 DEBUG Hashing ... hashing.py:352 DEBUG Hashing done in 0.005637 s. hashing.py:364 DEBUG Hash generated : 624642324_3890345977_2618601865 hashing.py:367 DEBUG List of the first few animation hashes of the scene: ['1185818338_2661024192_223132457', cairo_renderer.py:97 '624642324_3890345977_2618601865'] [10/05/24 20:26:25] INFO Animation 1 : Partial movie file written in scene_file_writer.py:527 '/home/niplav/proj/site/anim/turning/media/videos/bug/480p15/partial_movie_files/BugDemo /624642324_3890345977_2618601865.mp4' DEBUG Hashing ... hashing.py:352 DEBUG Hashing done in 0.011324 s. hashing.py:364 DEBUG Hash generated : 624642324_2810248401_2118276392 hashing.py:367 DEBUG List of the first few animation hashes of the scene: ['1185818338_2661024192_223132457', cairo_renderer.py:97 '624642324_3890345977_2618601865', '624642324_2810248401_2118276392'] INFO Animation 2 : Partial movie file written in scene_file_writer.py:527 '/home/niplav/proj/site/anim/turning/media/videos/bug/480p15/partial_movie_files/BugDemo /624642324_2810248401_2118276392.mp4' DEBUG Animation with empty mobject animation.py:175 DEBUG Hashing ... hashing.py:352 DEBUG Hashing done in 0.006592 s. hashing.py:364 DEBUG Hash generated : 624642324_3890345977_3537993160 hashing.py:367 DEBUG List of the first few animation hashes of the scene: ['1185818338_2661024192_223132457', cairo_renderer.py:97 '624642324_3890345977_2618601865', '624642324_2810248401_2118276392', '624642324_3890345977_3537993160'] INFO Animation 3 : Partial movie file written in scene_file_writer.py:527 '/home/niplav/proj/site/anim/turning/media/videos/bug/480p15/partial_movie_files/BugDemo /624642324_3890345977_3537993160.mp4' INFO Combining to Movie file. scene_file_writer.py:617 DEBUG Partial movie files to combine (4 files): scene_file_writer.py:561 ['/home/niplav/proj/site/anim/turning/media/videos/bug/480p15/partial_movie_files/BugDem o/1185818338_2661024192_223132457.mp4', '/home/niplav/proj/site/anim/turning/media/videos/bug/480p15/partial_movie_files/BugDemo /624642324_3890345977_2618601865.mp4', '/home/niplav/proj/site/anim/turning/media/videos/bug/480p15/partial_movie_files/BugDemo /624642324_2810248401_2118276392.mp4', '/home/niplav/proj/site/anim/turning/media/videos/bug/480p15/partial_movie_files/BugDemo /624642324_3890345977_3537993160.mp4'] INFO scene_file_writer.py:737 File ready at '/home/niplav/proj/site/anim/turning/media/videos/bug/480p15/BugDemo.mp4' INFO Rendered BugDemo scene.py:247 Played 4 animations ```

System specifications

System Details - OS Void Linux: ``` › uname -a (manim) Linux REDACTED 6.6.52_1 #1 SMP PREEMPT_DYNAMIC Sat Sep 21 15:47:36 UTC 2024 x86_64 GNU/Linux › cat /etc/os-release (manim) NAME="Void" ID="void" PRETTY_NAME="Void Linux" HOME_URL="https://voidlinux.org/" DOCUMENTATION_URL="https://docs.voidlinux.org/" LOGO="void-logo" ANSI_COLOR="0;38;2;71;128;97" DISTRIB_ID="void" ``` - RAM: 16GB - Python version (`python/py/python3 --version`): ``` python3 --version (manim) Python 3.12.5 ``` - Installed modules (provide output from `pip list`): ``` Package Version -------------------- ----------- Brotli 1.1.0 build 1.2.1 CacheControl 0.14.0 certifi 2024.7.4 cffi 1.17.0 charset-normalizer 3.3.2 cleo 2.1.0 click 8.1.7 click-default-group 1.2.4 cloup 3.0.5 colorama 0.4.6 crashtest 0.4.1 cryptography 43.0.0 decorator 5.1.1 distlib 0.3.8 dulwich 0.21.7 fastjsonschema 2.20.0 filelock 3.15.4 future 1.0.0 glcontext 2.5.0 h2 4.1.0 hpack 4.0.0 hyperframe 6.0.1 idna 3.7 importlib_metadata 8.2.0 installer 0.7.0 isosurfaces 0.1.2 jaraco.classes 3.4.0 jeepney 0.8.0 keyring 24.3.1 manim 0.18.1 ManimPango 0.5.0 mapbox_earcut 1.0.1 markdown-it-py 3.0.0 mdurl 0.1.2 moderngl 5.10.0 moderngl-window 2.4.1 more-itertools 10.4.0 msgpack 1.0.8 multipledispatch 0.6.0 networkx 3.3 numpy 1.26.4 packaging 24.1 pexpect 4.9.0 pillow 10.4.0 pip 24.2 pkginfo 1.11.1 platformdirs 4.2.2 poetry 1.8.3 poetry-core 1.9.0 poetry-plugin-export 1.8.0 ptyprocess 0.7.0 pycairo 1.26.1 pycparser 2.22 pydub 0.25.1 pyglet 1.5.27 Pygments 2.18.0 pyproject_hooks 1.1.0 pyrr 0.10.3 PySocks 1.7.1 PyYAML 6.0.2 rapidfuzz 3.9.6 requests 2.32.3 requests-toolbelt 1.0.0 rich 13.7.1 scipy 1.14.0 screeninfo 0.8.1 SecretStorage 3.3.3 setuptools 72.1.0 shellingham 1.5.4 six 1.16.0 skia-pathops 0.8.0.post1 srt 3.5.3 svgelements 1.9.6 tomli 2.0.1 tomlkit 0.13.0 tqdm 4.66.5 trove-classifiers 2024.7.2 typing_extensions 4.12.2 urllib3 2.2.2 virtualenv 20.26.3 watchdog 4.0.1 wheel 0.44.0 zipp 3.19.2 zstandard 0.23.0 ```

Additional comments

0x13b commented 1 month ago

After self.play(Transform(graph1, graph2)) you see graph1 left on the screen with 4 dots dots and 2 lines as submobjects. It's rather strange to see. Just print graph1.submobjects out.

Using ReplacementTransform results in graph2 left on the screen with all vertices and edges as expected.

niplav commented 1 month ago

Yes, but when I tried that with a directed graph I got a different error.

OliverStrait commented 3 weeks ago
* graph1 before: 
  * Family:[Undirected graph on 3 vertices and 2 edges, Dot, Dot, Dot, Line, Line] 
  * sub: [Dot, Dot, Dot, Line, Line]
* graph2 
  * Family: [Undirected graph on 3 vertices and 4 edges, Dot, Dot, Dot, Line, Line, Line, Line] 
  * sub: [Dot, Dot, Dot, Line, Line]
* graph1 after Transform.
  * Family: [Undirected graph on 3 vertices and 2 edges, Dot, Dot, Dot, Dot, Dot, Line, Line] 
  * sub: [Dot, Dot, Dot, Dot, Dot, Line, Line]

Observations:

class BugDemo(Scene):

def construct(self):
    def break_model(models:Mobject, where, color):
        for i, model in enumerate(models):
            self.play(model.animate.move_to(model.get_center()+where * (i+1)).set_color(color))
    # Create a graph with two edges
    graph1 = Graph(["A", "B", "C"], [("A", "B"), ("A", "C")])
    # Create another graph with three edges
    graph2 = Graph(["A", "B", "C"], [("A", "B"), ("A", "C"), ("B", "C"), ("C", "A")]).move_to(DL*2 +LEFT)

    # Display the first graph
    self.add(graph2)
    self.play(Create(graph1))

    # Attempt to transform graph1 into graph2
    print("graph1 before:", graph1.get_family(),"sub:", graph1.submobjects)
    t_family = graph2.get_family()
    print("target",t_family ,"sub:", graph1.submobjects)

    self.play(Transform(graph1, graph2))
    g1_after = graph1.get_family()
    print("after", g1_after,"sub:", graph1.submobjects)
    graph2.clear_updaters()
    graph1.clear_updaters()

    self.play(graph1.animate.move_to(UL*2 +LEFT))
    break_model(t_family[1:], RIGHT, YELLOW)
    break_model(g1_after[1:],  RIGHT, RED)
    self.wait(1)


https://github.com/user-attachments/assets/d74c1327-0c0b-4aa7-b7e6-8ebe00667044