Files
ConformalLabpp/code/include/hyper_ideal_visualization_utility.hpp
Tarik Moussa bc40a13e8d perf: architecture-touch quick-wins #6 + #10; skip #5 + #7 with honest notes
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>
2026-05-26 11:15:09 +02:00

132 lines
4.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
// Copyright (c) 2024-2026 Tarik Moussa.
// SPDX-License-Identifier: MIT
// Port of the static helper
// HyperIdealVisualizationPlugin.getEuclideanCircleFromHyperbolic()
// from de.varylab.discreteconformal.plugin.
//
// Converts a hyperbolic circle (center + radius in the hyperboloid model)
// to its Euclidean representation (cx, cy, r) in the Poincaré disk model.
//
// Mathematical background
// -----------------------
// Hyperboloid model: points (x,y,z,w) with w²-x²-y²-z²=1, w>0.
// Metric signature: g = diag(+1,+1,+1,1) (spatial-first, time-last).
//
// Hyperbolic translation from the origin e₄=(0,0,0,1) to p=(a,b,c,d):
// T = [ I₃ + p'·p'ᵀ/(d+1) p' ] p' = (a,b,c)
// [ p'ᵀ d ]
// This is the standard Lorentz boost; it is in O(3,1) and maps e₄ → p.
//
// Poincaré disk projection (jReality convention):
// (x,y,z,w) → (x,y) / (w+1)
//
// The three reference points on the unit hyperbolic circle (at origin) are
// p1 = (sinh r, 0, 0, cosh r)
// p2 = (0, sinh r, 0, cosh r)
// p3 = (-sinh r, 0, 0, cosh r)
// After translation and projection to the Poincaré disk their circumcircle
// equals the image of the original hyperbolic circle.
#include <Eigen/Core> // downgraded from <Eigen/Dense>: this header only
// uses Matrix/Vector primitives, no decompositions.
#include <array>
#include <cmath>
namespace conformallab {
/// Circumcenter of three 2-D points (`a`, `b`, `c`) in the Euclidean plane.
inline Eigen::Vector2d circumcenter2d(
const Eigen::Vector2d& a,
const Eigen::Vector2d& b,
const Eigen::Vector2d& c)
{
double ax = a.x(), ay = a.y();
double bx = b.x(), by = b.y();
double cx = c.x(), cy = c.y();
double D = 2.0 * (ax*(by - cy) + bx*(cy - ay) + cx*(ay - by));
double a2 = ax*ax + ay*ay;
double b2 = bx*bx + by*by;
double c2 = cx*cx + cy*cy;
double ux = (a2*(by - cy) + b2*(cy - ay) + c2*(ay - by)) / D;
double uy = (a2*(cx - bx) + b2*(ax - cx) + c2*(bx - ax)) / D;
return {ux, uy};
}
/// 4×4 Lorentz boost: maps the hyperboloid origin `e₄ = (0,0,0,1)` to
/// `center`. Precondition: `center` lies on the hyperboloid.
inline Eigen::Matrix4d hyperboloidTranslation(const Eigen::Vector4d& center)
{
Eigen::Vector3d p = center.head<3>();
double d = center(3);
Eigen::Matrix4d T = Eigen::Matrix4d::Identity();
// Upper-left 3×3 block: I + p'·p'ᵀ / (d+1)
T.block<3,3>(0,0) += p * p.transpose() / (d + 1.0);
// Right column and bottom row
T.block<3,1>(0,3) = p;
T.block<1,3>(3,0) = p.transpose();
T(3,3) = d;
return T;
}
/// Project a hyperboloid point `x` onto the Poincaré disk (jReality
/// convention: add 1 to the w-coordinate, then dehomogenise spatial part).
inline Eigen::Vector2d toPoincareDisk(const Eigen::Vector4d& x)
{
double w = x(3) + 1.0;
return {x(0) / w, x(1) / w};
}
// ---------------------------------------------------------------------------
// getEuclideanCircleFromHyperbolic
//
// Inputs
// center point on the hyperboloid, e.g. (0,0,0,1) for the origin
// radius hyperbolic radius (real number > 0)
//
// Output
// { euclidean_cx, euclidean_cy, euclidean_radius }
// describing the circle in the Poincaré disk that corresponds to the
// given hyperbolic circle.
//
// Port of HyperIdealVisualizationPlugin.getEuclideanCircleFromHyperbolic()
// ---------------------------------------------------------------------------
/// Convert a hyperbolic circle (`center` on the hyperboloid, hyperbolic
/// `radius`) to the corresponding Euclidean circle in the Poincaré disk;
/// returns `{cx, cy, r}`. Port of `HyperIdealVisualizationPlugin
/// .getEuclideanCircleFromHyperbolic()`.
inline std::array<double,3> getEuclideanCircleFromHyperbolic(
const Eigen::Vector4d& center, double radius)
{
const double s = std::sinh(radius);
const double ch = std::cosh(radius);
// Three points on the hyperbolic circle centered at the origin
Eigen::Vector4d p1( s, 0.0, 0.0, ch);
Eigen::Vector4d p2(0.0, s, 0.0, ch);
Eigen::Vector4d p3(-s, 0.0, 0.0, ch);
// Apply the hyperbolic translation to the target center
const Eigen::Matrix4d T = hyperboloidTranslation(center);
p1 = T * p1;
p2 = T * p2;
p3 = T * p3;
// Project to the Poincaré disk
const Eigen::Vector2d q1 = toPoincareDisk(p1);
const Eigen::Vector2d q2 = toPoincareDisk(p2);
const Eigen::Vector2d q3 = toPoincareDisk(p3);
// Euclidean circumcircle of the three projected points
const Eigen::Vector2d ec = circumcenter2d(q1, q2, q3);
const double r = (ec - q1).norm();
return {ec.x(), ec.y(), r};
}
} // namespace conformallab