-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Optimized partial and smooth Bézier curve computations in bezier.py
#3281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
chopan050
wants to merge
51
commits into
ManimCommunity:main
from
chopan050:optimized_partial_smooth_beziers
Closed
Optimized partial and smooth Bézier curve computations in bezier.py
#3281
chopan050
wants to merge
51
commits into
ManimCommunity:main
from
chopan050:optimized_partial_smooth_beziers
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…ptimized_partial_smooth_beziers Pull from main to see if there are merge conflicts
…ial_quadratic_bezier_points
bezier.py
3 tasks
3 tasks
Hello everyone! It's been a long time.
|
MrDiver
reviewed
Dec 2, 2023
3 tasks
Update:
|
…ptimized_partial_smooth_beziers
h0: Point3D, | ||
h1: Point3D, | ||
a1: Point3D, | ||
) -> Point3D_Array: ... |
Check notice
Code scanning / CodeQL
Statement has no effect Note
This statement has no effect.
h0: Point3D_Array, | ||
h1: Point3D_Array, | ||
a1: Point3D_Array, | ||
) -> Point3D_Array: ... |
Check notice
Code scanning / CodeQL
Statement has no effect Note
This statement has no effect.
3 tasks
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Overview: What does this pull request change?
I made many changes to the functions defined in bezier.py to calculate Bézier curves faster. Here is a list of those changes, starting from the most important ones:
partial_quadratic_bezier_points
,split_quadratic_bezier
,subdivide_quadratic_bezier
andquadratic_bezier_remap
with generalized and optimized analogspartial_bezier_points
,split_bezier
,subdivide_bezier
andbezier_remap
bezier_remap
reused(OpenGL)VMobject.insert_n_curves_to_point_list
, so I rewrote the latter methods by usingbezier_remap
insteadget_smooth_cubic_bezier_handle_points
get_smooth_handle_points
toget_smooth_cubic_bezier_handle_points
get_smooth_cubic_bezier_handle_points_for_open_curve
andget_smooth_cubic_bezier_handle_points_for_closed_curve
diag_to_matrix
and thescipy
importis_close
, and slightly optimizedbezier
andinterpolate
Motivation and Explanation: Why and how do your changes improve the library?
partial_bezier_points
The original algorithm made a lot of calls to
bezier
, making multiple redundant intermediate calculations that could've been reused for the other Béziers. Instead, in the n-th degree case I calculate all of the necessary points in a single pass, improving from O(n³) to O(n²) time. Even more, the process can be encoded as a matrix multiplication, which is what I did for the simple cases (up to cubic curves), giving around 10x speedup.split_bezier and subdivide_bezier
Again, the processes can be encoded as matrix multiplications. In the case of
subdivide_bezier
, these matrices can even be memoized, as they're always the same.bezier_remap
I realized that I could essentially reuse the code for
VMobject.insert_n_curves_to_point_list
for this. Notice the use ofsubdivide_bezier
instead of repeated use ofpartial_bezier_points
, which is much faster than the original algorithm:Because of the above, I also straight up rewrote
(OpenGL)VMobject.insert_n_curves_to_point_list
to usebezier_remap
instead:get_smooth_handle_points
Now we arrive to the first function in
bezier.py
I modified, and the main reason I made this PR.I had a test scene with more than 100
ParametricFunction
s in it, which were all being updated. More than half of the render time was spent onParametricFunction.generate_points
, and one of the 3 main culprits wasVMobject.make_smooth
. There were many issues in that function I plan to address in another PR, but the one I'm going to address in this specific PR isget_smooth_handle_points
.The main bottleneck for this function is the call to
scipy.linalg.solve_banded
. Apparently, it spends more time validating the arrays passed as a parameter, than actually solving the system of equations.That's why I decided to manually implement the algorithm for solving these equations. But also, all the matrices follow the same pattern and their coefficients are pretty much always the same, varying only in size, so the diagonals can be hardcoded in the algorithm rather than stored explicitly. Some of them can even be memoized, which is what I did!
In the end, I got a speedup of around 3x. I can show a Snakeviz output of this, but that output includes a change to the
is_closed
function which really needed a rewrite, so lemme talk about it first.is_closed
Repeatedly using
np.allclose
to compare only two 3D points each time is actually very expensive and takes a painful part of the total time ofget_smooth_handle_points
. So I rewrote it to a simple "compare 1st coords, then compare 2nd coords, then compare 3rd coords", which resulted in almost a 30x speedup!SnakeViz output for
VMobject.make_smooth
before/after the changes toget_smooth_handle_points
andis_closed
:A closer look to
get_smooth_handle_points
:The red block at the right is the new
is_closed
. It now takes much less time to run! Combined with theget_smooth_handle_points
optimization, this latter function now has an overall speedup of around 4x.interpolate
Rewriting this function to use 1 product instead of 2, makes a speedup of around 30%:
bezier
First of all, I added the cases for Bézier curves of grade 0 and 1. This was originally intended for
partial_bezier_curves
, which repeatedly calledbezier
and was not prepared for these specific cases, falling back to the expensive general nth grade case:Then, I made a very slight optimization for the cubic Bëzier case:
It reported a small speedup of around 10% - 15%. Not too bad! I did something similar with the quadratic Bëzier:
Finally, the general nth grade Bézier case. I avoided the extensive use of the
choose
function which can be expensive, and instead used the recursive definition of Béziers I used for functions likepartial_bezier_points
andsubdivide_bezier
.Also,
bezier
now returns named functions instead of lambdas, which is good for debugging and profiling: now you can know which function is taking what part of the time when profiling your code.Links to added or changed documentation pages
manim.utils.bezier
https://manimce--3281.org.readthedocs.build/en/3281/reference/manim.utils.bezier.html
Reviewer Checklist