Skip to content

Add origin offset to attenuate_all for translation-invariant bucketing - #50

Merged
allisonrenie merged 1 commit into
mainfrom
48/origin-offset
Aug 21, 2026
Merged

Add origin offset to attenuate_all for translation-invariant bucketing#50
allisonrenie merged 1 commit into
mainfrom
48/origin-offset

Conversation

@amarcozzi

Copy link
Copy Markdown
Contributor

Closes #48.

What

Adds an origin=(ox, oy) parameter to attenuate_all so the shadow-bucketing
plane sweep is anchored to a shared global frame, making the algorithm
translation-invariant.

Why

The sweep floors rotated coordinates onto a 1×1 lattice
(plane_sweep, general.py:27/:33).
floor is a phase-dependent quantizer, so the same physical canopy produces
different irradiance depending on where its coordinates start. A sub-window of a
larger domain therefore cannot reproduce a single whole-domain run — which
blocks tiling a large domain into parallel per-tile runs.

How

The offset is added to the leaf-area, terrain, and sensor coordinates before
rotation only
. Because the rotation is linear, a constant world offset before
rotating shifts every rotated coordinate consistently, so two sub-windows of the
same global domain floor into the same lattice. Outputs stay in the input's
local frame — only the shadow grouping is re-anchored. To tile: give each window
the offset of its lower-left corner in the global frame.

Default origin=(0.0, 0.0) is a no-op that reproduces the un-anchored result
exactly.

Tests (TDD)

Written before the implementation:

  • test_origin_default_matches_no_argorigin=(0, 0) reproduces the no-arg
    result byte-for-byte across canopy, terrain, and sensors.
  • test_origin_makes_canopy_translation_invariant — the same canopy expressed at
    a shifted origin gives identical irradiance when origin cancels the shift
    (0% of cells differ), with a guard asserting the shift genuinely perturbs the
    bucketing when not re-anchored (so the test can't pass vacuously).

Full suite: 57 passed. The stored-result assert_equal tests confirm the
default path is unchanged.

Scope

Kept to attenuate_all, the path the tiling consumer (FastFuels treevox) uses.
attenuate_surface is left unchanged. Independent of #49 (voxel_dim
anisotropy), but the two interact: correcting that geometry would change shadow
reach, and hence the up-sun halo a tiling caller supplies.

attenuate_all floors rotated coordinates onto a 1x1 lattice for the
shadow-bucketing plane sweep. Because floor is phase-dependent, the same
physical canopy gives different irradiance depending on where its
coordinates start, so a sub-window of a larger domain cannot reproduce a
whole-domain run even though the algorithm is otherwise deterministic.

Add an `origin=(ox, oy)` parameter (default (0, 0), fully backward
compatible) that is added to the leaf-area, terrain, and sensor
coordinates before rotation only. Since the rotation is linear, a
constant world offset before rotating shifts all rotated coordinates
consistently, anchoring the floor lattice to a shared global frame.
Outputs remain in the input's local frame.

Closes #48
@allisonrenie

Copy link
Copy Markdown
Collaborator

Looks good to me. Planning to merge this first then get the second PR in

@allisonrenie
allisonrenie merged commit 036f7a4 into main Aug 21, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an origin offset so plane-sweep bucketing is translation-invariant (enables tiling)

2 participants