-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Optimized :meth:.NumberLine.number_to_point
#3285
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 :meth:.NumberLine.number_to_point
#3285
Conversation
…lling of VGroup.add
…050/manim into optimized_number_to_point Merge latest commits from main into this branch
NumberLine.number_to_point
|
The tests have failed, but only by errors of really small orders of magnitude (around 1e-16 or less). Can these be ignored (as these errors can be negligible) or not really? |
NumberLine.number_to_point.NumberLine.number_to_point
|
I would still suggest taking a look at the currently failing tests. There maybe is a way to make it perfectly centered again with some small nudging. |
|
Converted to draft while I try to figure out what to do with this. I tried some stuff, and it still doesn't pass the tests:
and it still errors out 😅 I'll figure out later what to do with this. |
Overview: What does this pull request change?
I've made some changes to
NumberLinein order to speedup itsnumber_to_pointmethod, which is relevant when plotting or updating many graphs in a coordinate system:np.vstack(alphas)toalphas.reshape(-1, 1), which removes a major bottleneck when using vectorization.VMobject.get_startandVMobject.get_end, which are the most important bottleneck in general.UnitLinearBaseinbases.py, with methodsfunctionandinverse_functionthat return the same value that is passed, for an extra minor speedup.Motivation and Explanation: Why and how do your changes improve the library?
This is the code I've been using for testing:
There are 109 ParametricFunctions being updated for 5 seconds, which is costly. A big portion of the runtime is spent on
ParametricFunction.generate_points, and there are 3 main culprits:VMobject.make_smooth,VMobject.add_points_as_corners, andAxes.coords_to_point. I'm addressing the first 2 of them in other PRs.In this PR I shall address
Axes.coords_to_point, or more specificallyNumberLine.number_to_point(the pale green block which is two blocks belowAxes.coords_to_point):Here is the original code for
NumberLine.number_to_point:np.vstackNOTE: this problem only happened when an array (rather than a single float) is passed to
NumberLine.number_to_line.The major bottleneck is
np.vstack(alphas). Whatnp.vstackdoes in this case is to transformalphasinto a column, i.e. ifalphaswas[0.4, 0.8, 0.1], it becomes[[0.4], [0.8], [0.1]]. The thing is,np.vstackdoes a lot of preprocessing behind the curtains, and creates a new array to hold these values. In this case, that preprocessing is not necessary, and we don't actually need to copy the array. A simple view generated withalphas.reshape(-1, 1)is sufficient:And this bottleneck is now gone.
TipableVMobject.get_startandTipableVMobject.get_endIn the last line:
there are calls to
TipableVMobject's methodsget_startandget_end. They check if the line has a tip (callinghas_tipwhich doesreturn hasattr(self, tip) and self.tip in self, which is expensive) and, if it does, call itsget_startmethod, or else callTipableVMobject.get_start. They callVMobject.throw_error_if_no_points, which is another verification process. All of that is unnecessarily expensive for this case, where we do know theNumberLineshould have points, and there should be a way to get the x range of the line whether it has tips or not without recurring to these expensive calculations, right?Well, this issue was trickier to solve, because I couldn't just plug in
self.points[0]andself.points[-1]instead ofself.get_start()andself.get_end(). This is because the actual "line" inNumberLinebecomes shorter when tips are added, and thus theNumberLine.x_rangeattribute does no longer represent the range of the line without the tips, but the range of the full line including the tips. I had to do something more.To solve this, I overrode
TipableVMobject's methodsadd_tipandpop_tips, where I calculated the range of the actual portion of the line excluding the tips. For this, I created new attributes forNumberLine:x_range_no_tips,x_min_no_tipsandx_max_no_tips.With that done, now I can just plug in
self.points[0]andself.points[-1]inNumberLine.number_to_point, if I usex_range_no_tipsinstead ofx_range:IMPORTANT NOTE: With those parameters, I also modified some other functions to optimize them: see
point_to_number,get_unit_vectorandget_unit_size.NumberLine.scaling.inverse_functionand the adding ofUnitLinearBaseFinally, there's a very small portion of
NumberLine.number_to_point(the small paler green block at the right ofNumberLine.number_to_line) where we callNumberLine.scaling.inverse_function. Now, the default base ofNumberLineis aLinearBase, whose default scale factor is 1, and its function and inverse function consist ofreturn self.scale_factor * valueandreturn value / self.scale_factorrespectively. If the default scale factor is 1, this just returns the value itself, but multiplying and dividing by 1 still creates some small overhead.It is truly a small detail in comparison to the previous two points, but as I wanted to optimize
NumberLine.number_to_pointas much as possible for my use case, I decided to create a newUnitLinearBasewhose function and inverse function consist solely of returning the same value passed as parameter:Then I imported it in
number_line.pyand used it as the defaultNumberLine.scalingattribute. It's around 20x faster than the original "multiply/divide by 1" approach.Results
I managed a speedup of around 3.5x in
NumberLine.number_to_line! Compare the results:Links to added or changed documentation pages
Further Information and Comments
Reviewer Checklist