On the general ergonomics of apply
#82
Replies: 1 comment
Ergonomics of single-coordinate transformationsI feel strongly about keeping the core logic focused around transforming more than one point. This is the hot path as far as performance goes, and if you're transforming one point at a time, it's pretty clear performance is not important, and so doing a little extra work is OK. Putting a big sign saying "this method takes a 2D array" is much simpler to document and a much more clear API surface than "hey maybe it's 1D, but also maybe a scalar, and here are the 6 different output types it could give depending on what goes in". I would be open to a convenience method on the base class like class Transform(...):
def apply_once(self, point: Sequence[float]) -> ArrayT:
if not is_array_api_object(point):
# figure out the generics here somehow
point = np.array(point)
xp = array_namespace(point)
coords = xp.reshape(point, (1, -1))
out = self.apply(coords)
return xp.reshape(out, (-1,))That way, all the classes get the functionality for free (including those people have yet to implement), but it's a well-defined separate path. Splitting affinesThis isn't backed up by rigorous benchmarks (I should do that, though), but padding an array means copying the whole thing, which I was trying to avoid, as it's likely to be particularly slow on backends like dask and/or problematic on GPU backends. Meanwhile, my expectation is that (no more than) 2 calls to much simpler array ops would be pretty cheap. I would prefer not to have users manually do |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
I sometimes find
Transform.applya bit annoying to use if I just have a point or vector lying around, as I have to wrap it into another array.Another somewhat related topic is that it feels like homogenous coordinates are underutilized; The code in
Affine, for example, explicitly splits the matrix intoself._linear_mapandself._translation, and applies them individually to the incoming points instead of just multiplying the matrix by the point. This of course works fine, but it prevents one from differentiating between "points or positions", which are affected by translation and havew=1, and "vectors or directions", which are not affected by translation and do so by havingw=0.I see the comment in the
Affine.applymethod, mentioning that padding is slower, and maybe that's enough of a reason. Or perhaps we should just leave the padding to the user? I haven't explored this enough (specially in the performance side of things) to have a strong opinion on this yet. I wonder if you have any ideas, though =)All reactions