Repository: ManimCommunity/manim

Issue:
proportion_from_point is not working for some cases
## Description of bug / unexpected behavior
<!-- Add a clear and concise description of the problem you encountered. -->
Hello, I'm trying to get the proportion of the vertices from a triangle outline, but the result is not correct for some cases.

For easy testing purpose, I tried a regular triangle which is shifted and scaled somehow. 
The result i got was `0, 0.33333333333333337, 16336461.942820664` for the three vertices instead of `0, 0.3333333333333333, 0.6666666666666667`. 
Obviously, the proportion of the third vertex was wrong.


## Expected behavior
<!-- Add a clear and concise description of what you expected to happen. -->
In the case mentioned above, i should get `0, 0.3333333333333333, 0.6666666666666667` for each vertex of any regular triangle regardless of whether it's shifted/scaled or not.

## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->
Here is the code i used for testing the regular triangle case:
<details><summary>Code for reproducing the problem</summary>

## 1. Without shift and scale, the result is ok.
```py
class TestProportion(Scene):
    def construct(self):
        from math import sqrt
        A=sqrt(3)*UP
        B=LEFT
        C=RIGHT
        abc = Polygon(A,B,C)

        for p in abc.get_vertices():
            print(abc.proportion_from_point(p))

================================
output:

0.0
0.3333333333333333
0.6666666666666666
```

## 2. With shift only, the result is ok.
```py
class TestProportion(Scene):
    def construct(self):
        from math import sqrt
        A=sqrt(3)*UP
        B=LEFT
        C=RIGHT
        abc = Polygon(A,B,C)
        abc.shift(LEFT)

        for p in abc.get_vertices():
            print(abc.proportion_from_point(p))

================================
output:

0.0
0.3333333333333333
0.6666666666666666
```

## 3. With scale only, the result is ok.
```py
class TestProportion(Scene):
    def construct(self):
        from math import sqrt
        A=sqrt(3)*UP
        B=LEFT
        C=RIGHT
        abc = Polygon(A,B,C)
        abc.scale(0.8)

        for p in abc.get_vertices():
            print(abc.proportion_from_point(p))

================================
output:

0.0
0.3333333333333333
0.6666666666666666
```

## 4. With shift and scale, the result is NOT ok.
```py
class TestProportion(Scene):
    def construct(self):
        from math import sqrt
        A=sqrt(3)*UP
        B=LEFT
        C=RIGHT
        abc = Polygon(A,B,C)
        abc.shift(LEFT)
        abc.scale(0.8)
        for p in abc.get_vertices():
            print(abc.proportion_from_point(p))

================================
output:

0.0
0.33333333333333337
16336461.942820664
```

</details>


## Additional media files
<!-- Paste in the files manim produced on rendering the code above. -->

<details><summary>Images/GIFs</summary>

<!-- PASTE MEDIA HERE -->

</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. -->
I found the wrong value '16336461.942820664' was from `proportions_along_bezier_curve_for_point`, it could be caused by the accuracy while finding the roots of the bezier polynomial. 
As per the reference of [Numpy](https://numpy.org/doc/stable/reference/generated/numpy.polynomial.polynomial.Polynomial.roots.html?highlight=roots#numpy.polynomial.polynomial.Polynomial.roots), 
>the accuracy of the roots decrease the further outside the domain they lie.

- Maybe we could do some correction on the coefficient (like we are rounding the roots) before solving the polynomial?
- Or we could bypass all the roots which do not belong to [0,1]. Not sure why this is not checked currently, any reason that i missed?

p.s. The two approaches above are roughly tested on my case and works, but maybe there are better fixes that i'm not aware of.

At last, thanks for the great library, it really helps making math video much more easily.


Discussion:
Apologies for the very late reply, and thank you for this very fine report!

> Or we could bypass all the roots which do not belong to [0,1]. Not sure why this is not checked currently, any reason that i missed?

This seems to be like a very simple solution if this really fixes the issue -- do you happen to remember which changes you applied to where exactly, or would you perhaps be willing to contribute a PR with these changes?

Resolving patch:
diff --git a/manim/utils/bezier.py b/manim/utils/bezier.py
--- a/manim/utils/bezier.py
+++ b/manim/utils/bezier.py
@@ -478,7 +478,7 @@ def proportions_along_bezier_curve_for_point(
 
     roots = [[root for root in rootlist if root.imag == 0] for rootlist in roots]
     roots = reduce(np.intersect1d, roots)  # Get common roots.
-    roots = np.array([r.real for r in roots])
+    roots = np.array([r.real for r in roots if 0 <= r.real <= 1])
     return roots
 
 


Test patch:
diff --git a/tests/test_vectorized_mobject.py b/tests/test_vectorized_mobject.py
--- a/tests/test_vectorized_mobject.py
+++ b/tests/test_vectorized_mobject.py
@@ -3,7 +3,17 @@
 import numpy as np
 import pytest
 
-from manim import Circle, Line, Mobject, RegularPolygon, Square, VDict, VGroup, VMobject
+from manim import (
+    Circle,
+    Line,
+    Mobject,
+    Polygon,
+    RegularPolygon,
+    Square,
+    VDict,
+    VGroup,
+    VMobject,
+)
 from manim.constants import PI
 
 
@@ -295,3 +305,14 @@ def test_vmobject_point_at_angle():
     a = Circle()
     p = a.point_at_angle(4 * PI)
     np.testing.assert_array_equal(a.points[0], p)
+
+
+def test_proportion_from_point():
+    A = np.sqrt(3) * np.array([0, 1, 0])
+    B = np.array([-1, 0, 0])
+    C = np.array([1, 0, 0])
+    abc = Polygon(A, B, C)
+    abc.shift(np.array([-1, 0, 0]))
+    abc.scale(0.8)
+    props = [abc.proportion_from_point(p) for p in abc.get_vertices()]
+    np.testing.assert_allclose(props, [0, 1 / 3, 2 / 3])