Skip to content

Conversation

@Nikhil172913832
Copy link
Contributor

Overview: What does this pull request change?

Fixes issue where MathTex submobjects did not correctly correspond to their tex_strings when using subscripts and superscripts in different orders. The fix uses geometric position matching specifically for script elements (^, _) to handle LaTeX's reordering while preserving sequential matching for non-script elements.

Motivation and Explanation: Why and how do your changes improve the library?

Problem: LaTeX compiles expressions like A ^n _1 and A _1 ^n to identical SVG output where subscripts and superscripts may appear in a different order than specified. This caused MathTex('A', '^n', '_1') and MathTex('A', '_1', '^n') to have submobjects that didn't match their original tex_strings, breaking operations like get_parts_by_tex() and set_color_by_tex().

Solution: Modified _break_up_by_substrings() method to detect script elements (tex strings starting with ^ or _) and match them to rendered submobjects based on geometric position (center point). Non-script elements continue using sequential matching to maintain backward compatibility and avoid issues with complex formulas.

Impact: Users can now reliably access and manipulate subscripts/superscripts by their tex strings regardless of the order they're specified.

Links to added or changed documentation pages

No documentation changes required.

Further Information and Comments

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

…ition

Use geometric matching for script elements to handle LaTeX reordering
while preserving sequential matching for non-script elements.
@henrikmidtiby
Copy link
Contributor

Thanks for the PR.

I have tried to run your new test without the suggested changes to the tex_mobject.py file.
I would expect the test to reveal an issue with the matching process.
But on my computer the test passes without any issues...

Similarly I have tested the full PR on the two scenes (Minimal and MinimalWithSum) reported in issue #3548. Both scenes still fail even with the changes from this PR active.
Do you get similar results?

@Nikhil172913832
Copy link
Contributor Author

@henrikmidtiby Thanks for pointing that out. I had initially overlooked the fix, and since the test passed, I missed verifying whether the original issue was actually resolved. I’ve now updated the test and revised my approach. The test correctly fails on the main branch now.

@henrikmidtiby
Copy link
Contributor

Good progress.

I have tried to apply the current PR to the following test case (from #3548)

from manim import *

class MinimalWithSum(Scene):
    def construct(self):
        """ This shows that substring may not correspond to tex shape """
        t2cm = {'\sum': BLUE, '^n': RED, '_1': GREEN, 'x':YELLOW}
        eq1 = MathTex('\sum', '^n', '_1', 'x', tex_to_color_map=t2cm)
        eq2 = MathTex('\sum', '_1', '^n', 'x', tex_to_color_map=t2cm)

        font = {'font_size': 24}
        txts = [Text(sub.get_tex_string(), t2c=t2cm, **font) for sub in (eq1, eq2) for i in range(len(sub))]
        txt1 = VGroup(*txts[:4])
        txt2 = VGroup(*txts[4:])

        cap1 = Text('tex rendered', **font)
        cap2 = Text('tex substrings', **font)
        
        grp = VGroup(cap1, cap2, eq1, txt1, eq2, txt2).arrange_in_grid(3,2)
        grp.scale(2).move_to(ORIGIN)
        self.add(grp)

Which renders as shown here.

MinimalWithSum_ManimCE_v0 19 0

Which is more consistent than if I render the scene using the current main branch, that produces this output.

MinimalWithSum_ManimCE_v0 19 0

I still think that the coloring is off in both cases, as I would expect the summation signs to be blue.

In addition I wonder if it is possible to extract some of the functionality into a separate method. The intention here is to make it easier to understand what the code is actually doing.
Prior to this PR I had to pay close attention to understand the 26 lines of code in the _break_up_by_substrings method. The method is now close to 100 lines and I haven't yet managed to really understand what is happening (e.g. why should the order of the sorted_pool be reversed in some cases).

@Nikhil172913832
Copy link
Contributor Author

Screenshot from 2025-11-04 12-20-04 @henrikmidtiby does it look correct now?

@henrikmidtiby
Copy link
Contributor

@Nikhil172913832 Much better!
This is exactly what I would expect from reading the code for the MinimalWithSum scene.

Nikhil172913832 and others added 2 commits November 6, 2025 11:51
@Nikhil172913832
Copy link
Contributor Author

@henrikmidtiby I’ve made the necessary changes related to the colors. Please let me know if everything looks good.

@henrikmidtiby
Copy link
Contributor

Nice to see your progress on this.
I have attempted to find an example where the colors of the parts of the MathTex is assigned in an unwanted way. Until now I haven't been successful at that. However I have found this example, where parts of the extracted tex strings seems to duplicated in certain conditions.

from manim import *

class MinimalWithSumDifficult(Scene):
    def construct(self):
        """ This shows that substring may not correspond to tex shape """
        t2cm = {'\sum': BLUE, '^n': RED, '_1': GREEN, 'x':YELLOW}
        eq1 = MathTex(r'\sum', '^n', '_1', 'x', '^2', '= n_2', tex_to_color_map=t2cm)
        eq2 = MathTex(r'\sum', '_1', '^n', 'x', '^2', '= n_2', tex_to_color_map=t2cm)

        font = {'font_size': 24}
        txts = [Text(sub.get_tex_string(), t2c=t2cm, **font) for sub in (eq1, eq2) for i in range(len(sub))]
        for txt in txts: 
            print(txt)
        txt1 = VGroup(*txts[:4])
        txt2 = VGroup(*txts[4:])

        cap1 = Text('tex rendered', **font)
        cap2 = Text('tex substrings', **font)
        
        grp = VGroup(cap1, cap2, eq1, txt1, eq2, txt2).arrange_in_grid(3,2)
        grp.scale(1).move_to(ORIGIN)
        self.add(grp)

On my computer it renders as shown here:

MinimalWithSumDifficult_ManimCE_v0 19 0

It seems like the strings "^n" and "_1" have been duplicated in the lower equation.

@henrikmidtiby
Copy link
Contributor

Now I managed to find a case, where the new code seems to render the equation badly.

from manim import *

class MathTexUnexpectedBehaviour(Scene):
    def construct(self):
        t = MathTex("\\int^b{{_a}} dx = b - a")
        self.add(t)

        t[1].set_color(RED)

Which renders as

MathTexUnexpectedBehaviour_ManimCE_v0 19 0

Where I miss the upper limit of the integral.
The issue disappears if the limits of the integral are interchanged.

from manim import *

class MathTexUnexpectedBehaviour(Scene):
    def construct(self):
        t = MathTex("\\int{{_a}}^b dx = b - a")
        self.add(t)

        t[1].set_color(RED)
MathTexUnexpectedBehaviour_ManimCE_v0 19 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 🆕 New

Development

Successfully merging this pull request may close these issues.

Rendered submobjects of MathTex may not correspond to their tex strings

2 participants