Scaling: Resource: compose an absolute location in one pass instead of re-deriving it per level - #1248
Merged
BioCam merged 2 commits intoSep 9, 2026
Conversation
…ch rotation matrix once `get_absolute_location` answered by recursing into its parent, and at every level of that recursion built two rotation matrices - its own and its parent's - through `get_absolute_rotation`, which is itself a walk of the same chain. A resource three levels down therefore built seven matrices and composed the rotation chain nine times to answer one question, and each matrix costs twelve trigonometry calls and two 3x3 multiplications in pure Python. The chain is now collected once, topmost first, and walked back down accumulating the position. A matrix is built where a resource turns and left alone where it does not, so a chain of squarely placed resources builds one rather than one per level. `get_absolute_rotation` is called once, at the top of the chain, instead of at every level. Two subtleties are preserved deliberately. The walk stops at the first ancestor carrying no location, because there is nothing there to add - `location` is optional throughout and six callers rely on it, among them racks in an incubator and stacks in a BenchCel. The rotation, however, is taken from the whole tree, since an ancestor can turn what hangs from it without positioning it. The two have never referenced the same frame and this does not change that. Behaviour is unchanged: `Resource.get_absolute_rotation` and `Rotation.get_rotation_matrix` are untouched, and every returned coordinate is identical, not merely close. Checked against the previous implementation on 670,761 absolute locations - all 332 catalog resources constructible from a name alone, placed and rotated, every descendant, all 27 anchor combinations - and on 10,330 positions in random trees to depth 7 with rotations on all three axes, including chains hanging from an ancestor that carries no location. Zero differences in both. A well three levels down goes from 71 to 26 us, and a sweep of a loaded STARlet deck's 996 resources from 70 to 26 ms. Depth stops compounding: six levels deep goes from 105 to 35 us. Tests cover a rotated three-level chain against hand-checked coordinates, and the walk against level-by-level composition across rotation angles, three anchor combinations and a chain hanging from a location-less ancestor. Five planted mistakes are caught by them: turning a location by the child's matrix instead of its parent's, ignoring rotation above a location-less ancestor, skipping the matrix where the chain does turn, stopping the walk early, and dropping the requested anchor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ain turns Almost nothing on a deck is rotated, and turning a vector by nothing returns the vector: the matrix is the identity, and `matrix_vector_multiply_3x3` against it hands back its input. Building that matrix still costs twelve trigonometry calls and two 3x3 multiplications, and every multiplication through it still allocates a `Coordinate`. `get_absolute_location` now builds no matrix at all while the accumulated rotation is zero, and adds the offsets directly. The moment a resource in the chain turns, the matrix is built and the rest of the walk proceeds as before, so a rotated chain is unaffected beyond the levels above the rotation. `Rotation(0, 0, 0)` is the identity in both the current Euler representation and the quaternion one proposed upstream, so the test is exact rather than approximate. Returned coordinates are unchanged, again identically rather than closely: the same 670,761 catalog positions and 10,330 synthetic ones, zero differences. A well three levels down goes from 26 to 10 us and a loaded STARlet deck's 996 resources from 26 to 9 ms, taking the pair of changes to 71 -> 10 us and 70 -> 9 ms. Nine fewer `Coordinate` objects are allocated per query as a side effect, and twenty-seven fewer roundings; the rest of those remain, since every intermediate result is still carried in a `Coordinate`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
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.
Hi everyone,
In this PR, we continue improving our
Resourcemodelling system's efficiency, on our way to a more scalable PLR:Resource.get_absolute_locationis now ~7x faster on a typical deck:it collects the parent chain once and builds at most one rotation matrix per query, instead of rebuilding them at every level.
And the cost no longer grows meaningfully with nesting depth.
Results are bit-identical and fully backwards-compatible.
Where resources are rotated the improvement is smaller but never negative:
2.9x with one rotation high in the chain, 1.7x with every level turned.
The final row highlights a reduced startup cost:
assign_child_resourcecomputes positions as resources are placed, so a loaded STARlet deck builds in 91 ms rather than 643 ms.-> your scripts will start faster - yeah 🎉💨
Problem
get_absolute_locationrecursed into its parent, and at each level built two rotation matrices - its own and its parent's - throughget_absolute_rotation, which is itself a walk of the same chain.A resource d levels down built 2d+1 matrices and added up the rotation chain d² times to answer one question.
Each matrix is twelve trigonometry calls and two 3x3 multiplications in pure Python, and on a deck where nothing is rotated, every one of those matrices is the identity matrix, so every multiplication through it returns the offset unchanged.
Changes
get_absolute_locationcollects the chain it is positioned through, then walks back down accumulating the position. A matrix is built where a resource turns and left as it was where it does not.get_absolute_rotationis called once, at the top of that chain, rather than at every level.Behaviour: unchanged. Every coordinate is bit-for-bit what the recursion returned, not merely within a tolerance.
get_absolute_rotationandRotation.get_rotation_matrixare untouched; only how often they are called changed.Scope:
the walk still stops at the first ancestor carrying no
location, and the rotation is still taken from the whole tree.The two methods have never referenced the same frame, and six callers rely on the location-less state; making them agree would move results and belongs in its own PR.
Not addressed / next steps:
repeated queries still recompute from scratch, and every intermediate result is still carried in a
Coordinatethat rounds three numbers on construction.Caching absolute locations would need invalidation on every move, which is a much riskier change and worth measuring again only once this and the
Coordinatework have landed (i.e. out of scope for this PR).#1247 moves
Rotationto internal quaternion storage, which makesget_rotation_matrixabout 17x faster butRotation.__add__about 24x slower.The old recursion leaned on exactly the operation #1247 makes slower, calling
__add__d² times per query, so against current main that PR measures 71 us to 87 us here.By removing the d² complexity entirely, this PR therefore clears the way for #1247 to be purely an improvement:
in its primary intent, correct rotation composition, and in its side effect on compute cost.
Tests
Two added:
a rotated four-level chain checked against hand-computed coordinates at three anchor points, and a chain hanging from an ancestor that turns what it holds without positioning it.
Checked separately against the implementation it replaces on 670 761 absolute locations - every descendant of all 332 catalog resources constructible from a name alone, placed and rotated, across all 27 anchor combinations - and on 10 330 positions in random trees to depth 7.
Zero differences, at exact equality rather than a tolerance.