Adds a comprehensive 'External audit documents' section to the reviewer index:
- groups all 12 audits (correctness, API/perf/packaging, robustness)
- surfaces G0 (porting rights) as the cross-cutting release blocker
- records the decisions taken: coverage gate 80/70/90, API naming A1-A5
(A1-A3 implemented in PR #36, A4-A5 deferred pending G0/G1)
- points a new session at the low-risk quick-win findings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Checks code/docs against the published mathematics (vs java-port-audit which
checks code vs Java):
- M1: Kolpakov-Mednykh volume formula used in code but missing from references.md
- M2: BPS circle-packing paper cited as '2010' in code vs '2015' in references.md
- M3: post-2023/preprint citations back unimplemented phases — re-verify + clarify
scope before submission
- M4: no canonical citation-key convention
- M5: large derivations numerically validated but prose unreviewed — expert sign-off
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the dimensions the prior audits did not cover:
- numerical-stability: tolerance hierarchy (FD-step vs Newton tol), discontinuous
clamps, cancellation in cotangent/area, magic constants.
- input-validation: malformed JSON/XML deserialization, NaN/Inf vertex coords on
load, schema-drift handling.
- thread-safety: no mutable static state (good); undocumented same-mesh concurrency
contract; Eigen threading.
- dependency-license: existing THIRD-PARTY-LICENSES matrix is sound but predicated
on the MIT premise that G0 undermines; test meshes have no provenance/license.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finding-U6 from doc/reviewer/usability-audit-2026-05-31.md.
The Finding-D fix (external-audit-2026-05-30) added a clean one-call
gauge-vertex overload:
assign_euclidean_vertex_dof_indices(mesh, maps, gauge_vertex)
But all user-facing code still showed the old verbose manual loop:
auto vit = mesh.vertices().begin();
maps.v_idx[*vit++] = -1;
int idx = 0;
for (; vit != mesh.vertices().end(); ++vit) maps.v_idx[*vit] = idx++;
Replaced in three places:
README.md 'Minimal usage' code block
example_euclidean.cpp Step 3
example_layout.cpp pin_first() helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
U4 (README.md:17) — status line: v0.9.0 → v0.10.0, test count 0 → 277
U5 (Discrete_conformal_map.h:6-16) — \file Doxygen block rewritten:
was: 'provides a single function … Spherical/hyperbolic scheduled for Phase 8b.2'
now: lists all three discrete_conformal_map_* functions already present,
plus pointers to the circle-packing companion headers
U7 (getting-started.md) — new 'Mode 4 — Low-memory build' section added
after Mode 3; shows CONFORMALLAB_LOW_MEMORY_BUILD=ON with -j1 and explains
the -O0 / no PCH / batch-1 tradeoffs for Raspberry Pi / ≤ 4 GB runners
U8 (README.md compile-time modes) — LOW_MEMORY_BUILD entry added to the
compile-time workflow code block with a one-line explanation and the
mandatory -j1 note
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Finding-U3 from doc/reviewer/usability-audit-2026-05-31.md.
Three concrete errors corrected:
1. check_gauss_bonnet() row (was: implies all map types work)
→ now: 'EuclideanMaps/SphericalMaps only' — HyperIdealMaps overload
is = delete (compile error since external-audit Finding-B fix)
2. enforce_gauss_bonnet() row (same issue)
→ same restriction note added
3. newton_hyper_ideal() preconditions (was: 'lambda0 initialised')
→ WRONG: HyperIdeal computes lengths from b_v, a_e via zeta functions
internally; lambda0 plays no role. Correct precondition:
'no lambda0 needed · no GB pre-check'
→ Added explanation: strictly convex energy (Springborn 2020 Thm 1.3)
→ Newton converges for any targets without a pre-check
Gauss-Bonnet prose section also updated:
- Examples now show EuclideanMaps/SphericalMaps explicitly
- HyperIdeal compile-error note added with the correct hyperbolic
identity Σ(2π-Θ_v) - Area = 2π·χ and why no pre-check is needed
- Open-mesh note added (GB identity does not hold for boundary meshes)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
U10: CLI has no parameter reference in README or getting-started.md.
Documents all 7 current flags with defaults in the audit file so
a future session can add the table to getting-started.md.
U11: Three useful CLI gaps documented as a roadmap item:
- --tol / --max-iter: Newton solver tuning, currently hardcoded
- -g cp_euclidean / -g inversive_distance: Phase-9a models not
reachable from CLI despite being fully implemented in the library
Each item has an effort estimate (~30 min / ~2 h) and concrete
acceptance criteria so a future session can pick it up cold.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Finding-U1 and Finding-U2 from doc/reviewer/usability-audit-2026-05-31.md.
The existing examples (example_euclidean, example_layout, example_hyper_ideal)
all used the "natural theta" pattern which makes x*=0 trivially the
equilibrium — u_v ≈ 0 everywhere, no deformation. A new user following
these examples saw solver output but not conformal geometry.
New: example_flatten.cpp
- PRIMARY USE CASE: conformally flatten a mesh to the plane
- Sets Θ_v = 2π for all interior vertices (flat target)
- Pins boundary vertices (no Gauss-Bonnet check for open meshes)
- Demonstrates non-trivial u_v (cathead.obj: range ≈ 2.96, 5 Newton iters)
- Documents the difference from "natural theta" explicitly
New: example_cgal_api.cpp
- Demonstrates CGAL::discrete_conformal_map_euclidean (Discrete_conformal_map.h)
- First runnable CGAL public API example; contrast with internal API
- Documents the "natural theta" default behaviour and explains why u_v=0
- Explains when to use CGAL API vs internal API
Both examples registered in code/examples/CMakeLists.txt and compile
cleanly with -DWITH_CGAL=ON.
Updated:
- example_euclidean.cpp: prominent "TESTING CONVENTION" warning
- example_layout.cpp: same warning on set_natural_theta helper
- doc/getting-started.md: example_flatten is now the recommended
"start here" example; note on natural-theta behaviour added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
External documentation and usability review of v0.10.0.
9 findings covering documentation correctness, API usability,
and new-user experience.
Critical:
U1 — All examples show trivial identity map (natural theta),
not real conformal flattening — new users see no-op output
U2 — CGAL public API has zero runnable examples
Medium:
U3 — contracts.md incorrect: check_gauss_bonnet row missing
HyperIdeal restriction (deleted overload after Finding-B)
U4 — README still shows v0.9.0 (current: v0.10.0, 277 tests)
U5 — Discrete_conformal_map.h header comment describes Phase-8a
state; all 5 entry functions already implemented
U6 — New gauge-vertex overload (Finding-D) not reflected in
README or examples — old verbose loop still shown
Minor:
U7 — CONFORMALLAB_LOW_MEMORY_BUILD absent from getting-started.md
U8 — CONFORMALLAB_LOW_MEMORY_BUILD absent from README table
U9 — Layout2D.uv index semantics undocumented (v.idx() contract)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
MINOR-1 (spherical_functional.hpp:426)
Wrong comment said "second derivative < 0 for a convex functional".
The spherical energy is *concave* (NSD Hessian); the monotone-f argument
applies to both convex and concave functionals equally. Comment rewritten
to explain the actual physics: increasing scale increases all angles and
thus reduces Σ G_v.
MINOR-2 (spherical_functional.hpp:494-495)
Forward finite difference O(ε) → central finite difference O(ε²):
old: dft = (sum_Gv(t + fd_eps) - ft) / fd_eps
new: dft = (sum_Gv(t + fd_eps) - sum_Gv(t - fd_eps)) / (2*fd_eps)
Same cost when the extra sum_Gv(t - fd_eps) replaces the cached ft.
MINOR-3 (euclidean_functional.hpp, spherical_functional.hpp,
inversive_distance_functional.hpp)
New header gauss_legendre.hpp centralises the 10-point Gauss-Legendre
nodes and weights (gl10_nodes() / gl10_weights()). The three energy
functions now use the shared accessors instead of duplicated local
static arrays.
MINOR-4 (euclidean_functional.hpp, spherical_functional.hpp,
hyper_ideal_functional.hpp, inversive_distance_functional.hpp)
halfedge_to_index() centralised in conformal_mesh.hpp. All four local
aliases (eucl_hidx, spher_hidx, hidx, id_detail::hidx) now delegate to
it as one-line wrappers; the aliases are kept for now to avoid a larger
call-site churn, clearly documented as thin wrappers.
MINOR-5 (clausen.hpp:33-38)
Added a comment above inits() explaining the intentional off-by-one
return value and how it interacts with csevl() — matching the Java
Clausen.inits() / csevl() contract.
277/277 CGAL + 26/26 pure-math tests pass, 0 failed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Finding-E from doc/reviewer/external-audit-2026-05-30.md.
Scan also uncovered the same issue in inversive_distance_functional.hpp.
Three functions used absolute error `|analytic - fd| > tol` while the
rest of the library (euclidean_functional, spherical_functional,
euclidean_hessian, hyper_ideal_functional) all use relative error
`|analytic - fd| / max(1, |analytic|) > tol`.
Absolute error is too strict for large gradients (false failures) and
too lenient for small gradients.
Fixed:
cp_euclidean_functional.hpp gradient_check_cp_euclidean()
cp_euclidean_functional.hpp hessian_check_cp_euclidean()
inversive_distance_functional.hpp gradient_check_inversive_distance()
All three now use the relative criterion and accumulate all failures
before returning (ok=false instead of early return on first mismatch).
Default tol updated from 1e-6/1e-5 to 1e-4, matching the Java
FunctionalTest convention used by all other checks in the library.
Error message updated to print rel-err instead of raw diff.
266/266 CGAL tests pass, 0 failed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Finding-D from doc/reviewer/external-audit-2026-05-30.md.
All three DOF-assignment functions iterated over every vertex
unconditionally, overwriting any v_idx set before the call. The
documentation said "pin before OR after" — the "before" option was
silently wrong (the pin would be overwritten). For the
inversive-distance variant the doc explicitly said the pre-call pin
would make the function "a no-op for that vertex", which was false.
Changes (three headers):
- euclidean_functional.hpp assign_euclidean_vertex_dof_indices()
- spherical_functional.hpp assign_vertex_dof_indices()
- inversive_distance_functional.hpp assign_inversive_distance_vertex_dof_indices()
For each:
1. Single-arg overload: doc corrected to "pin AFTER, not before;
pre-call pins are overwritten"
2. New two-arg overload accepting a Vertex_index gauge: pins the
requested vertex (v_idx=-1) in a single pass, preventing the
user error entirely
Three new GTests in test_euclidean_functional.cpp:
SingleArg_PinBeforeHasNoEffect — documents the old pitfall
TwoArg_GaugeIsPinnedOthersAreSequential — verifies the new overload
TwoArg_NewtonConvergesWithGaugeOverload — end-to-end correctness
266/266 CGAL tests pass, 0 failed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Finding-C from doc/reviewer/external-audit-2026-05-30.md.
The box comment at the top and the function-level comment above
euclidean_cot_weights() both stated:
cot_k = (t_adj1·l123 − t_adj2·t_opp) / denom2 ← WRONG
The correct formula (verified numerically on a 3-4-5 right triangle,
expected cot1=4/3, cot2=3/4, cot3=0) is:
cot_k = (t_opp · l123 − t_a · t_b) / denom2
where t_opp is the t-value of the edge OPPOSITE vertex k, and t_a/t_b
are the t-values of the two edges ADJACENT to vertex k.
The implementation in euclidean_cot_weights() was already correct;
only the documentation was wrong.
Changes (documentation only, zero code changes):
- Box comment: rewritten with correct formula and explicit per-vertex
assignment (cot1: t_opp=t23, t_a=t12, t_b=t31; etc.)
- Box comment: added missing ½ factor to Hessian contribution lines
- Function-level comment: corrected to (t_opp·l123 − t_a·t_b)/(8·Area)
with a pointer to the box comment for the full assignment
- Inline return comment in euclidean_cot_weights(): now shows the
mapping (cot1: t_opp=t23, t_a=t12, t_b=t31) directly at the formula
263/263 CGAL tests pass, 0 failed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Finding-B from doc/reviewer/external-audit-2026-05-30.md.
The Euclidean Gauss–Bonnet identity Σ(2π−Θ_v) = 2π·χ is ONLY valid for
flat/Euclidean and spherical metrics. For hyperbolic metrics (HyperIdeal)
the correct identity is Σ(2π−Θ_v) − Area(M) = 2π·χ. The previously
provided gauss_bonnet_sum(HyperIdealMaps) overload would silently pass the
wrong LHS to check_gauss_bonnet, which would always throw "deficit = ±Area"
for valid hyperbolic targets.
Fix:
- gauss_bonnet_sum(mesh, HyperIdealMaps) → = delete + explanation comment
- enforce_gauss_bonnet(mesh, HyperIdealMaps&) → = delete + explanation comment
- Header box comment rewritten with the correct hyperbolic Gauss–Bonnet
identity and a clear "HyperIdeal: NOT SUPPORTED" section
New test in test_phase6.cpp:
- HyperIdeal_EuclideanSumDiscrepancy_DocumentsWhyCheckIsDeleted
verifies numerically that the Euclidean sum = 0 but 2π·χ = 4π for a
regular tetrahedron, documenting the −4π discrepancy that motivated
the deletion
- Three compile-time static_asserts (SFINAE) confirm the overload is not
invocable with HyperIdealMaps but remains so with Euclidean/SphericalMaps
263/263 CGAL tests pass, 0 failed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Follow-up to the Finding-A fix (face_energy guard). The throw is the
correct safe behaviour for now; the mathematically complete solution
requires formulas for semi-ideal and fully-ideal tetrahedra that are
absent from the Java reference.
Adds a structured research entry to research-track.md (Phase 9b+):
- primary references: Kolpakov-Mednykh arXiv:math/0603097, Milnor 1982,
Vinberg 1985
- acceptance criteria: two new volume functions + gradient-check tests +
limiting-behaviour continuity witness
- explicit note that the throw in face_energy() must not be removed
without implementing and testing the replacement formulas
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Finding-A from doc/reviewer/external-audit-2026-05-30.md.
Root cause: both the Java reference (HyperIdealFunctional.java:222-231)
and the C++ port silently applied the one-ideal-vertex volume formula
to the first ideal vertex found in a face, ignoring any additional ideal
vertices. For two or three ideal vertices this produces a wrong energy
value with no diagnostic.
Fix: add an ideal_count guard at the top of face_energy() that throws
std::logic_error for ideal_count >= 2. The one-ideal (Kolpakov-Mednykh)
and zero-ideal (Meyerhoff/Ushijima) paths are unchanged and correct.
Three new GTests cover the three guard cases:
MultiIdealGuard_TwoIdealVertices_Throws (two ideal → throw)
MultiIdealGuard_AllThreeIdealVertices_Throws (all ideal → throw)
MultiIdealGuard_ExactlyOneIdeal_DoesNotThrow (one ideal → no throw)
262/262 CGAL tests pass, 0 failed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full external code review of v0.10.0 against all math-critical headers
(direct source read, not automated scan). Documents 5 open bugs/API errors,
3 test gaps inherited from java-port-audit.md, 1 architectural CI risk,
and 5 minor findings. Each finding is self-contained with file+line
references, a concrete fix proposal, and acceptance criteria so a new
session can pick up any single finding without prior context.
Key findings:
- FINDING-A (critical): face_energy() wrong for mixed ideal/hyper-ideal configs
- FINDING-B (medium): Gauss–Bonnet API conceptually wrong for HyperIdeal
- FINDING-C (medium): cotangent formula in euclidean_hessian.hpp header is wrong
- FINDING-D (medium): DOF-assignment doc falsely claims "pin before" works
- FINDING-E (medium): cp_euclidean gradient_check uses absolute not relative error
- FINDING-F/G/H: three open test gaps from java-port-audit.md
- FINDING-I (arch): 246/272 CGAL tests not gated in CI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Upgrades the cyclic Euclidean Hessian from block-FD to closed-form, satisfying
the novelty-statement §3.2 "analytic Hessians, not finite difference" claim for
the Euclidean path.
- euclidean_hessian.hpp: `euclidean_hessian_analytic` — closed-form cyclic
Hessian from the law-of-cosines angle derivatives
∂α_i/∂s_i = ℓ_i²/4A, ∂α_i/∂s_j = ½cot α_i − ℓ_j²/4A (Σ_j = 0),
chained to (u, λ_e) and sign-mapped to the gradient outputs (−α vertex,
+α_opp edge). Reuses euclidean_cot_weights. Block-FD kept as cross-check.
- newton_solver.hpp: newton_euclidean cyclic path now uses the analytic Hessian.
- tests: CyclicHessian_Analytic_MatchesBlockFD_Tetrahedron — analytic == block-FD
(1e-6), == gradient FD (1e-5), symmetric (1e-9). Existing cyclic convergence
oracle still GREEN with the analytic Hessian routed in.
Tier-2 (Wente) finding: wente_torus02.obj is a QUAD mesh (1240 quads) and the
Java golden comes from cyclic (quad-net) uniformization; the C++ period-matrix
pipeline is triangle-based, so a faithful bit-vs-Java τ comparison needs a
quad/cyclic pipeline (Phase 9f). Deferred and documented; golden τ = ½+i√3/2.
244/244 cgal tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build-verified attempt to port the Java EuclideanCyclicConvergenceTest
(cathead, "circular hole edge" φ = π−0.1).
- ✅ GREEN `CyclicCircularEdge_PhiEntersGradient_CatHead`: solver-free
cross-validation that the circular-edge φ target enters the cyclic edge
gradient exactly (ΔG_e = −Δφ_e at 1e-12; no other component moves).
- ⏸️ DISABLED `CyclicCircularEdge_CatHead_JavaXVal`: the full Java convergence
assertion (α_opp+α_opp = π−0.1), kept with golden semantics. Auto-activates
once the edge-DOF Hessian lands.
Build-verification finding: `newton_euclidean` -> `euclidean_hessian` throws
"edge DOFs are not supported" — the cyclic full solve is blocked by a missing
edge-DOF Euclidean Hessian (gradient supports edge DOFs, analytic Hessian does
not). Spherical convergence (Tier-1 #2) is already covered by test_newton_solver.
Documented in doc/reviewer/java-ignore-crossvalidation.md.
All 13 EuclideanFunctional tests pass (1 disabled).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Classifies the 22 Java @Ignore'd test files by root cause (ARM64 PETSc
native lib vs logic/known-issue) and by cross-validation value for the
C++ port.
- Notes what PR #27 already locks (math cores + functional-eval oracles)
to avoid duplication.
- Priority ranking: HIGH (HyperIdeal Lawson convergence golden vector —
records the exact u*), MEDIUM (genus-1 Wente uniformization, branch-point
variant), LATER (genus-2/hyperelliptic/Mobius/quasi-isothermic/Schottky),
NON-ORACLE (Java gaps: testHessian, testDoLayout; ARM64-flaky known issues).
- Documents why the Lawson mesh needs a low-level half-edge generator
(4 vertices / 12 edges => multi-edges; CGAL add_face cannot build it).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Infrastructure table: add Matrix (→ Eigen inline, done) and P2 rows.
P2 marked "port inline with Phase 9c" — almost exclusively consumed by
FundamentalPolygon/CanonicalForm/SurfaceCurve (all Phase 9c), so building
p2_geometry.hpp as a standalone ahead of 9c would be dead weight.
- Phase 9c rows in the utility table: ⚠️ annotation listing the exact P2.*
methods needed (makeDirectIsometryFromFrames, projectP3ToP2,
imbedMatrixP2InP3) plus the P2Big/cpp_dec_float_50 high-precision
prerequisite for the group-relation product ∏gᵢ = Id.
Ensures a future Phase 9c session starts with full context and does not
discover the jReality/precision dependencies mid-port.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ports the small but pervasive de.jreality.math.Pn surface that the Java
algorithmic core uses across every not-yet-ported geometric phase:
HyperbolicLayout, SphericalLayout, FundamentalPolygon (9c), KoebePolyhedron
(10c'), quasi-isothermic (10e), hyperelliptic theta, CircleDomain (11c).
Porting it once unblocks all downstream consumers instead of re-deriving
the metric ad hoc per phase.
pn_geometry.hpp — header-only, Eigen, ~120 LOC:
PnMetric { EUCLIDEAN=0, ELLIPTIC=+1, HYPERBOLIC=-1 } matching jReality.
pn_inner_product — bilinear form per signature.
pn_norm / pn_dehomogenize / pn_set_to_length / pn_normalize.
pn_distance_between — Euclidean (spatial), elliptic (acos), hyperbolic (acosh).
pn_linear_interpolation — affine (E) and slerp (S/H constant-speed geodesic).
HYPERBOLIC uses the timelike-positive ("upper-sheet") convention, identical to
the already-verified projective_math.hpp::hyperbolicDistance; pn_distance_between
is regression-anchored against it in the tests.
test_pn_geometry.cpp — 6 tests, all GREEN:
InnerProductSignatures, EuclideanDistance, EllipticDistanceIsAngle,
HyperbolicDistanceClosedFormAndAnchor (+ projective_math.hpp anchor),
NormAndScaling, LinearInterpolationGeodesic.
doc/roadmap/java-parity.md — new "Infrastructure / support layers" section:
de.jreality.math.Pn → pn_geometry.hpp (partial, 6 fns covering core usage)
de.jreality.math.Rn → Eigen (no separate port needed)
MatrixBuilder → not yet (only needed for hyperelliptic theta)
conformallab XML types → not planned (GUI persistence, not algorithmic)
246/246 cgal tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add bit-for-bit (1e-12) golden-value oracle tests pinning the C++ pure-math
and functional cores against the compiled upstream Java library (openjdk 17):
- HyperIdealGoldenJava: Clausen/Л/ImLi2, ζ13/14/15/ζ, both tetrahedron-volume
formulas (real de.varylab…Clausen / HyperIdealUtility).
- EuclideanGoldenJava / SphericalGoldenJava: angle formulas + β relations + Л
energy terms, plus FULL-MESH oracles driving the real EuclideanCyclicFunctional
/ SphericalFunctional on a shared tetrahedron — per-vertex gradient (Θ−Σα) and
ΔE = E(x)−E(0) (C++ Gauss-Legendre path integral vs Java closed form).
- SphericalGoldenJava.FullMeshEdgeDofGradient: edge-DOF gradient (vertex + edge
components, α_opp⁺+α_opp⁻−θ_e) vs raw conformalEnergyAndGradient — locks
Finding 3 at the solution level (audit items 4 & 5).
- PeriodMatrix.NormalizeModulus_GoldenJava: τ-reduction fold convention vs the
real DiscreteEllipticUtility.normalizeModulus (audit items 7 & 8).
Subtlety documented: the spherical oracles call Java's raw
conformalEnergyAndGradient, not evaluate() (which pre-runs a Brent gauge
maximization that C++ factors into the Newton solver's spherical_gauge_shift).
Also:
- P1-2 (layout.hpp): Euclidean holonomy now uses a per-cut-edge rigid-motion fit
g(z)=a·z+b, exposing residual_rotation = |arg(a)| as a diagnostic; non-
regressive (flat case a=1 reduces to the old midpoint formula).
- P1-3 (period_matrix.hpp): is_in_fundamental_domain fixed to the correct
half-open SL(2,ℤ) domain (−½ ≤ Re < ½). Updated the now-exposed
ComputePeriodMatrix_ReducedTau_InFD to assert the normalizeModulus domain
(closed +½ edge) instead.
Test counts (single source of truth = doc/api/tests.md): 272/272 pass, 0
skipped (26 non-CGAL + 246 CGAL).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Java uniformization classes (FundamentalPolygonUtility, CanonicalFormUtility)
rely on the *Big arbitrary-precision geometry (MathContext(50)) because products
of hyperbolic isometry generators grow exponentially and double fails to verify
the group relation ∏gᵢ = Id. Record this planning-relevant prerequisite and
resolve the contradiction in java-parity.md, which previously listed *Big as
permanently out of scope.
- CLAUDE.md: † note on the Phase-9 not-yet-ported table
- java-parity.md: *Big exception (localized high-precision substrate for 9c/10)
- phases.md: precision prerequisite as 9c sub-task + effort estimate
- design-decisions.md: new "Scalar type: double, with one localized exception"
Core flattening (Newton/energy/Eigen solver) stays double; the substrate
(cpp_dec_float_50 / mpreal) is localized to the uniformization module only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cross-referenced all 232 Java classes against the roadmap. New plan
entries for genuinely-useful, previously-unplanned items:
- 9g.1 conformal quality measures (Isothermicity, ConformalEquivalence,
FlippedTriangles, LengthCrossRatio + ConvergenceUtility metrics)
- 9g.2 optional period-matrix convergence experiment (method only;
Java harness depends on Mathematica/JLink + jReality, not ported)
- DEC operator layer (heds/dec/) noted as a 10a prerequisite
- HomotopyUtility -> 10a (explicit generator cycles; not covered by
cut_graph.hpp, which yields only the 2g cut edges)
- SurfaceCurveUtility -> 9c; MercatorTextureProjection -> 9d.3 companion
Documented as deliberately out-of-scope: SpanningTreeUtility (subsumed
by cut_graph.hpp), convergence/* harness, datasource/* (jReality viewer
decoration), *Big arbitrary-precision geometry, jReality GUI, and the
MTJ/Tao/PETSc solver bindings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Refresh CLAUDE.md for v0.10.0 (3 CI jobs incl. disabled test-cgal,
compile-time option matrix, reviewer/pages docs, agentic + token-hygiene
workflow patterns) and condense the historical Phase-8/audit logs to
pointers.
Add the documentation-pass process so the single-source-of-truth rules
stay enforced:
* scripts/quality/check-doc-freshness.sh — string-only drift gate
(version/date across CITATION/CHANGELOG/CLAUDE, doc-map count), <1 s,
registered in run-all.sh + quality README.
* doc/release-policy.md — "Documentation passes" subsection (triggers +
docs:sync rule); fix stale Phase-milestone mapping (v0.10.0 was
reviewer-ready, 9c→v0.11.0) and the test-cgal CI mention.
Add shared Claude config (un-ignore the two files only):
* .claude/settings.json — permission allowlist for safe repo commands.
* .claude/token-hygiene.md — Tier-3 cache-discipline user guide that
CLAUDE.md instructs Claude to remind the user about.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two bugs in the reviewer hub HTML:
1. The earlier sed-rewrite that lifted the hub from
`preview/reviewer-snapshot-v6` into `branch/main` accidentally
produced `branch/branch/main` — sed pattern matched the
`preview/reviewer-snapshot-v6` slug but the surrounding URL
already had `/src/branch/` prefix. All 25 in-hub links to the
repo were broken with 404 on codeberg.org.
Fix: `s|/src/branch/branch/main/|/src/branch/main/|g`.
2. The version pill in the badge strip still read v0.9.0 (stale
from the original v0.9.0-era hub template). Updated to v0.10.0.
The fix is in this in-repo source-of-truth file so any future manual
republication of the pages branch picks up the corrected hub. The
just-pushed pages branch already has the fix applied; codeberg's
`tmoussa.codeberg.page` frontend cache (max-age=600 s) will refresh
over the next 10 minutes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three small cleanups surfaced by running the full gate sweep on the
merged main:
1. CGAL conventions (CGAL-2 \\file briefs)
doc-only headers `Conformal_map/doxygen_groups.h` and
`Conformal_map/doxygen_namespaces.h` were missing the `\\file`
brief required by the CGAL conventions check. Added both.
2. codespell — three new triggers
`code/tests/cgal/CMakeLists.txt` uses "honour", `doc/reviewer/hub.html`
uses `<thead>` (HTML tag, false-positive for "thread"), and
`doc/roadmap/research-track.md` uses "optimiser". All three are
British-English / HTML usage; added to `.codespellrc` ignore list.
3. Doxygen warning in `doc/architecture/compile-time.md:250`
A trailing backtick-quoted CMake flag at end-of-file confused
Doxygen's markdown parser into starting a never-closing verbatim
block. Rewrote the line to put the prose first and the backtick
in the middle, not at end-of-file.
All gates green again on the merged main:
✅ 259/259 tests pass
✅ test-count consistency
✅ markdown links 166/166 resolve
✅ CGAL conventions 0/8 violations
✅ license-headers 68/68 carry MIT SPDX
✅ codespell 0 typos
✅ shellcheck 0 findings (18 scripts)
✅ Doxygen 100% coverage, 0 warnings
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two additions that close out the reviewer-prep work cleanly:
1. doc/reviewer/hub.html + publish-workflow integration
──────────────────────────────────────────────────────
Move the hand-curated reviewer hub from the codeberg `pages`
branch (where it lived as an opaque snapshot) into the repo as
`doc/reviewer/hub.html`. `.gitea/workflows/doxygen-pages.yml`
gains a conditional `if [ -f doc/reviewer/hub.html ]; then …`
step that installs it as the publish `index.html` and demotes
the auto-generated Doxygen index to `/doxygen.html`.
Effect: merging any of the open PRs into main will trigger the
workflow, which republishes BOTH the Doxygen + the reviewer hub
together. The reviewer URL stays live across merges with zero
manual intervention.
Source-controlled benefits:
* hub edits go through normal PRs, not orphan-branch
force-pushes
* old hub versions live in git history
* the in-repo links now target `branch/main/…` instead of the
transient `preview/reviewer-snapshot-vN/…` paths
2. .gitea/workflows/perf-compile-time.yml — Linux CI bench
────────────────────────────────────────────────────────
New workflow runs on every push to main that touches the build
system or public headers. Five-step matrix measures and reports:
Run 1 cold baseline (no PCH, no Unity, no ccache)
Run 2 + PCH only
Run 3 + PCH + Unity (current default)
Run 4 + FAST_TEST_BUILD=ON (-O0 -g — Linux's expected ~40 % win)
Run 5 + ccache warm rerun (expected ≥ 90 % cache hit)
Validates the Apple-M1 predictions in doc/architecture/compile-time.md
against the Linux + g++ runner where Backend dominates more and
the Apple-clang+PCH friction that defeats ccache locally does not
apply. Job is data-collection only; never blocks a merge.
doc updates:
* doc/architecture/compile-time.md — new "Cross-platform perf bench"
section linking to the workflow + the table of macOS-vs-Linux
expected deltas.
* doc/reviewer/README.md — new "Hub-Page durability" subsection
explaining how the hub survives merges.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Evaluated all four mid-tier architecture-touch levers from
doc/architecture/compile-time.md. Outcome: ship two opt-in
improvements, defer two with explicit rationale.
#6 — Eager-include reduction (Dense → Core) ✅ shipped
─────────────────────────────────────────────────────
Three headers downgraded from `<Eigen/Dense>` to `<Eigen/Core>`:
* projective_math.hpp
* hyper_ideal_visualization_utility.hpp
* mesh_utils.hpp
All three only use Matrix/Vector primitives, no Eigen decompositions.
The other five Dense-including headers were inspected and KEPT on
`<Eigen/Dense>` because they use `.inverse()`, `.determinant()`,
`ColPivHouseholderQR`, or `SelfAdjointEigenSolver`.
Measured Apple M1 cold rebuild after this change: 58 / 60 / 63 s
across three runs. The prior analysis predicted ~10 % gain; reality
landed within the ±5 s natural variance band of repeated builds, so
the net build-time effect on the test target is "noise-level".
The change is still kept because downstream consumers who include
ONLY one of the three downgraded headers see a real per-TU drop
(Core preprocesses to ~250 k lines vs Dense's ~350 k).
#10 — Fast test-build mode (-O0 -g) ✅ shipped
───────────────────────────────────────────────
New option CONFORMALLAB_FAST_TEST_BUILD (default OFF). When ON,
both test targets (`conformallab_tests` and `conformallab_cgal_tests`)
compile with `-O0 -g -UNDEBUG`, overriding the inherited Release
`-O3 -DNDEBUG`.
Measured Apple clang: 51.6 s vs 46.8 s without -O0 → slightly slower.
The Backend phase that prior analysis predicted would drop from 9.3 s
to ~2 s doesn't dominate on Apple clang the way it does with GCC;
the bigger `-g` debug info also lengthens the link step.
Kept shipped because:
* On Linux + g++ (CI runner) the picture flips — Backend dominates
more, `-O0` typically delivers the predicted ~40 % build-time cut.
* Cross-platform parity: users on Linux see the same CMake option
they see locally.
Honest documentation in doc/architecture/compile-time.md notes that
the Apple-clang-local benefit is currently 0 %. Tests RUN ~15× slower
under `-O0` (1.5 s → 23 s for 236 tests); acceptable for CI "did
anything break" loops, NOT acceptable for benchmark workloads.
#5 — Move detail:: impls to .inl files ⏸ deferred
───────────────────────────────────────────────────
Pure enabler for #7. Without #7 landing, the .inl extraction would
just add an extra hop to header reading. Reconsider once a concrete
maintenance reason emerges (e.g. a downstream user wants to override a
detail helper).
#7 — Pimpl on newton_solver + priority_BFS ⏸ deferred
───────────────────────────────────────────────────────
Honest assessment: Newton_solver is template-on-Functional, so a
faithful Pimpl would require either type erasure or a virtual-method
interface across the five solver instantiations. Estimated 1-2 weeks
of refactor with measurable API-surface risk. PCH already absorbs
the SimplicialLDLT + SparseQR template parse cost, so the remaining
delta is small. Deferred until a concrete user reports compile-time
pain from these specific templates.
Documentation
─────────────
README.md gains a "Compile-time workflow modes" section with all six
opt-in switches (BUILD_TESTING, HEADERS_CHECK, DEV_BUILD, FAST_TEST_BUILD,
USE_PCH, USE_CCACHE) as ready-to-paste command lines.
doc/architecture/compile-time.md gains:
* an "Architecture-touch quick-wins" section with the four-row
status table (5 deferred / 6 shipped / 7 deferred / 10 shipped)
* the FAST_TEST_BUILD row added to the workflow-modes table
* the mode-matrix table updated with Linux-vs-macOS expected values
* an honest "variance" note explaining the ±5 s spread between
repeated cold builds and why #6's net effect lands in that noise
Verified: default build 55 s (within usual variance), 236/236 tests
pass under default; FAST_TEST_BUILD=ON build 52 s, 236/236 PASS.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Four orthogonal opt-in build modes on top of the existing PCH + Unity
defaults. Each addresses a specific iteration scenario; defaults are
unchanged (PCH + Unity stays the canonical fast full-rebuild path).
(A) HEADERS_CHECK target — opt-in via -DCONFORMALLAB_HEADERS_CHECK=ON
Per-public-header smoke-compile sentinels. For each of the six
public CGAL umbrella headers, a stub TU `#include <…>\nint main(){}`
is generated at configure time and compiled in isolation.
* Full headers_check build: ≈ 12 s
* Incremental after touching one header: ≈ 0.1 s
Use case: "did my refactor still parse the public API?" without
waiting 55 s for the full CGAL test build.
(C) DEV_BUILD mode — opt-in via -DCONFORMALLAB_DEV_BUILD=ON
PCH stays on; Unity Build is forced off (both globally AND on the
cgal-tests target which previously overrode the global setting).
Trade-off: full clean rebuild ~75 s (+36 % vs the 55 s default)
but incremental rebuild after editing a single test file drops
from ~46 s (unity batch) to ~16 s (single TU + relink).
Flip on for trial-and-error sessions, flip off before measuring
CI build time or shipping a PR.
(D) ccache integration — default ON, disable with -DCONFORMALLAB_USE_CCACHE=OFF
Detects `ccache` on PATH and prepends it to compile + link
launchers. On Apple clang + PCH + Unity the macOS-local hit
rate is currently 0 % (3 separate friction points documented
in doc/architecture/compile-time.md § "ccache — honesty notes");
stays neutral when it doesn't help. Real payoff on Linux CI
(g++ + traditional PCH) where 80 %+ hit rates are typical.
(BUILD_TESTING=OFF) Standard CMake gate, now respected end-to-end.
Wrapped both `add_subdirectory(tests)` AND the FetchContent of
GoogleTest in `if(BUILD_TESTING)`. Pass `-DBUILD_TESTING=OFF`:
* Configure ≈ 1 s
* Build ≈ 0 s
* 0 object files
* No GTest fetch
Use case: IDE-syntax-check workflow that needs
`compile_commands.json` but does NOT need to download GTest
or build any test binary.
doc/architecture/compile-time.md gains:
* a "Workflow modes — what to choose when" section with a 4-row
switch matrix and a "mode matrix at a glance" comparison table
* a ccache honesty-notes block listing the three macOS friction
points (PCH artefact caching, Unity Build path randomisation,
CMake launcher integration) — Linux CI is where the lever pays
off
Verified: default build 53 s wall, 236/236 tests pass; all opt-in
modes tested end-to-end with their expected workflow numbers
documented in the doc.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two structural compile-time optimisations on the conformallab_cgal_tests
target, both opt-out-able and verified safe (236/236 tests pass under
every configuration).
(1) Precompiled headers — option CONFORMALLAB_USE_PCH (default ON)
target_precompile_headers(conformallab_cgal_tests PRIVATE
<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/Dense> <Eigen/Sparse> <Eigen/SparseCholesky> <Eigen/SparseQR>
<gtest/gtest.h>
<vector> <string> <cmath> <complex>
)
Absorbs the per-TU CGAL+Eigen template-parse cost (measured at 5.9 s
per minimal "include <CGAL/Discrete_conformal_map.h>" hello-world TU
on Apple M1).
(2) Unity Build — UNITY_BUILD ON with UNITY_BUILD_BATCH_SIZE 4
Concatenates the 22 test TUs into 5 batches of <=4 files each;
CGAL+Eigen headers parsed once per batch instead of once per TU.
Batch size 4 keeps gtest's TEST(...) macros and per-file
`using namespace ...` from colliding across batched files.
Numbers (Apple M1, Ninja, -j8, clean rebuild)
─────────────────────────────────────────────
wall CPU tests
baseline 78 s 676 s 236/236
+ PCH 66 s 474 s 236/236 (-15% wall, -30% CPU)
+ PCH + Unity 55 s 167 s 236/236 (-30% wall, -75% CPU)
Honest deferred items (documented in doc/architecture/compile-time.md):
* `extern template` (lever #2 in the analysis) — subsumed by PCH;
estimated residual gain <5%, would add Eigen-version fragility.
* Header split <CGAL/Discrete_conformal_map_{euclidean,spherical,
hyper_ideal}.h> (lever #3) — downstream-only benefit (our test
build needs all three); kept as a future cleanup once a downstream
user actually requests it.
Opt-outs: `-DCONFORMALLAB_USE_PCH=OFF` and `-DCMAKE_UNITY_BUILD=OFF`.
Detailed measurement methodology, per-TU breakdowns, clang
-ftime-trace template hot-spots, and a "what comes next" lever list
live in doc/architecture/compile-time.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Earlier baustein B (`07c653c`) added a 6-row Research-Alignments table
+ a "what's new" banner, but only surfaced ~75 % of what the four
Java-scan + literature-integration commits (`f854f0e`, `979f30c`,
`e8a118f`, `8daee1b`) had introduced. This commit closes the gaps
identified in the lückenprüfung:
Research-Alignments table grows from 6 → 10 rows
─────────────────────────────────────────────────
* +quasi-isothermic maps (Phase 10e, Java port — 6 classes incl.
discrete Beltrami-field solver, Lawson correspondence ~800 lines)
* +higher-genus + hyperelliptic surfaces (Phase 10b — `HyperellipticUtility`
+ Bobenko–Bücking 2009)
* +Möbius centring as variational problem (Phase 9d.4 — replaces the
iterative Fréchet-mean fallback in `normalise_hyperbolic()`)
* +Boundary-First / interactive flattening row (Crane 2017 BFF; Stripe
Patterns 2015) — listed for comparison even though it is not on the
porting roadmap, so the reader sees we know about it
* table caption clarifies that some rows are RESEARCH-only (no Java
parent) and some are planned ports
"What's new" banner
───────────────────
* citation count corrected from "+9" to "+13" — the four Tier-2 papers
added in commit `e8a118f` (Springborn 2019, Springborn–Veselov 2015,
Crane 2017 BFF, Bonneel et al. 2015 Stripe Patterns) are now named
* phase count of "+6 phases" kept, but +Phase 9d.4 (Möbius centring)
and +Phase 10e (quasi-isothermic) are now called out explicitly so
the reader knows what is in the count
* cross-link to `java-parity.md` for the reverse table (every Java
class → C++ destination or *do-not-port* rationale)
questions.md / Q1
─────────────────
* expand the table from 3 to 6 candidate phases — adding 10e (quasi-
isothermic), 10b (hyperelliptic), and 9d.4 (Möbius centring) as
options alongside the existing 9d.2 / 9f / 10c+10c′
* track column distinguishes RESEARCH-only from planned-port
* +Question C — "is there a seventh line we are missing?" — invites
the reader to flag a research thread we have not scoped yet
Pages hub will be refreshed to v5 in a follow-up step (separate from
the in-repo commit, lives only on the codeberg `pages` branch).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Updates the "Quick links the reviewer should bookmark" section of
doc/reviewer/README.md so it matches the Pages-hub v4 layout:
* the hub itself now carries status badges, a "what's new" banner,
and the research-alignment table — call this out so the reader
knows what to expect when they click through;
* adds direct links to the three documents that the new Q1/Q2/Q3
questions actually depend on (references.md, phases.md,
research-track.md) — previously only the Schläfli-derivation note
and the locked-vs-flexible architecture page were linked;
* re-points the Schläfli derivation link from "Q2" (old numbering)
to "Q3" (new numbering after baustein B inserted research
questions at Q1/Q2).
Companion change to the Pages-hub v4 already pushed to the codeberg
`pages` branch; this commit keeps the in-repo guidance in sync.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>