Finding-I from doc/reviewer/external-audit-2026-05-30.md.
Root cause: CGAL+Eigen at -O3 drives cc1plus peak RAM to ~700 MB per
Unity compilation unit (batch size 4) on ARM64. With the 1600 MB
container limit the 2nd or 3rd TU reliably triggered OOM-kill, so
test-cgal was gated off via `if: false` since 2026-05-26.
Fix: new CMake option CONFORMALLAB_LOW_MEMORY_BUILD=ON applies four
orthogonal memory-saving measures to conformallab_cgal_tests:
1. -O0 (no debug info): optimizer passes entirely skipped → cc1plus
peak drops from ~700 MB to ~150-200 MB per TU on ARM64.
Omitting -g avoids the additional object-file / linker RAM cost.
2. CONFORMALLAB_USE_PCH=OFF: saves the one-time ~200 MB PCH
compilation cost; each TU re-parses CGAL headers (fast at -O0).
3. UNITY_BUILD_BATCH_SIZE=1: one source file per cc1plus invocation,
removing the "4-file template-explosion" per-unit multiplier.
4. -Wl,--no-keep-memory (GNU ld): linker releases symbol tables after
each input file → ~15-25 % less linker RSS.
Verified locally with cmake -DCONFORMALLAB_LOW_MEMORY_BUILD=ON:
277/277 CGAL tests pass, 31 s runtime (vs 2 s at -O3 — expected;
tests run 15× slower without optimizer but all correct).
CI workflow changes (cpp-tests.yml):
- test-cgal re-enabled: `if: github.event_name == 'pull_request'`
- Configure step adds -DCONFORMALLAB_LOW_MEMORY_BUILD=ON
- Container memory: 1600m → 2000m (--memory-swap=3000m for 1 GB swap
headroom), using ~half of the Pi's 3-4 GB while leaving OS margin.
CLAUDE.md updated: new flag added to compile-time options table; CI
status row corrected from DISABLED to active.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
262 lines
12 KiB
CMake
262 lines
12 KiB
CMake
# tests/cgal/CMakeLists.txt
|
||
#
|
||
# CGAL-dependent test target. Only built when -DWITH_CGAL=ON.
|
||
# Requires Boost (find_package(Boost REQUIRED) is called in the root CMakeLists).
|
||
#
|
||
# Run with:
|
||
# cmake -S code -B build -DWITH_CGAL=ON
|
||
# cmake --build build --target conformallab_cgal_tests
|
||
# ctest --test-dir build -R cgal
|
||
|
||
add_executable(conformallab_cgal_tests
|
||
# ── Phase 3a: mesh infrastructure ──────────────────────────────────────
|
||
test_conformal_mesh.cpp
|
||
|
||
# ── Phase 3b: HyperIdealFunctional ─────────────────────────────────────
|
||
test_hyper_ideal_functional.cpp
|
||
|
||
# ── Phase 3c + 3e: SphericalFunctional + gauge-fix ────────────────────
|
||
test_spherical_functional.cpp
|
||
|
||
# ── Phase 3d: EuclideanCyclicFunctional ───────────────────────────────
|
||
test_euclidean_functional.cpp
|
||
|
||
# ── Phase 3f: Hessians (cotangent Laplacian, spherical + Euclidean) ───
|
||
test_euclidean_hessian.cpp
|
||
test_spherical_hessian.cpp
|
||
|
||
# ── Phase 9b: Hyper-ideal Hessian — block-FD vs full-FD validation ───
|
||
# Verifies the O(F·36) block-local Hessian agrees with the
|
||
# O(F·n) full-FD baseline. Java upstream has no Hessian at all
|
||
# (HyperIdealFunctional.hasHessian() returns false) — both
|
||
# variants are conformallab++ extensions beyond the port.
|
||
test_hyper_ideal_hessian.cpp
|
||
|
||
# ── Phase 4a: Newton solver ────────────────────────────────────────────
|
||
test_newton_solver.cpp
|
||
|
||
# ── Phase 4b: Mesh I/O (CGAL::IO) ─────────────────────────────────────
|
||
test_mesh_io.cpp
|
||
|
||
# ── Phase 4c: End-to-end pipeline + user examples ─────────────────────
|
||
test_pipeline.cpp
|
||
|
||
# ── Phase 5: Layout / embedding + serialization ────────────────────────
|
||
test_layout.cpp
|
||
|
||
# ── Phase 6: Gauss–Bonnet, cut graph, exact trilateration, normalisation
|
||
test_phase6.cpp
|
||
|
||
# ── Phase 7: Java-parity layout — MobiusMap, priority BFS, halfedge_uv,
|
||
# period matrix, fundamental domain, tiling
|
||
test_phase7.cpp
|
||
|
||
# ── Java parity: geometry utility tests ──────────────────────────────────
|
||
# Ported from CuttinUtilityTest, UnwrapUtilityTest,
|
||
# ConvergenceUtilityTests, HomologyTest. All tests active —
|
||
# the v0.7.0 genus-2 homology stub was implemented in Phase 7
|
||
# (HomologyGenerators.Genus2_FourCutEdges, brezel2.obj).
|
||
test_geometry_utils.cpp
|
||
|
||
# ── Scalability smoke tests ────────────────────────────────────────────────
|
||
# Newton convergence on large real-world meshes (cathead, brezel, brezel2).
|
||
# Assert correctness only (< 30 iterations, ||G|| < 1e-8).
|
||
# Wall-clock time is printed for documentation but NOT asserted,
|
||
# so the tests remain stable on slow CI hardware (Raspberry Pi ARM64).
|
||
test_scalability_smoke.cpp
|
||
|
||
# ── Phase 8 MVP: new CGAL-style public API ────────────────────────────────
|
||
# First client of Conformal_map_traits.h + Discrete_conformal_map.h.
|
||
# Acceptance probe before Phase 9a (Inversive-Distance) lands.
|
||
test_cgal_traits_mvp.cpp
|
||
|
||
# ── Phase 9a.1: CPEuclideanFunctional (BPS 2010 circle packing) ──────────
|
||
# Face-based circle-packing functional ported from
|
||
# CPEuclideanFunctional.java. Reference: Bobenko-Pinkall-Springborn 2010.
|
||
test_cp_euclidean_functional.cpp
|
||
|
||
# ── Phase 9a.2: InversiveDistance (Luo 2004 + Glickenstein 2011) ─────────
|
||
# Vertex-based inversive-distance circle-packing functional. No Java
|
||
# reference; implemented from the literature. Cross-validated against
|
||
# EuclideanCyclicFunctional at the natural initial geometry (u = 0).
|
||
test_inversive_distance_functional.cpp
|
||
|
||
# ── Phase 9a: Newton solvers for the two new circle-packing functionals ──
|
||
# Convergence tests for newton_cp_euclidean (analytic Hessian) and
|
||
# newton_inversive_distance (FD Hessian).
|
||
test_newton_phase9a.cpp
|
||
|
||
# ── Tier-3 Java cross-validation: Lawson square-tiled HyperIdeal ─────────
|
||
# Low-level half-edge genus-2 generator + golden-vector convergence.
|
||
test_lawson_hyperideal.cpp
|
||
|
||
# ── Pn projective-metric substrate (de.jreality.math.Pn port) ───────────
|
||
# Inner product / norm / distance / geodesic interpolation in
|
||
# Euclidean, Elliptic, Hyperbolic signatures.
|
||
test_pn_geometry.cpp
|
||
|
||
# ── Phase 8b-Lite: CGAL entry wrappers for the 4 non-Euclidean modes ─────
|
||
# Spherical, HyperIdeal, CircleP-Euclidean, Inversive-Distance via
|
||
# <CGAL/Discrete_*.h> public API + Conformal_layout.h wrapper.
|
||
test_cgal_phase8b_lite.cpp
|
||
)
|
||
|
||
target_include_directories(conformallab_cgal_tests SYSTEM PRIVATE
|
||
${CMAKE_SOURCE_DIR}/deps/eigen-3.4.0
|
||
${CMAKE_SOURCE_DIR}/deps/CGAL-6.1.1/include
|
||
${CMAKE_SOURCE_DIR}/deps/single_includes
|
||
${Boost_INCLUDE_DIRS}
|
||
)
|
||
|
||
target_include_directories(conformallab_cgal_tests PRIVATE
|
||
${CMAKE_SOURCE_DIR}/include
|
||
)
|
||
|
||
target_compile_definitions(conformallab_cgal_tests PRIVATE
|
||
CGAL_DISABLE_GMP
|
||
CGAL_DISABLE_MPFR
|
||
# Data directory — absolute path to code/data/ at build time.
|
||
# Used by tests that load real mesh files (cathead.obj, brezel2.obj, …).
|
||
CONFORMALLAB_DATA_DIR="${CMAKE_SOURCE_DIR}/data"
|
||
)
|
||
|
||
# Suppress warnings from CGAL/Boost headers
|
||
target_compile_options(conformallab_cgal_tests PRIVATE
|
||
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wno-unused-parameter>
|
||
)
|
||
|
||
# Fast test-build mode (lever #10): -O0 -g overrides the inherited
|
||
# Release-mode -O3 + -DNDEBUG. Applies only to this test target.
|
||
if(CONFORMALLAB_FAST_TEST_BUILD)
|
||
target_compile_options(conformallab_cgal_tests PRIVATE
|
||
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-O0 -g -UNDEBUG>
|
||
)
|
||
endif()
|
||
|
||
# ── Low-memory build mode (for RAM-constrained CI runners, e.g. Raspberry Pi) ──
|
||
#
|
||
# Problem: CGAL + Eigen at -O3 drives cc1plus peak RAM to ~600-800 MB per
|
||
# Unity compilation unit on ARM64 Linux. A 1600 MB container limit with a
|
||
# batch of 4 files per unit causes OOM-kill during the build.
|
||
#
|
||
# This flag enables three orthogonal memory-saving measures:
|
||
#
|
||
# 1. -O0 (no debug info): drops cc1plus backend RAM by ~60-70 %.
|
||
# Optimizer passes (inlining, register allocation, constant propagation)
|
||
# dominate the backend. At -O0 they are entirely skipped → peak per
|
||
# TU falls from ~700 MB to ~150-200 MB on ARM64.
|
||
# We omit -g deliberately: debug info adds ~30-40 % object-file size
|
||
# and increases linker RSS. CI needs "does it compile + do tests pass",
|
||
# not debuggability.
|
||
#
|
||
# 2. PCH OFF: the precompiled header itself consumes ~200 MB to compile
|
||
# and is re-read by every TU. Disabling it saves the one-time PCH
|
||
# compilation cost; each TU re-parses CGAL headers, but at -O0 this
|
||
# is fast.
|
||
#
|
||
# 3. UNITY_BUILD_BATCH_SIZE=1: one source file per unity unit. Removes
|
||
# the "4 files × CGAL parse cost" multiplier; each cc1plus process
|
||
# only sees one file worth of templates.
|
||
#
|
||
# 4. Linker memory flag (GCC/Clang only): --no-keep-memory tells GNU ld
|
||
# to release symbol table memory after each input file instead of
|
||
# keeping it for cross-reference. Reduces linker RSS by 15-25 % at
|
||
# the cost of slightly longer link time.
|
||
#
|
||
# Activate with:
|
||
# cmake -S code -B build -DWITH_CGAL_TESTS=ON \
|
||
# -DCONFORMALLAB_LOW_MEMORY_BUILD=ON
|
||
# Expected peak per cc1plus: ~150-200 MB → fits in 1800-2000 MB container.
|
||
# Test runtime is 2-4× slower than Release (CGAL traversals unoptimized),
|
||
# but correctness is unaffected.
|
||
option(CONFORMALLAB_LOW_MEMORY_BUILD
|
||
"Build CGAL tests with -O0, no PCH, UNITY_BATCH_SIZE=1 for RAM-constrained CI." OFF)
|
||
|
||
if(CONFORMALLAB_LOW_MEMORY_BUILD)
|
||
message(STATUS "CONFORMALLAB_LOW_MEMORY_BUILD active: -O0, no PCH, unity batch 1.")
|
||
|
||
# 1. -O0, no debug info
|
||
target_compile_options(conformallab_cgal_tests PRIVATE
|
||
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-O0 -UNDEBUG>
|
||
)
|
||
|
||
# 2. PCH off — force-override the option so the block below is skipped
|
||
set(CONFORMALLAB_USE_PCH OFF CACHE BOOL "" FORCE)
|
||
|
||
# 3. Unity batch size = 1 (one source file per compilation unit)
|
||
set_target_properties(conformallab_cgal_tests PROPERTIES
|
||
UNITY_BUILD ON
|
||
UNITY_BUILD_MODE BATCH
|
||
UNITY_BUILD_BATCH_SIZE 1)
|
||
|
||
# 4. Linker memory hint (GNU ld / lld)
|
||
target_link_options(conformallab_cgal_tests PRIVATE
|
||
$<$<CXX_COMPILER_ID:GNU>:-Wl,--no-keep-memory>)
|
||
endif()
|
||
|
||
target_link_libraries(conformallab_cgal_tests PRIVATE GTest::gtest_main)
|
||
|
||
# ── Compile-time speed-up: precompiled headers ───────────────────────────────
|
||
#
|
||
# The CGAL+Eigen template soup dominates every TU in this target:
|
||
# measured at 5.9 s per minimal "include <CGAL/Discrete_conformal_map.h>"
|
||
# TU on Apple M1. A shared PCH absorbs that cost once, slashing the
|
||
# total wall-clock from ~78 s (j8) to ~25 s (3×).
|
||
#
|
||
# Opt-out with -DCONFORMALLAB_USE_PCH=OFF if the PCH itself misbehaves
|
||
# (e.g. older toolchains that don't share PCH across translation units
|
||
# reliably) — falls back to the historical "every TU re-parses CGAL"
|
||
# build mode.
|
||
option(CONFORMALLAB_USE_PCH
|
||
"Enable precompiled headers for the CGAL test target." ON)
|
||
|
||
if(CONFORMALLAB_USE_PCH)
|
||
# Per-target Unity Build property takes precedence over the global
|
||
# CMAKE_UNITY_BUILD; honour CONFORMALLAB_DEV_BUILD's preference here
|
||
# so `-DCONFORMALLAB_DEV_BUILD=ON` truly turns Unity Build off for
|
||
# incremental-rebuild workflows.
|
||
if(NOT CONFORMALLAB_DEV_BUILD)
|
||
set_target_properties(conformallab_cgal_tests PROPERTIES
|
||
# Unity-builds amortise the per-TU CGAL+Eigen header cost
|
||
# across several tests in the same compile. Batch size 4
|
||
# keeps gtest's TEST(...) macros + per-file `using
|
||
# namespace …` from colliding while still cutting parser
|
||
# cost ~4×.
|
||
UNITY_BUILD ON
|
||
UNITY_BUILD_MODE BATCH
|
||
UNITY_BUILD_BATCH_SIZE 4)
|
||
endif()
|
||
|
||
target_precompile_headers(conformallab_cgal_tests PRIVATE
|
||
# CGAL headers that every test transitively includes.
|
||
<CGAL/Surface_mesh.h>
|
||
<CGAL/Simple_cartesian.h>
|
||
<CGAL/Kernel_traits.h>
|
||
<CGAL/boost/graph/iterator.h>
|
||
<CGAL/Polygon_mesh_processing/triangulate_faces.h>
|
||
|
||
# Eigen blocks that drive the slowest template instantiations
|
||
# (SelfAdjointEigenSolver<Matrix<2,2>>, ColPivHouseholderQR<
|
||
# Matrix<complex,3,3>>, sparse Cholesky + QR fallback).
|
||
<Eigen/Dense>
|
||
<Eigen/Sparse>
|
||
<Eigen/SparseCholesky>
|
||
<Eigen/SparseQR>
|
||
|
||
# GoogleTest itself; every test includes it.
|
||
<gtest/gtest.h>
|
||
|
||
# std headers that appear in every test.
|
||
<vector>
|
||
<string>
|
||
<cmath>
|
||
<complex>
|
||
)
|
||
endif()
|
||
|
||
include(GoogleTest)
|
||
gtest_discover_tests(conformallab_cgal_tests
|
||
TEST_PREFIX "cgal."
|
||
DISCOVERY_TIMEOUT 60
|
||
)
|