Repository navigation
ci(measure): mesh_bodies_vs_ifc and saved_meshes_vs_ifc mesh across the CPUs, share their meshes and prune surface distance exactly, output unchanged (#1237) - #1295
Merged
Conversation
…he CPUs and measure surface distance by exact pruning, output unchanged (#1237) mesh_bodies_vs_ifc opens each file once, meshes in a process pool with one create_shape per part instead of one after another, and prunes surface_distance with block, box and plane lower bounds, evaluating the survivors with the arithmetic the exhaustive search uses so the result is the same float. saved_meshes_vs_ifc meshes through the same pool in place of the geometry iterator, which took twice the CPU per element, and lists the bodies in the iterator's one-thread (ascending instance id) order, which the tables' ties follow.
… what they meshed, through a cache Measure deletes after the model (#1237) mesh_bodies_vs_ifc and saved_meshes_vs_ifc mesh the same bodies of the same two files, so Revit's side was meshed twice. With RVT_MESH_CACHE set, mesh_parts keeps what it meshed in npz shards keyed by the file's path, size and mtime, and reads what an earlier scorer kept; the script chains the two scorers as one task per model, sets it to the model's folder and removes it with the model's other bulky outputs. Fewer than 16 parts are meshed without a pool.
…ric tolerance of a very large triangle, and the mesher's workers, shards and cache key are firmer (#1237) The slack was 1e-9 of the extent, less than the 1e-9 tolerance of inside can move a point (up to 2e-9 of two edges), so a point 0.99e-9 outside a 1e7 ft triangle measured 0.003 where the exhaustive search says 0; it is 4e-9 now, with the argument in the docstring. A pool worker stops at its next part when its parent has died, a shard is named by process and time, the cache key holds every setting, a pool that breaks says so on stderr, a part IfcOpenShell cannot mesh keeps its reason in MESH_FAILURES (and in the cache), and the script logs the cache's size before deleting it.
Owner
Author
|
Automated Claude Code review (the maintainer's review agent; it compared both scorers' outputs across four runs and fuzzed the pruned distance against the exhaustive one): no blocking items. Fixed anyway in 9851ae6: the pruning slack now covers the barycentric tolerance of a very large triangle, and a pool worker stops when its parent dies, shards and the cache key cannot collide, a broken pool says so, and the cache's size is logged before it is deleted. |
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.
Summary
mesh_bodies_vs_ifcandsaved_meshes_vs_ifcwere 2,752 of the 5,548 s of scorer time in Measure run 41655009606. Their output is unchanged on every model (66 of 66 files byte-identical, with and without--saved-meshes); on the BIM4LCA office, as built, the pair takes 163.6 s where it took 576.4 s, one scorer at a time on one runner (default exports; 138.7 s against 444.8 s with--saved-meshes).Motivation
#1237. In a Measure run the office's
mesh_bodies_vs_ifctook 418 s alone and 940 s beside the other steps,saved_meshes_vs_ifc300 s and 397 s, on a runner that is CPU-bound.Changes
tools/re/mesh_bodies_vs_ifc.py: opens each IFC once.surface_distanceis pruned and exact. A point's nearest distance is the minimum over a set of triangles that holds every one that could be nearest, found with a block box, then a triangle box, then the triangle's plane and the side planes of its edges as lower bounds (slivers, whose computed plane is unreliable, are measured against every point). Every candidate is measured by the arithmetic the exhaustive search uses (one function now serves both), so the result is the same float, not an approximation; the argument is in the docstring. A triangle is left out only when a lower bound on it exceeds the nearest distance found plus 1e-6 ft and 4e-9 of the extent, which covers the 1e-9 barycentric tolerance ofinsideeven on a 1e7 ft triangle (the first version's 1e-9 did not: a point 0.99e-9 outside such a triangle measured 0.003 where the exhaustive search says 0). Jobs of 200,000 pairs or fewer, and any input that is not finite, still take the exhaustive search.tools/re/ifc_common.py:mesh_partsmeshes with onecreate_shapeper part in a forked process pool, falling back to serial if a worker dies, and, whenRVT_MESH_CACHEnames a directory, keeps what it meshed as npz shards (keyed by the file's path, size and mtime and by every setting) for the next scorer. Fewer than 16 parts are meshed without a pool.RVT_MESH_WORKERSsets the pool size. A worker stops at its next part if its parent has died, a shard is named by process and time, a pool that breaks prints one line on stderr and falls back to serial, and a part IfcOpenShell cannot mesh keeps its reason inMESH_FAILURES(also in the cache; nothing prints it, so no output changes).tools/re/saved_meshes_vs_ifc.py: meshes throughmesh_partsin place ofifcopenshell.geom.iterator, and lists bodies in ascending instance id, the order the iterator returns them in with one thread. The iterator's order with several threads varies from run to run, and it decides the order of the tables' tied rows (the office's 269 doors and 269 windows); a first version that listed them by type order changed one tie in a local run.tools/ci/measure-reference-models.sh: the two scorers run one after the other as one task per model (meshes), withRVT_MESH_CACHEset to the model's folder, so Revit's side is meshed once; the folder's size is logged and the folder removed with the model's other bulky outputs.Testing
Unit tests are banned; identity is Measure against Measure, outputs diffed file by file.
Identity,
-f ref=8add3443615ba576bdb81c822548243b5cd314dd -f base=none -f nordic=true -f r2025=true -f scorers="mesh_bodies_vs_ifc saved_meshes_vs_ifc", once without and once with-f ifc_flags=--saved-meshes, scorers from main (8add344) against scorers from this branch (9851ae6, main merged in).nordic=trueis four offices now (as built, permit, and the two terrain variants), 33 models in all:mesh_bodies_vs_ifc.txtandsaved_meshes_vs_ifc.txt--saved-meshesPeak memory with four offices (vmstat.log, 16 GiB runners, no swap in any run): used memory peaked 2.9 GiB above the start with default exports (main: 4.9) and 6.0 GiB with
--saved-meshes(main: 5.2); the lowest free memory was 9.5 and 6.6 GiB (main: 7.6 and 7.2). The Measure step took 6:01 and 6:33 against main's 8:17 and 8:24, on runners of different CPUs (main on an EPYC 9V45; the branch on a 7763 and a 9V74), so those are not a pair.The earlier identity runs on 3ff51de (main 41676807914 and 41676812191 against 41686047329 and 41686052305) were 62 of 62 identical as well. The speed measurements below were taken on 3ff51de; the later commits changed only the slack and the robustness items above.
Speed, the office as built, one scorer at a time on one runner (
MEASURE_JOBS=1,/usr/bin/time; a throwaway branch, fd34397, since deleted, added main's two scorers beside the new ones; runs 41685996778 and 41686000775 hold its cpu.log and outputs): the old and new outputs are identical there as well.mesh_bodies_vs_ifcsaved_meshes_vs_ifcsaved_meshes_vs_ifc--saved-meshes(41686000775)mesh_bodies_vs_ifcsaved_meshes_vs_ifcsaved_meshes_vs_ifcWith one pool worker the new scorers take 302.8 s (
mesh_bodies_vs_ifc) and 271.9 s (saved_meshes_vs_ifc) on the first runner: the pruning accounts for the 125 s thatmesh_bodies_vs_ifcloses, the pool for the rest of the wall time. The pool's user time is higher than serial (the 4 vCPUs are probably two cores' hyperthreads, and each worker parses the parts it meshes again), so it buys wall time on an idle runner and not CPU on a busy one; the shared meshes and the pruning are what lower the CPU (the pair's 1,001 CPU-s become 496 on the first runner).In Measure runs, the same pair of runs on one CPU type (AMD EPYC 7763,
--saved-meshes, both offices running beside each other, 3ff51de):mesh_bodies_vs_ifc830 and 842 s to 52 and 53 s,saved_meshes_vs_ifc442 and 439 s to 324 and 324 s, the Measure step 885 s to 421 s.Largest resident set of a single process (
/usr/bin/timereports the largest in the tree, and the pool runs several): 0.76 to 0.89 GB, against 1.18 to 1.89 GB for main's scorers, which were one process each.Not done, and limits
ifcopenshell.geom.iteratorandcreate_shapegive the same triangles bit for bit on the 538 Revit parts of the office (checked locally), and Measure's identical outputs say the same on the rest.saved_mesh_status_vs_ifc.pyandfitting_faces_vs_ifc.pyimportmeshesandsurface_distance; their signatures hold (meshesgained an optionalmodel).Breaking changes
None.