-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
NumberLine
in order to speedup itsnumber_to_point
method, 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_start
andVMobject.get_end
, which are the most important bottleneck in general.UnitLinearBase
inbases.py
, with methodsfunction
andinverse_function
that 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.vstack
NOTE: 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.vstack
does in this case is to transformalphas
into a column, i.e. ifalphas
was[0.4, 0.8, 0.1]
, it becomes[[0.4], [0.8], [0.1]]
. The thing is,np.vstack
does 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_start
andTipableVMobject.get_end
In the last line:
there are calls to
TipableVMobject
's methodsget_start
andget_end
. They check if the line has a tip (callinghas_tip
which doesreturn hasattr(self, tip) and self.tip in self
, which is expensive) and, if it does, call itsget_start
method, 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 theNumberLine
should 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" inNumberLine
becomes shorter when tips are added, and thus theNumberLine.x_range
attribute 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_tip
andpop_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_tips
andx_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_tips
instead ofx_range
:IMPORTANT NOTE: With those parameters, I also modified some other functions to optimize them: see
point_to_number
,get_unit_vector
andget_unit_size
.NumberLine.scaling.inverse_function
and the adding ofUnitLinearBase
Finally, 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 ofNumberLine
is aLinearBase
, whose default scale factor is 1, and its function and inverse function consist ofreturn self.scale_factor * value
andreturn value / self.scale_factor
respectively. 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_point
as much as possible for my use case, I decided to create a newUnitLinearBase
whose function and inverse function consist solely of returning the same value passed as parameter:Then I imported it in
number_line.py
and used it as the defaultNumberLine.scaling
attribute. 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