-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Optimized various point-related :class:.VMobject methods
#3292
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
Optimized various point-related :class:.VMobject methods
#3292
Conversation
… length memos and binary search
…d similar methods
…hen subpath has a single curve
…hen subpath has a single curve
…050/manim into optimized_vmobject_points
…050/manim into optimized_vmobject_points
…stead of copies, and other mistakes
…in VMobject._update_curve_memory
|
Done! I addressed most comments. def insert_n_curves_to_point_list(self, n: int, points: np.ndarray) -> np.ndarray:
# ... other stuff
for quad, sf in zip(bezier_quads, split_factors):
if sf == 1:
new_points[start_i : start_i + nppcc] = quad
start_i += nppcc
else:
for i in range(sf):
new_points[start_i : start_i + nppcc] = partial_bezier_points(
quad, i / sf, (i + 1) / sf
)
start_i += nppcc
return new_pointsIn #3281 I added a new function in def insert_n_curves_to_point_list(self, n: int, points: np.ndarray) -> np.ndarray:
# ... other stuff
for quad, sf in zip(bezier_quads, split_factors):
new_points[start_i : start_i + sf * nppcc] = subdivide_bezier(quad, sf)
start_i += sf * nppcc
return new_pointsThis would look much cleaner and is also more efficient, since in So, if #3281 gets merged first, I'd wanna add this change to this PR before merging this one. |
…ptimized_vmobject_points
1c5072a to
d6e3aaf
Compare
Overview: What does this pull request change?
Main changes:
np.appendinVMobject's methods, and reduced the use ofVMobject.append_pointsas much as possible, because their extensive use is expensive. When possible, they're replaced with preallocations of empty ndarrays whose lengths can be precalculated.VMobject.append_pointsto usenp.emptyinstead ofnp.append, because it is slightly faster this way. Useful because this method can still get called a lot.np.linspacefromVMobject's methods, because repeated call to this method for small arrays is also too expensive.VMobject.bezier_alphasrelated to thenppcc. This attribute is a precalculation of the alphas needed to interpolate pairs of anchors to obtain the required handles for the new straight segments that shall be represented by Bézier curves. This replaces those special cases wherenp.linspacewas originally used for interpolation.VMobject.consider_points_equalsby replacing thenp.allclosemethod, which is also really expensive when used repeatedly to compare only two 3D points.VMobject.consider_points_equals_2d, where a comparison coord-by-coord is made to determine if the points are close or not.np.allcloseapproach.Added two new methods,EDIT: Nevermind, I removed those methods to avoid code duplication.VMobject.consider_points_differentandVMobject.consider_points_different_2d, because more often than not we actually want to know if two points are distant enough from each other, rather than if they're close to each other.VMobject.get_subpath_split_indicesandVMobject.get_subpath_split_indices_from_points, which instead of explicitly obtaining a Python list of subpaths, it obtains an (n_subpaths, 2) ndarray of indices indicating where every subpath starts and ends.n_dims(which allows us to choose between 2D or 3D point comparison) andstrip_null_end_curves(necessary forVMobject.align_pointswhere the null end curves at every subpath must be removed as a fix to certain bug).VMobject.get_subpaths_from_pointsandVMobject.gen_subpaths_from_points_2dto use this new methodVMobject.get_subpath_split_indices_from_points.VMobject._gen_subpaths_from_pointsis no longer used and can probably be deprecated if this PR is accepted.VMobject.add_points_as_corners: it originally used a for loop to add every point one by one throughVMobject.add_line_to. This was extremely expensive (especially with the previous use ofnp.linspace) and was completely changed to just add all the points at once, calculating the necessary handles via interpolations.VMobject.change_anchor_mode: if using "jagged" mode, it is not necessary at all to separate the points into subpaths (a simple interpolation is sufficient). Only get the subpaths in "smooth" mode. In this case, the new implementation now uses this new methodVMobject.get_subpath_split_indicesinstead of explicitly precalculating all the subpaths previously and storing them all in a Python list. As the number of points remains pretty much the same and only the handles change, there's no need to clear the points or create another ndarray of points: we can just directly overwrite the handles.VMobject.align_points: this method was completely rewritten so that it now usesVMobject.get_subpath_indicestoo. In this way, one can also easily calculate useful information like how long is every subpath (just subtract the pairs of indices!) and, thus, which subpath is longer when comparing pairs of subpaths between the twoVMobjects (vianp.maximum). Thenp.appends were obliterated. As mentioned earlier, this new implementation usesstrip_null_end_curves=Trueto remove the excess null curves at the end of the subpaths, if any.Mobject.memory, which might be useful for memoizing certain desirable properties.VMobject.point_from_proportion.VMobject._init_curve_memoryandVMobject._update_curve_memory, which precalculate the lengths and accumulated lengths of the Bézier curves making up theVMobjectand update it only if the actual points or the number of sample points per curve have changed.VMobject.point_from_proportion's time!VMobject.get_area_vector, where this could be used as well.Motivation and Explanation: Why and how do your changes improve the library?
My original intention was to optimize my test scene where I have 110 ParametricFunctions being updated in a span of 5 seconds, which is really expensive!
Almost half of the time of the scene is spent on
ParametricFunction.generate_points, and there are 3 major culprits:VMobject.make_smooth,VMobject.add_points_as_corners, andAxes.coords_to_point.In #3281 I optimized
get_smooth_handle_points(and other Bézier-related functions), which was an important cause ofVMobject.make_smooth's excessive time, and in #3285 and #3286 I addressed the issue withAxes.coords_to_pointand, more specifically,NumberLine.number_to_point.Regarding the
VMobject.add_points_as_cornersand theVMobject.get_subpaths, which are the other major issues, I initially rewroteParametricFunctionto circumvent those issues. After all, I don't thinkParametricFunction.generate_pointsshould be relying a lot on these functions to, well, generate its points, when they can be generated in other efficient ways. I have some ideas about rewritingParametricFunction, which I wanna discuss in a future PR.However, there are still many other
VMobjectsubclasses which depend on those currently inefficient methods too. As I noticed this was a general issue for manyVMobjects, I decided to address the root issues and directly modify and optimize most ofVMobject's point-related methods.EDIT: here's a new screenshot of how

ParametricFunction.generate_pointslooks after these changes.VMobject.get_subpaths(replaced byVMobject.get_subpath_split_indices) andVMobject.add_points_as_cornersare no longer a problem!Links to added or changed documentation pages
Further Information and Comments
Reviewer Checklist