Repository navigation
Conversation
… not by address Several containers keyed by pointers or CGAL handles were iterated in hash or address order, so a building's model depended on where its objects landed in memory: the output changed with the length of the output path, and from run to run when the I/O threads' allocations shifted the heap. - line regularisation: clusters get a creation id; their sets are ordered by it, which fixes the distance heap's tie order, the merge direction and the cluster ids handed to the lines; - step merging: face pairs are visited in the order the arrangement's edges first meet them; Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZBFfd55Us1zg7chCUDs5C
… uninitialised extremes When a distance cluster holds only intersection lines, there is no other line to bound them, and calc_segment read its uninitialised pmin/pmax/dmin/dmax: the segment took whatever was left in memory, and the model changed from run to run. The intersection line now bounds itself, and calc_segment starts from the centroid so that an empty list can no longer read garbage. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZBFfd55Us1zg7chCUDs5C
…addresses CGAL's edge iterator reports each edge from whichever of its two faces has the lower address, so the snapper's "first short edge" and the order in which the snapped arrangement was rebuilt depended on the heap layout. The snapper now collapses the shortest short edge (ties broken by its endpoints' coordinates), inserts the constrained edges in the order of their endpoints' vertex indices, and seeds the dangling-constraint peeling in vertex order rather than in the order of a hash map keyed by vertex handles. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZBFfd55Us1zg7chCUDs5C
The loop that merges each footprint's buffer ground points into its point cloud started from an uninitialised index, so whether those points were merged depended on whatever the stack held. It now starts at 0, so they are always merged, as the comment above the loop intends. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZBFfd55Us1zg7chCUDs5C
…ng past it gridthinPointcloud skipped points whose cell index was greater than the count raster's size, but not the one equal to it, so a point mapping to that cell read one element past the array. Whether it was kept, and whether a random draw was consumed, then depended on that memory, and so did every following point's draw. Footprint buffer ground points, which lie outside the footprint's raster, hit this case. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZBFfd55Us1zg7chCUDs5C
Member
|
Thanks, this is a welcome contribution. I'll review it as soon as I have time. |
This branch has not been deployed
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.
Addresses #138.
Why
Running the same roofer build twice on the same inputs does not always give the same models. We hit this while comparing roofer versions and settings at scale: differences between two variants were drowned in run-to-run noise.
On 7,393 buildings (a French municipality plus a national sample of houses, IGN LiDAR HD), current
maingives:-j 16, 5,364 buildings, two runsThe output path matters because a path over 15 characters no longer fits in
std::string's inline buffer. It is then heap-allocated, which shifts every later allocation.Causes, one commit each
None of these is TBB: the Conan build does not link it.
Unseeded shuffle:
RegionGrower_DS_CGAL::get_seedsshuffled withstd::random_device. It now uses a fixed seed (the same31415asgridthinPointcloud).Containers ordered by memory address:
std::set<std::shared_ptr<…>>, that is, ordered by address. That order decided the distance heap's ties, the merge direction and the cluster ids. Clusters now carry a creation id, and the sets are ordered by it.ArrangementBaseiterated a hash map of face pairs. It now visits them in the order the arrangement's edges first meet them.Uninitialised extremes in
calc_segment: when a distance cluster holds only intersection lines, there is no other line to bound them.calc_segmentthen received an empty list and returned its uninitialisedpmin/pmax. A lone intersection line is now bounded by itself, andcalc_segmentstarts from the centroid.Snapper order: CGAL's edge iterator reports each edge from whichever of its two faces has the lower address. As a result, "the first short edge" and the order in which the snapped arrangement was rebuilt both depended on the heap. The snapper now:
The new CDT labelling already breaks label ties by source face id, so it needed no change.
Cropper: in
PointsInPolygonsCollector::do_post_process, the loop that merges the buffer ground points declaredsize_t poly_i;without initialising it.Thinning:
gridthinPointcloudskipped cells withi > size(), so a point mapping toi == size()read one past the count raster. Whether that point was kept, and therefore every later random draw, depended on that memory. Buffer ground points outside the footprint's raster hit this case.I found the causes by fingerprinting each stage's output (planes, alpha rings, lines, regularised lines, arrangement, snapper, input ground points), then comparing two diverging runs of the same building.
Verification
Same 7,393 buildings and inputs, comparing geometry and all
rf_*attributes exceptrf_t_run:main-j 16, two runs (5,364 buildings)Effect on the models compared with
main:mainitself flips between two models from run to run, depending on the heap layout. This PR picks one of them every time.The six changes are independent. I can split them into separate PRs if that is easier to review.
Noticed, not changed here
gridthinPointcloudandRasterisePointcloudcompute a cell index for points outside the footprint's bounding box withstatic_cast<size_t>(floor(...)). Negative values wrap. Points beyond the right edge land in the next row. So buffer ground points are either dropped by the thinning or counted against an unrelated cell. This PR only removes the out-of-bounds read, because fixing the mapping changes which ground points are kept.🤖 Generated with Claude Code
https://claude.ai/code/session_01LZBFfd55Us1zg7chCUDs5C