chore: translate all German text to English across code, docs, and CI
Some checks failed
C++ Tests / test-fast (push) Successful in 2m15s
C++ Tests / test-cgal (push) Has been skipped
C++ Tests / test-fast (pull_request) Successful in 2m23s
C++ Tests / test-cgal (pull_request) Failing after 2m36s

Unified the codebase language to English throughout. German text appeared
in code comments, test file headers, CI step names, and several markdown
documents. All natural-language text is now English; proper nouns
(Institut für Mathematik, Technische Universität Berlin) are unchanged.

Files changed:
- .gitea/workflows/cpp-tests.yml  — CI step names and job comments
- code/include/mesh_utils.hpp     — inline comment
- code/tests/cgal/CMakeLists.txt  — section comment block
- code/tests/cgal/test_geometry_utils.cpp — full file header + all test comments
- doc/math/references.md          — geometry-central section
- doc/math/validation.md          — Section 9 (geometry-central cross-validation)
- doc/roadmap/phases.md           — Optional geometry-central track (GC-1/2/3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-19 00:09:09 +02:00
parent 52f61cec36
commit e958afbd19
7 changed files with 214 additions and 223 deletions

View File

@@ -11,8 +11,8 @@ on:
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Job 1 — test-fast # Job 1 — test-fast
# Pure-math tests (Clausen, ImLi₂, Hyper-ideal Geometrie). # Pure-math tests (Clausen, ImLi₂, Hyper-ideal geometry).
# Kein CGAL, kein Boost. Nur Eigen + GTest. Läuft auf ALLEN Branches. # No CGAL, no Boost. Eigen + GTest only. Runs on ALL branches.
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
jobs: jobs:
test-fast: test-fast:
@@ -35,7 +35,7 @@ jobs:
--output-on-failure --output-on-failure
--output-junit test-results.xml --output-junit test-results.xml
- name: Zusammenfassung - name: Summary
if: always() if: always()
run: | run: |
if [ -f test-results.xml ]; then if [ -f test-results.xml ]; then
@@ -48,14 +48,14 @@ jobs:
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Job 2 — test-cgal # Job 2 — test-cgal
# Vollständige CGAL-Test-Suite (Phase 37, 158 Tests). # Full CGAL test suite (Phase 37, 158 tests).
# Läuft NUR bei Pull Requests (nicht bei direkten Pushes auf dev/main). # Runs ONLY on pull requests (not on direct pushes to dev/main).
# Startet erst nach erfolgreichem test-fast. # Starts only after test-fast succeeds.
# #
# Verwendet -DWITH_CGAL_TESTS=ON (nicht -DWITH_CGAL=ON), damit kein # Uses -DWITH_CGAL_TESTS=ON (not -DWITH_CGAL=ON) to avoid building
# Viewer/GLFW gebaut wird — der CI-Container hat kein wayland-scanner. # Viewer/GLFW — the CI container has no wayland-scanner.
# #
# Boost (libboost-dev) ist seit Image-Rebuild bereits im Container. # Boost (libboost-dev) is already present in the container since the image rebuild.
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
test-cgal: test-cgal:
needs: test-fast needs: test-fast
@@ -68,7 +68,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Configure (WITH_CGAL_TESTS — kein Viewer, kein wayland-scanner) - name: Configure (WITH_CGAL_TESTS — no viewer, no wayland-scanner)
run: cmake -S code -B build -DWITH_CGAL_TESTS=ON -DCMAKE_BUILD_TYPE=Release run: cmake -S code -B build -DWITH_CGAL_TESTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build CGAL-Tests - name: Build CGAL-Tests
@@ -81,7 +81,7 @@ jobs:
--output-on-failure --output-on-failure
--output-junit cgal-results.xml --output-junit cgal-results.xml
- name: Zusammenfassung - name: Summary
if: always() if: always()
run: | run: |
if [ -f cgal-results.xml ]; then if [ -f cgal-results.xml ]; then

View File

@@ -36,7 +36,7 @@ void simple_visualize_mesh(Eigen::MatrixXd& V, Eigen::MatrixXi& F) {
viewer.data().set_mesh(V, F); viewer.data().set_mesh(V, F);
viewer.launch(); viewer.launch();
} }
// Zero-Copy Map für V (optional) // Zero-copy map for V (optional)
template <typename Kernel> template <typename Kernel>
Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::RowMajor>> Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::RowMajor>>
get_vertex_map(CGAL::Surface_mesh<typename Kernel::Point_3>& mesh) { get_vertex_map(CGAL::Surface_mesh<typename Kernel::Point_3>& mesh) {

View File

@@ -44,10 +44,10 @@ add_executable(conformallab_cgal_tests
# period matrix, fundamental domain, tiling # period matrix, fundamental domain, tiling
test_phase7.cpp test_phase7.cpp
# ── Java-Parität: Geometrie-Utility-Tests ───────────────────────────────── # ── Java parity: geometry utility tests ─────────────────────────────────
# Portiert aus CuttinUtilityTest, UnwrapUtilityTest, # Ported from CuttinUtilityTest, UnwrapUtilityTest,
# ConvergenceUtilityTests, HomologyTest (Tests 16). # ConvergenceUtilityTests, HomologyTest (tests 16).
# Test 7 (Genus-2-Homologie) als GTEST_SKIP-Stub bis Phase 8. # Test 7 (genus-2 homology) as GTEST_SKIP stub until Phase 8.
test_geometry_utils.cpp test_geometry_utils.cpp
# ── Scalability smoke tests ──────────────────────────────────────────────── # ── Scalability smoke tests ────────────────────────────────────────────────

View File

@@ -1,64 +1,64 @@
// test_geometry_utils.cpp // test_geometry_utils.cpp
// //
// Portierung der Java ConformalLab Geometrie-Utility-Tests. // Port of the Java ConformalLab geometry utility tests.
// //
// Java-Quelle Java-Testmethode Status // Java source Java test method Status
// ───────────────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────────────
// CuttinUtilityTest.java testIsInConvexTextureFace_False PORTIERT // CuttinUtilityTest.java testIsInConvexTextureFace_False PORTED
// CuttinUtilityTest.java testIsInConvexTextureFace_True PORTIERT // CuttinUtilityTest.java testIsInConvexTextureFace_True PORTED
// UnwrapUtilityTest.java testGetAngleReturnsPI PORTIERT // UnwrapUtilityTest.java testGetAngleReturnsPI PORTED
// ConvergenceUtilityTests.java testGetTextureCircumRadius PORTIERT // ConvergenceUtilityTests.java testGetTextureCircumRadius PORTED
// ConvergenceUtilityTests.java testGetTextureTriangleArea PORTIERT // ConvergenceUtilityTests.java testGetTextureTriangleArea PORTED
// ConvergenceUtilityTests.java testScaleInvariantCircumCircleRadius PORTIERT // ConvergenceUtilityTests.java testScaleInvariantCircumCircleRadius PORTED
// HomologyTest.java testHomology PORTIERT // HomologyTest.java testHomology PORTED
// EuclideanLayoutTest.java testDoLayout PORTIERT // EuclideanLayoutTest.java testDoLayout PORTED
// EuclideanCyclicConvergenceTest.java testEuclideanConvergence PORTIERT // EuclideanCyclicConvergenceTest.java testEuclideanConvergence PORTED
// SphericalConvergenceTest.java testSphericalConvergence PORTIERT // SphericalConvergenceTest.java testSphericalConvergence PORTED
// //
// ─── Geometrische Grundlage ────────────────────────────────────────────────────────── // ─── Geometric background ────────────────────────────────────────────────────────────
// //
// Tests 12 Punkt-in-konvexem-Dreieck (2D UV-Raum, baryzentrische Vorzeichen-Methode) // Tests 12 Point-in-convex-triangle (2D UV space, barycentric sign method)
// Java: CuttingUtility.isInConvexTextureFace(pp, face, adapters) // Java: CuttingUtility.isInConvexTextureFace(pp, face, adapters)
// Hinweis: Java-Test 2 hat ein 5-elementiges T-Array mit w=0 (Punkt im // Note: Java test 2 has a 5-element T-array with w=0 (point at
// Unendlichen), was ein Tippfehler im Original ist. Hier werden // infinity), which is a typo in the original. Equivalent, well-formed
// äquivalente, wohlgeformte Koordinaten verwendet. // coordinates are used here instead.
// //
// Test 3 Eckenwinkel für kollineare Vertices über den Kosinussatz. // Test 3 Corner angle for collinear vertices via the law of cosines.
// Java: UnwrapUtility.getAngle(edge, adapters) — gibt den Winkel am // Java: UnwrapUtility.getAngle(edge, adapters) — returns the angle at
// Zielknoten zurück. Für v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) ist // the target vertex. For v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) the
// der Winkel bei v1 genau π (Dreiecksungleichung entartet). // angle at v1 is exactly π (degenerate triangle inequality).
// //
// Tests 45 2D Umkreisradius und Dreiecksfläche. // Tests 45 2D circumradius and triangle area.
// Java: ConvergenceUtility.getTextureCircumCircleRadius(face) // Java: ConvergenceUtility.getTextureCircumCircleRadius(face)
// ConvergenceUtility.getTextureTriangleArea(face) // ConvergenceUtility.getTextureTriangleArea(face)
// Formeln: Area = |det([B-A, C-A])| / 2 // Formulas: Area = |det([B-A, C-A])| / 2
// R = (a·b·c) / (4·Area) // R = (a·b·c) / (4·Area)
// //
// Test 6 Skaleninvarianter Umkreisradius über ein Mesh. // Test 6 Scale-invariant circumradius over a mesh.
// Java: ConvergenceUtility.getMaxMeanSumScaleInvariantCircumRadius(hds) // Java: ConvergenceUtility.getMaxMeanSumScaleInvariantCircumRadius(hds)
// Gibt [max, mean, sum] von R_f / sqrt(total_texture_area) zurück. // Returns [max, mean, sum] of R_f / sqrt(total_texture_area).
// Invariant unter uniformer Skalierung der Texturkoordinaten (Test mit // Invariant under uniform scaling of texture coordinates (tested with
// homogenem Gewicht w: Position = (T[0]/w, T[1]/w)). // homogeneous weight w: position = (T[0]/w, T[1]/w)).
// //
// Test 7 Genus-2 Homologie-Generatoren. // Test 7 Genus-2 homology generators.
// Java: HomologyTest.testHomology (brezel2.obj) // Java: HomologyTest.testHomology (brezel2.obj)
// Erwartet: getGeneratorPaths(root).size() == 4 (2g = 4 für g = 2) // Expected: getGeneratorPaths(root).size() == 4 (2g = 4 for g = 2)
// C++: compute_cut_graph(mesh).cut_edge_indices.size() == 4 // C++: compute_cut_graph(mesh).cut_edge_indices.size() == 4
// Mesh: code/data/obj/brezel2.obj (V=2622, F=5248, χ=2, g=2) // Mesh: code/data/obj/brezel2.obj (V=2622, F=5248, χ=2, g=2)
// Pfad zur Compile-Zeit via CONFORMALLAB_DATA_DIR (CMakeLists.txt). // Path set at compile time via CONFORMALLAB_DATA_DIR (CMakeLists.txt).
// //
// Tests 89 Layout-Kanten-Längenerhalt (tetraflat.obj). // Tests 89 Layout edge-length preservation (tetraflat.obj).
// Java: EuclideanLayoutTest.testDoLayout // Java: EuclideanLayoutTest.testDoLayout
// Nach Layout mit u=0 müssen UV-Kantenlängen == 3D-Kantenlängen (±1e-10). // After layout with u=0, UV edge lengths must equal 3D edge lengths (±1e-10).
// //
// Test 10 Euklidischer Newton auf cathead.obj — Konvergenz + Winkeldefekt. // Test 10 Euclidean Newton on cathead.obj — convergence + angle deficit.
// Java: EuclideanLayoutTest.testLayout02 (130-Werte-Array für cathead.heml) // Java: EuclideanLayoutTest.testLayout02 (130-value array for cathead.heml)
// C++: Newton ab u=0, prüft Konvergenz + Σα_v ≈ 2π für alle inneren Knoten. // C++: Newton from u=0, checks convergence + Σα_v ≈ 2π for all interior nodes.
// //
// Test 11 Sphärischer Newton auf Oktaeder — Konvergenz + Winkeldefekt. // Test 11 Spherical Newton on octahedronconvergence + angle deficit.
// Java: SphericalConvergenceTest.testSphericalConvergence (Oktaeder, zufällig // Java: SphericalConvergenceTest.testSphericalConvergence (octahedron, randomly
// störe Radien, seed=1). C++: konstruierter regulärer Oktaeder, prüft // perturbed radii, seed=1). C++: constructed regular octahedron, checks
// Konvergenz und dass Σα_v ≈ 2π (Target für Sphäre nach prepareInvariantData). // convergence and that Σα_v ≈ 2π (target for sphere after prepareInvariantData).
// //
// ───────────────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────────────
@@ -81,12 +81,12 @@
using namespace conformallab; using namespace conformallab;
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
// Lokale Geometrie-Hilfsfunktionen // Local geometry helper functions
// (portiert aus Java CuttingUtility / ConvergenceUtility) // (ported from Java CuttingUtility / ConvergenceUtility)
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
/// Punkt-in-Dreieck Test (2D, baryzentrische Vorzeichenmethode). /// Point-in-triangle test (2D, barycentric sign method).
/// Gibt true zurück wenn p strikt innerhalb oder auf dem Rand von v0-v1-v2 liegt. /// Returns true if p lies strictly inside or on the boundary of v0-v1-v2.
/// Java: CuttingUtility.isInConvexTextureFace /// Java: CuttingUtility.isInConvexTextureFace
static bool point_in_triangle_2d( static bool point_in_triangle_2d(
Eigen::Vector2d p, Eigen::Vector2d p,
@@ -103,7 +103,7 @@ static bool point_in_triangle_2d(
return !(has_neg && has_pos); return !(has_neg && has_pos);
} }
/// 2D Dreiecksfläche (halbes Kreuzprodukt). /// 2D triangle area (half cross product).
/// Java: ConvergenceUtility.getTextureTriangleArea /// Java: ConvergenceUtility.getTextureTriangleArea
static double triangle_area_2d( static double triangle_area_2d(
Eigen::Vector2d A, Eigen::Vector2d B, Eigen::Vector2d C) Eigen::Vector2d A, Eigen::Vector2d B, Eigen::Vector2d C)
@@ -112,7 +112,7 @@ static double triangle_area_2d(
- (B - A).y() * (C - A).x()) * 0.5; - (B - A).y() * (C - A).x()) * 0.5;
} }
/// 2D Umkreisradius: R = (a·b·c) / (4·Area). /// 2D circumradius: R = (a·b·c) / (4·Area).
/// Java: ConvergenceUtility.getTextureCircumCircleRadius /// Java: ConvergenceUtility.getTextureCircumCircleRadius
static double circumradius_2d( static double circumradius_2d(
Eigen::Vector2d A, Eigen::Vector2d B, Eigen::Vector2d C) Eigen::Vector2d A, Eigen::Vector2d B, Eigen::Vector2d C)
@@ -125,17 +125,17 @@ static double circumradius_2d(
return (a * b * c) / (4.0 * area); return (a * b * c) / (4.0 * area);
} }
/// Skaleninvarianter Umkreisradius für ein Mesh: /// Scale-invariant circumradius for a mesh:
/// scale_R_f = R_f / sqrt(total_area) /// scale_R_f = R_f / sqrt(total_area)
/// Gibt {max, mean, sum} über alle Flächen zurück. /// Returns {max, mean, sum} over all faces.
/// Java: ConvergenceUtility.getMaxMeanSumScaleInvariantCircumRadius /// Java: ConvergenceUtility.getMaxMeanSumScaleInvariantCircumRadius
/// ///
/// Homogene Koordinaten: Position = (x/w, y/w). /// Homogeneous coordinates: position = (x/w, y/w).
static std::array<double, 3> scale_invariant_circumradius_stats( static std::array<double, 3> scale_invariant_circumradius_stats(
const std::vector<Eigen::Vector2d>& verts, const std::vector<Eigen::Vector2d>& verts,
const std::vector<std::array<int, 3>>& faces) const std::vector<std::array<int, 3>>& faces)
{ {
// Gesamtfläche // Total area
double total_area = 0.0; double total_area = 0.0;
for (auto& f : faces) for (auto& f : faces)
total_area += triangle_area_2d(verts[f[0]], verts[f[1]], verts[f[2]]); total_area += triangle_area_2d(verts[f[0]], verts[f[1]], verts[f[2]]);
@@ -154,70 +154,70 @@ static std::array<double, 3> scale_invariant_circumradius_stats(
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Tests 12 — CuttingUtility: Punkt-in-konvexem-Dreieck (2D UV-Raum) // Tests 12 — CuttingUtility: point-in-convex-triangle (2D UV space)
// Java: CuttinUtilityTest.testIsInConvexTextureFace_False / _True // Java: CuttinUtilityTest.testIsInConvexTextureFace_False / _True
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 1: Punkt liegt weit außerhalb — exakte Java-Koordinaten // Test 1: point lies far outside — exact Java coordinates
TEST(CuttingUtility, IsInConvexTextureFace_False) TEST(CuttingUtility, IsInConvexTextureFace_False)
{ {
// Winziges Dreieck um (0.7488, 0.0629) — Java-Testkoordinaten (T[3]=1, w=1) // Tiny triangle around (0.7488, 0.0629) — Java test coordinates (T[3]=1, w=1)
Eigen::Vector2d v0(0.7488102998904661, 0.06293998610761144); Eigen::Vector2d v0(0.7488102998904661, 0.06293998610761144);
Eigen::Vector2d v1(0.7487811940754379, 0.06289451051246124); Eigen::Vector2d v1(0.7487811940754379, 0.06289451051246124);
Eigen::Vector2d v2(0.7487254625255592, 0.06291429499873116); Eigen::Vector2d v2(0.7487254625255592, 0.06291429499873116);
// Testpunkt weit entfernt bei (0.447, 0.000228) // Test point far away at (0.447, 0.000228)
Eigen::Vector2d pp(0.44661534423161037, 2.2808373704822393e-4); Eigen::Vector2d pp(0.44661534423161037, 2.2808373704822393e-4);
EXPECT_FALSE(point_in_triangle_2d(pp, v0, v1, v2)); EXPECT_FALSE(point_in_triangle_2d(pp, v0, v1, v2));
} }
// Test 2: Punkt liegt innerhalb // Test 2: point lies inside
// Hinweis: Das originale Java-Array p2 hat 5 Elemente mit w=0 (Tippfehler im // Note: the original Java array p2 has 5 elements with w=0 (typo in the
// Java-Original). Hier werden äquivalente, wohlgeformte Koordinaten verwendet, // Java original). Equivalent, well-formed coordinates are used here
// die dasselbe geometrische Szenario abbilden. // that represent the same geometric scenario.
TEST(CuttingUtility, IsInConvexTextureFace_True) TEST(CuttingUtility, IsInConvexTextureFace_True)
{ {
// Dreieck: (0,0) — (1e-8, 0) — (0, 1e-8) // Triangle: (0,0) — (1e-8, 0) — (0, 1e-8)
Eigen::Vector2d v0(0.0, 0.0); Eigen::Vector2d v0(0.0, 0.0);
Eigen::Vector2d v1(1e-8, 0.0); Eigen::Vector2d v1(1e-8, 0.0);
Eigen::Vector2d v2(0.0, 1e-8); Eigen::Vector2d v2(0.0, 1e-8);
// Schwerpunkt des Dreiecks — liegt immer innen // Centroid of the triangle — always lies inside
Eigen::Vector2d pp(1e-8 / 3.0, 1e-8 / 3.0); Eigen::Vector2d pp(1e-8 / 3.0, 1e-8 / 3.0);
EXPECT_TRUE(point_in_triangle_2d(pp, v0, v1, v2)); EXPECT_TRUE(point_in_triangle_2d(pp, v0, v1, v2));
} }
// Zusätzlich: einfaches Einheitsdreieck für Klarheit // Additional: simple unit triangle for clarity
TEST(CuttingUtility, IsInConvexTextureFace_UnitTriangle_InAndOut) TEST(CuttingUtility, IsInConvexTextureFace_UnitTriangle_InAndOut)
{ {
Eigen::Vector2d v0(0.0, 0.0), v1(1.0, 0.0), v2(0.0, 1.0); Eigen::Vector2d v0(0.0, 0.0), v1(1.0, 0.0), v2(0.0, 1.0);
EXPECT_TRUE( point_in_triangle_2d(Eigen::Vector2d(0.25, 0.25), v0, v1, v2)); EXPECT_TRUE( point_in_triangle_2d(Eigen::Vector2d(0.25, 0.25), v0, v1, v2));
EXPECT_FALSE(point_in_triangle_2d(Eigen::Vector2d(2.0, 2.0), v0, v1, v2)); EXPECT_FALSE(point_in_triangle_2d(Eigen::Vector2d(2.0, 2.0), v0, v1, v2));
EXPECT_FALSE(point_in_triangle_2d(Eigen::Vector2d(0.6, 0.6), v0, v1, v2)); // jenseits Hypotenuse EXPECT_FALSE(point_in_triangle_2d(Eigen::Vector2d(0.6, 0.6), v0, v1, v2)); // beyond hypotenuse
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 3 — UnwrapUtility: Eckenwinkel = π für kollineare Vertices // Test 3 — UnwrapUtility: corner angle = π for collinear vertices
// Java: UnwrapUtilityTest.testGetAngleReturnsPI // Java: UnwrapUtilityTest.testGetAngleReturnsPI
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Java: v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) kollinear. // Java: v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) collinear.
// Kante e von v2 nach v1. getAngle(e) = Winkel bei v1 = π. // Edge e from v2 to v1. getAngle(e) = angle at v1 = π.
// //
// C++: Kosinussatz mit Kantenlängen a=|v0-v1|=1, b=|v1-v2|=1, c=|v0-v2|=2. // C++: law of cosines with edge lengths a=|v0-v1|=1, b=|v1-v2|=1, c=|v0-v2|=2.
// cos(γ_v1) = (a² + b² c²) / (2ab) = (1 + 1 4) / 2 = 1 → γ = π // cos(γ_v1) = (a² + b² c²) / (2ab) = (1 + 1 4) / 2 = 1 → γ = π
TEST(UnwrapUtility, GetAngle_CollinearVertices_ReturnsPI) TEST(UnwrapUtility, GetAngle_CollinearVertices_ReturnsPI)
{ {
const double a = 1.0; // |v0 v1| const double a = 1.0; // |v0 v1|
const double b = 1.0; // |v1 v2| const double b = 1.0; // |v1 v2|
const double c = 2.0; // |v0 v2| (= a + b, entartet) const double c = 2.0; // |v0 v2| (= a + b, degenerate)
double cos_angle = (a*a + b*b - c*c) / (2.0 * a * b); double cos_angle = (a*a + b*b - c*c) / (2.0 * a * b);
cos_angle = std::max(-1.0, std::min(1.0, cos_angle)); // numerisches Clamp cos_angle = std::max(-1.0, std::min(1.0, cos_angle)); // numeric clamp
double angle = std::acos(cos_angle); double angle = std::acos(cos_angle);
EXPECT_NEAR(M_PI, angle, 1e-15); EXPECT_NEAR(M_PI, angle, 1e-15);
} }
// Gegenkontrolle: gleichseitiges Dreieck → Winkel = π/3 // Counter-check: equilateral triangle → angle = π/3
TEST(UnwrapUtility, GetAngle_EquilateralTriangle_ReturnsPiOver3) TEST(UnwrapUtility, GetAngle_EquilateralTriangle_ReturnsPiOver3)
{ {
const double s = 1.0; const double s = 1.0;
@@ -227,57 +227,57 @@ TEST(UnwrapUtility, GetAngle_EquilateralTriangle_ReturnsPiOver3)
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 4 — ConvergenceUtility: 2D Umkreisradius // Test 4 — ConvergenceUtility: 2D circumradius
// Java: ConvergenceUtilityTests.testGetTextureCircumRadius // Java: ConvergenceUtilityTests.testGetTextureCircumRadius
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(ConvergenceUtility, TextureCircumRadius_RightTriangle) TEST(ConvergenceUtility, TextureCircumRadius_RightTriangle)
{ {
// A=(0,0), B=(1,0), C=(0,1): rechtwinkliges gleichschenkliges Dreieck // A=(0,0), B=(1,0), C=(0,1): right isosceles triangle
// Seiten: 1, 1, √2. R = √2 / (4 · 0.5) = √2/2 // Sides: 1, 1, √2. R = √2 / (4 · 0.5) = √2/2
Eigen::Vector2d A(0.0, 0.0), B(1.0, 0.0), C(0.0, 1.0); Eigen::Vector2d A(0.0, 0.0), B(1.0, 0.0), C(0.0, 1.0);
EXPECT_NEAR(std::sqrt(2.0) / 2.0, circumradius_2d(A, B, C), 1e-10); EXPECT_NEAR(std::sqrt(2.0) / 2.0, circumradius_2d(A, B, C), 1e-10);
} }
TEST(ConvergenceUtility, TextureCircumRadius_SmallerTriangle) TEST(ConvergenceUtility, TextureCircumRadius_SmallerTriangle)
{ {
// A=(0,0), B=(0.5,0.5), C=(0,1): Java-Variante mit B.T={0.5,0.5,0,1} // A=(0,0), B=(0.5,0.5), C=(0,1): Java variant with B.T={0.5,0.5,0,1}
// Seiten: √0.5, √0.5, 1. Area = 0.25. R = (√0.5·√0.5·1)/(4·0.25) = 0.5 // Sides: √0.5, √0.5, 1. Area = 0.25. R = (√0.5·√0.5·1)/(4·0.25) = 0.5
Eigen::Vector2d A(0.0, 0.0), B(0.5, 0.5), C(0.0, 1.0); Eigen::Vector2d A(0.0, 0.0), B(0.5, 0.5), C(0.0, 1.0);
EXPECT_NEAR(0.5, circumradius_2d(A, B, C), 1e-10); EXPECT_NEAR(0.5, circumradius_2d(A, B, C), 1e-10);
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 5 — ConvergenceUtility: 2D Dreiecksfläche // Test 5 — ConvergenceUtility: 2D triangle area
// Java: ConvergenceUtilityTests.testGetTextureTriangleArea // Java: ConvergenceUtilityTests.testGetTextureTriangleArea
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(ConvergenceUtility, TextureTriangleArea_RightTriangle) TEST(ConvergenceUtility, TextureTriangleArea_RightTriangle)
{ {
// A=(0,0), B=(1,0), C=(0,1) → Fläche = 0.5 // A=(0,0), B=(1,0), C=(0,1) → area = 0.5
Eigen::Vector2d A(0.0, 0.0), B(1.0, 0.0), C(0.0, 1.0); Eigen::Vector2d A(0.0, 0.0), B(1.0, 0.0), C(0.0, 1.0);
EXPECT_NEAR(0.5, triangle_area_2d(A, B, C), 1e-10); EXPECT_NEAR(0.5, triangle_area_2d(A, B, C), 1e-10);
} }
TEST(ConvergenceUtility, TextureTriangleArea_SmallerTriangle) TEST(ConvergenceUtility, TextureTriangleArea_SmallerTriangle)
{ {
// A=(0,0), B=(0.5,0.5), C=(0,1) → Fläche = 0.25 // A=(0,0), B=(0.5,0.5), C=(0,1) → area = 0.25
Eigen::Vector2d A(0.0, 0.0), B(0.5, 0.5), C(0.0, 1.0); Eigen::Vector2d A(0.0, 0.0), B(0.5, 0.5), C(0.0, 1.0);
EXPECT_NEAR(0.25, triangle_area_2d(A, B, C), 1e-10); EXPECT_NEAR(0.25, triangle_area_2d(A, B, C), 1e-10);
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 6 — ConvergenceUtility: Skaleninvarianter Umkreisradius // Test 6 — ConvergenceUtility: scale-invariant circumradius
// Java: ConvergenceUtilityTests.testScaleInvariantCircumCircleRadius // Java: ConvergenceUtilityTests.testScaleInvariantCircumCircleRadius
// //
// Mesh: 4 Vertices (v1..v4), 2 Flächen (f1: v1-v2-v3, f2: v1-v3-v4). // Mesh: 4 vertices (v1..v4), 2 faces (f1: v1-v2-v3, f2: v1-v3-v4).
// Skaleninvariante Größe: R_f / sqrt(total_area) — invariant unter // Scale-invariant quantity: R_f / sqrt(total_area) — invariant under
// uniformer Skalierung (homogeneous weight w: pos = (x/w, y/w)). // uniform scaling (homogeneous weight w: pos = (x/w, y/w)).
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(ConvergenceUtility, ScaleInvariantCircumRadius_BaseScale) TEST(ConvergenceUtility, ScaleInvariantCircumRadius_BaseScale)
{ {
// Positionen bei w=1 (T[3]=1): v1=(0,0), v2=(1,0), v3=(0,1), v4=(-1,0) // Positions at w=1 (T[3]=1): v1=(0,0), v2=(1,0), v3=(0,1), v4=(-1,0)
std::vector<Eigen::Vector2d> verts = { std::vector<Eigen::Vector2d> verts = {
{0.0, 0.0}, // v1 {0.0, 0.0}, // v1
{1.0, 0.0}, // v2 {1.0, 0.0}, // v2
@@ -287,13 +287,13 @@ TEST(ConvergenceUtility, ScaleInvariantCircumRadius_BaseScale)
// f1: v1-v2-v3, f2: v1-v3-v4 // f1: v1-v2-v3, f2: v1-v3-v4
std::vector<std::array<int, 3>> faces = { {0, 1, 2}, {0, 2, 3} }; std::vector<std::array<int, 3>> faces = { {0, 1, 2}, {0, 2, 3} };
// Einzelflächen-Prüfung (Java testGetTextureTriangleArea-Anforderung) // Per-face check (Java testGetTextureTriangleArea requirement)
EXPECT_NEAR(0.5, triangle_area_2d(verts[0], verts[1], verts[2]), 1e-10); EXPECT_NEAR(0.5, triangle_area_2d(verts[0], verts[1], verts[2]), 1e-10);
EXPECT_NEAR(0.5, triangle_area_2d(verts[0], verts[2], verts[3]), 1e-10); EXPECT_NEAR(0.5, triangle_area_2d(verts[0], verts[2], verts[3]), 1e-10);
auto [max_r, mean_r, sum_r] = scale_invariant_circumradius_stats(verts, faces); auto [max_r, mean_r, sum_r] = scale_invariant_circumradius_stats(verts, faces);
// Erwartet: sin(π/4) = √2/2 für max und mean (beide Dreiecke identisch) // Expected: sin(π/4) = √2/2 for max and mean (both triangles identical)
EXPECT_NEAR(std::sin(M_PI / 4.0), max_r, 1e-10); EXPECT_NEAR(std::sin(M_PI / 4.0), max_r, 1e-10);
EXPECT_NEAR(std::sin(M_PI / 4.0), mean_r, 1e-10); EXPECT_NEAR(std::sin(M_PI / 4.0), mean_r, 1e-10);
EXPECT_NEAR(2.0 * std::sin(M_PI / 4.0), sum_r, 1e-10); EXPECT_NEAR(2.0 * std::sin(M_PI / 4.0), sum_r, 1e-10);
@@ -301,7 +301,7 @@ TEST(ConvergenceUtility, ScaleInvariantCircumRadius_BaseScale)
TEST(ConvergenceUtility, ScaleInvariantCircumRadius_HalvedByW2_SameResult) TEST(ConvergenceUtility, ScaleInvariantCircumRadius_HalvedByW2_SameResult)
{ {
// Skalierung durch w=2: alle Positionen halbiert (homogene Koordinaten) // Scaling by w=2: all positions halved (homogeneous coordinates)
// pos_scaled = (T[0]/2, T[1]/2) // pos_scaled = (T[0]/2, T[1]/2)
std::vector<Eigen::Vector2d> verts = { std::vector<Eigen::Vector2d> verts = {
{0.0, 0.0}, // v1/2 {0.0, 0.0}, // v1/2
@@ -311,35 +311,35 @@ TEST(ConvergenceUtility, ScaleInvariantCircumRadius_HalvedByW2_SameResult)
}; };
std::vector<std::array<int, 3>> faces = { {0, 1, 2}, {0, 2, 3} }; std::vector<std::array<int, 3>> faces = { {0, 1, 2}, {0, 2, 3} };
// Flächen sind ein Viertel der ursprünglichen (Längen halbiert → Area / 4) // Areas are one quarter of the original (lengths halved → Area / 4)
EXPECT_NEAR(0.125, triangle_area_2d(verts[0], verts[1], verts[2]), 1e-10); EXPECT_NEAR(0.125, triangle_area_2d(verts[0], verts[1], verts[2]), 1e-10);
EXPECT_NEAR(0.125, triangle_area_2d(verts[0], verts[2], verts[3]), 1e-10); EXPECT_NEAR(0.125, triangle_area_2d(verts[0], verts[2], verts[3]), 1e-10);
auto [max_r, mean_r, sum_r] = scale_invariant_circumradius_stats(verts, faces); auto [max_r, mean_r, sum_r] = scale_invariant_circumradius_stats(verts, faces);
// Skaleninvariante Größe muss identisch zu w=1 sein // Scale-invariant quantity must be identical to the w=1 case
EXPECT_NEAR(std::sin(M_PI / 4.0), max_r, 1e-10); EXPECT_NEAR(std::sin(M_PI / 4.0), max_r, 1e-10);
EXPECT_NEAR(std::sin(M_PI / 4.0), mean_r, 1e-10); EXPECT_NEAR(std::sin(M_PI / 4.0), mean_r, 1e-10);
EXPECT_NEAR(2.0 * std::sin(M_PI / 4.0), sum_r, 1e-10); EXPECT_NEAR(2.0 * std::sin(M_PI / 4.0), sum_r, 1e-10);
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 7 — HomologyTest: Genus-2 Homologie-Generatoren // Test 7 — HomologyTest: genus-2 homology generators
// Java: HomologyTest.testHomology // Java: HomologyTest.testHomology
// //
// Java-Test: // Java test:
// CoHDS hds = TestUtility.readOBJ("brezel2.obj"); // Genus-2-Brezel-Fläche // CoHDS hds = TestUtility.readOBJ("brezel2.obj"); // genus-2 pretzel surface
// List<Set<CoEdge>> paths = getGeneratorPaths(hds.getVertex(0), weightAdapter); // List<Set<CoEdge>> paths = getGeneratorPaths(hds.getVertex(0), weightAdapter);
// Assert.assertEquals(4, paths.size()); // 2g = 4 für g = 2 // Assert.assertEquals(4, paths.size()); // 2g = 4 for g = 2
// //
// C++quivalent: // C++ equivalent:
// ConformalMesh mesh = load_mesh("code/data/obj/brezel2.obj"); // ConformalMesh mesh = load_mesh("code/data/obj/brezel2.obj");
// CutGraph cg = compute_cut_graph(mesh); // CutGraph cg = compute_cut_graph(mesh);
// EXPECT_EQ(4u, cg.cut_edge_indices.size()); // 2g = 4 // EXPECT_EQ(4u, cg.cut_edge_indices.size()); // 2g = 4
// EXPECT_EQ(2, cg.genus); // EXPECT_EQ(2, cg.genus);
// //
// Mesh: V=2622, F=5248, E=7872, χ=2, genus=2. // Mesh: V=2622, F=5248, E=7872, χ=2, genus=2.
// Pfad via CONFORMALLAB_DATA_DIR (CMakeLists.txt: ${CMAKE_SOURCE_DIR}/data). // Path via CONFORMALLAB_DATA_DIR (CMakeLists.txt: ${CMAKE_SOURCE_DIR}/data).
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(HomologyGenerators, Genus2_FourCutEdges) TEST(HomologyGenerators, Genus2_FourCutEdges)
@@ -359,17 +359,17 @@ TEST(HomologyGenerators, Genus2_FourCutEdges)
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Tests 89 — EuclideanLayoutTest: Kantenlängenerhalt auf tetraflat.obj // Tests 89 — EuclideanLayoutTest: edge-length preservation on tetraflat.obj
// Java: EuclideanLayoutTest.testDoLayout // Java: EuclideanLayoutTest.testDoLayout
// //
// Java-Test: // Java test:
// Vector u = new SparseVector(n); // u = 0 (kein konformer Faktor) // Vector u = new SparseVector(n); // u = 0 (no conformal factor)
// EuclideanLayout.doLayout(hds, fun, u); // EuclideanLayout.doLayout(hds, fun, u);
// for (CoEdge e : hds.getEdges()) // for (CoEdge e : hds.getEdges())
// assertEquals(Pn.distanceBetween(s.P, t.P), Pn.distanceBetween(s.T, t.T), 1E-11); // assertEquals(Pn.distanceBetween(s.P, t.P), Pn.distanceBetween(s.T, t.T), 1E-11);
// //
// Bedeutung: Mit u=0 ist der konforme Faktor 0, also ℓ̃ = (keine Verformung). // Meaning: with u=0 the conformal factor is 0, so ℓ̃ = (no deformation).
// Das Layout muss die ursprünglichen 3D-Kantenlängen exakt reproduzieren. // The layout must reproduce the original 3D edge lengths exactly.
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(EuclideanLayout, DoLayout_TetraFlat_EdgeLengthsPreserved) TEST(EuclideanLayout, DoLayout_TetraFlat_EdgeLengthsPreserved)
@@ -414,18 +414,18 @@ TEST(EuclideanLayout, DoLayout_TetraFlat_EdgeLengthsPreserved)
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 10 — EuclideanCyclicConvergenceTest: Newton auf cathead.obj // Test 10 — EuclideanCyclicConvergenceTest: Newton on cathead.obj
// Java: EuclideanLayoutTest.testLayout02 (130-Werte-Regression auf cathead.heml) // Java: EuclideanLayoutTest.testLayout02 (130-value regression on cathead.heml)
// EuclideanCyclicConvergenceTest.testEuclideanConvergence // EuclideanCyclicConvergenceTest.testEuclideanConvergence
// //
// Java-Test: // Java test:
// EuclideanLayout.doLayout(hdsCat, fun, uCat); // EuclideanLayout.doLayout(hdsCat, fun, uCat);
// for (CoVertex v : interior vertices) // for (CoVertex v : interior vertices)
// assertEquals(2*PI, calculateAngleSum(v), 1E-6); // assertEquals(2*PI, calculateAngleSum(v), 1E-6);
// for (CoEdge e : positiveEdges) // for (CoEdge e : positiveEdges)
// assertEquals(fun.getNewLength(e, u), tLength, 1E-6); // assertEquals(fun.getNewLength(e, u), tLength, 1E-6);
// //
// C++quivalent: Newton converges on cathead.obj; interior angle sums ≈ 2π. // C++ equivalent: Newton converges on cathead.obj; interior angle sums ≈ 2π.
// The 130-value u-vector from the Java test is cathead-topology-specific and // The 130-value u-vector from the Java test is cathead-topology-specific and
// depends on vertex ordering in the Java CoHDS — not portable directly. // depends on vertex ordering in the Java CoHDS — not portable directly.
// Instead we verify the same mathematical invariant: convergence + angle sums. // Instead we verify the same mathematical invariant: convergence + angle sums.
@@ -468,18 +468,18 @@ TEST(EuclideanLayout, CatHead_NewtonConverges_AngleSumsTwoPi)
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 11 — SphericalConvergenceTest: Newton auf Oktaeder // Test 11 — SphericalConvergenceTest: Newton on octahedron
// Java: SphericalConvergenceTest.testSphericalConvergence // Java: SphericalConvergenceTest.testSphericalConvergence
// //
// Java-Test: // Java test:
// FunctionalTest.createOctahedron(hds, aSet); // FunctionalTest.createOctahedron(hds, aSet);
// // randomly perturb vertex radii (seed=1) // // randomly perturb vertex radii (seed=1)
// prepareInvariantDataHyperbolicAndSpherical(functional, hds, aSet, u); // prepareInvariantDataHyperbolicAndSpherical(functional, hds, aSet, u);
// optimizer.minimize(u, opt); // optimizer.minimize(u, opt);
// for (CoVertex v) assertEquals(2*PI, sum of angles at v, 1E-8); // for (CoVertex v) assertEquals(2*PI, sum of angles at v, 1E-8);
// //
// C++: regulärer Oktaeder (alle Knoten auf S², keine Störung), sphärischer Newton, // C++: regular octahedron (all vertices on S², no perturbation), spherical Newton,
// prüft Konvergenz + Restgradienten (≡ Winkeldefekt = 0 nach Konvergenz). // checks convergence + residual gradients (≡ angle deficit = 0 after convergence).
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(SphericalLayout, SphericalTetrahedron_NewtonConverges_AngleSumsTwoPi) TEST(SphericalLayout, SphericalTetrahedron_NewtonConverges_AngleSumsTwoPi)

View File

@@ -28,22 +28,22 @@ Java reference implementation: [github.com/varylab/conformallab](https://github.
## geometry-central cross-reference *(optional comparison track)* ## geometry-central cross-reference *(optional comparison track)*
> Diese Referenzen beziehen sich auf eine alternative Implementierung desselben > These references relate to an alternative implementation of the same
> mathematischen Problems. Sie sind keine Voraussetzung für conformallab++, > mathematical problem. They are not prerequisites for conformallab++,
> aber relevant für Kreuz-Validierung und mögliche algorithmische Adoptionen > but are relevant for cross-validation and possible algorithmic adoptions
> (→ GC-1/2/3 im Phasen-Roadmap, → Abschnitt 9 in `validation.md`). > (→ GC-1/2/3 in the phase roadmap, → Section 9 in `validation.md`).
| Reference | Relevanz | | Reference | Relevance |
|---|---| |---|---|
| **Gillespie, Springborn, Crane***Discrete Conformal Equivalence of Polyhedral Surfaces*, ACM SIGGRAPH 2021. DOI: [10.1145/3450626.3459763](https://doi.org/10.1145/3450626.3459763) | Implementiert in **geometry-central**. Erweitert Springborn 2020 um intrinsische Triangulierungen und Ptolemäische Flips. Löst dasselbe DCE-Problem wie conformallab++, aber mit anderem Algorithmus. | | **Gillespie, Springborn, Crane***Discrete Conformal Equivalence of Polyhedral Surfaces*, ACM SIGGRAPH 2021. DOI: [10.1145/3450626.3459763](https://doi.org/10.1145/3450626.3459763) | Implemented in **geometry-central**. Extends Springborn 2020 with intrinsic triangulations and Ptolemaic flips. Solves the same DCE problem as conformallab++, but with a different algorithm. |
| **Sharp, Soliman, Crane***Navigating Intrinsic Triangulations*, ACM SIGGRAPH 2019 | Algorithmische Grundlage für `SignpostIntrinsicTriangulation` in geometry-central — relevant für GC-2 (optionales Pre-Conditioning). | | **Sharp, Soliman, Crane***Navigating Intrinsic Triangulations*, ACM SIGGRAPH 2019 | Algorithmic basis for `SignpostIntrinsicTriangulation` in geometry-central — relevant for GC-2 (optional pre-conditioning). |
**Hinweis zu Springborn 2020:** **Note on Springborn 2020:**
Das Papier *"Ideal Hyperbolic Polyhedra and Discrete Uniformization"* The paper *"Ideal Hyperbolic Polyhedra and Discrete Uniformization"*
(Springborn, Discrete & Computational Geometry 2020) ist **in conformallab++ (Springborn, Discrete & Computational Geometry 2020) is **already implemented in
bereits implementiert** — es ist die direkte Referenz für den HyperIdeal-Geometriemodus conformallab++** — it is the direct reference for the HyperIdeal geometry mode
(`hyper_ideal_geometry.hpp`). Die geometry-central Implementierung (Gillespie 2021) (`hyper_ideal_geometry.hpp`). The geometry-central implementation (Gillespie 2021)
baut auf diesem Papier auf und ergänzt es um Ptolemäische Flips. builds on this paper and augments it with Ptolemaic flips.
--- ---

View File

@@ -193,66 +193,58 @@ These are the **holonomy consistency** checks implemented in `test_phase7.cpp`
## 9 — Cross-validation with geometry-central *(optional / hypothetical)* ## 9 — Cross-validation with geometry-central *(optional / hypothetical)*
> **Hinweis:** Dieser Abschnitt beschreibt eine mögliche externe Kreuz-Validierung, > **Note:** This section describes a possible external cross-validation that is not
> die keine Voraussetzung für die Korrektheit der Implementierung ist. > a prerequisite for the correctness of the implementation.
> Sie ist interessant, weil geometry-central denselben mathematischen Kern > It is of interest because geometry-central implements the same mathematical core
> implementiert (Gillespie, Springborn, Crane — SIGGRAPH 2021, aufbauend auf > (Gillespie, Springborn, Crane — SIGGRAPH 2021, building on
> Springborn 2020), aber mit einer anderen algorithmischen Strategie > Springborn 2020), but with a different algorithmic strategy
> (Ptolemäische Flips + intrinsische Triangulierungen statt Newton auf der > (Ptolemaic flips + intrinsic triangulations instead of Newton on the
> Original-Triangulierung). > original triangulation).
### Welche Outputs sind vergleichbar? ### Which outputs are comparable?
| Output | conformallab++ | geometry-central | Vergleichbar? | | Output | conformallab++ | geometry-central | Comparable? |
|---|---|---|---| |---|---|---|---|
| u-Vektor (Skalierungsparameter) | `res.x` | `u` nach Yamabe flow | ✓ nach Normalisierung | | u-vector (scale parameters) | `res.x` | `u` after Yamabe flow | ✓ after normalisation |
| UV-Koordinaten | `layout.uv[v]` | konforme Parametrisierung | ✓ bis auf Möbius-Transformation | | UV coordinates | `layout.uv[v]` | conformal parameterisation | ✓ up to Möbius transformation |
| Gauss-Bonnet Defekt | `gauss_bonnet_sum()` | implizit via Krümmungsfluss | ✓ (analytisch identisch) | | Gauss-Bonnet deficit | `gauss_bonnet_sum()` | implicit via curvature flow | ✓ (analytically identical) |
| Anzahl Newton-Iterationen | `res.iterations` | Yamabe-Schritte | ~ (anderer Algorithmus) | | Number of Newton iterations | `res.iterations` | Yamabe steps | ~ (different algorithm) |
| Period-Matrix τ | `pd.tau_reduced` | **nicht vorhanden** | ✗ | | Period matrix τ | `pd.tau_reduced` | **not available** | ✗ |
| Möbius-Holonomie | `hol.T_a, T_b` | **nicht vorhanden** | ✗ | | Möbius holonomy | `hol.T_a, T_b` | **not available** | ✗ |
### Normalisierungsabgleich ### Normalisation alignment
Der u-Vektor in conformallab++ hat einen Freiheitsgrad (globale additive Konstante The u-vector in conformallab++ has one degree of freedom (global additive constant —
Eichfreiheit nach Pin-Fixierung). geometry-central kann eine andere Konvention nutzen. gauge freedom after pin-fixing). geometry-central may use a different convention.
Vor dem Vergleich normalisieren: Normalise before comparing:
```cpp ```cpp
// conformallab++: u zentrieren // conformallab++: centre u
double mean_u = std::accumulate(x.begin(), x.end(), 0.0) / x.size(); double mean_u = std::accumulate(x.begin(), x.end(), 0.0) / x.size();
std::vector<double> x_norm(x.size()); std::vector<double> x_norm(x.size());
for (int i = 0; i < x.size(); ++i) x_norm[i] = x[i] - mean_u; for (int i = 0; i < x.size(); ++i) x_norm[i] = x[i] - mean_u;
// Dann mit geometry-central u-Vektor (ebenfalls zentriert) vergleichen: // Then compare with the geometry-central u-vector (also centred):
// max|x_norm[i] - gc_u[i]| < 1e-8 → identischer Konvergenzpunkt // max|x_norm[i] - gc_u[i]| < 1e-8 → identical convergence point
``` ```
### Wann ist der Vergleich sinnvoll? ### When is the comparison useful?
| Zeitpunkt | Was ist möglich | | Point in time | What is possible |
|---|---| |---|---|
| **Jetzt (Phase 7)** | Manueller Vergleich mit denselben `.off`/`.obj` Testnetzen | | **Now (Phase 7)** | Manual comparison using the same `.off`/`.obj` test meshes |
| **Nach Phase 8** | Automatisiertes Vergleichsskript (Python oder separates C++-Binary) | | **After Phase 8** | Automated comparison script (Python or separate C++ binary) |
| **Phase 10 (Forschung)** | Algorithmus-Vergleich: Newton vs. Ptolemäische Flips auf schwierigen Netzen | | **Phase 10 (research)** | Algorithm comparison: Newton vs. Ptolemaic flips on difficult meshes |
### Voraussetzungen für einen fairen Vergleich ### Connection to the literature
1. Identische Eingabenetze (OFF/OBJ, gleiche Vertex-Orientierung) The Springborn 2020 paper ("Ideal Hyperbolic Polyhedra and Discrete Uniformization")
2. Gleiche Gauss-Bonnet-Zielkrümmungen (Θᵥ = 2π für alle v, geschlossene Fläche) is **already implemented in conformallab++** — it is the mathematical foundation
3. u-Normalisierung abgeglichen (zentriert, gleiche Eichfixierung) for the HyperIdeal geometry mode (Phase 2/3). The geometry-central implementation
4. Konvergenztoleranz synchronisiert (max. Gradientnorm < 1e-8) is based on the extension by Gillespie, Springborn & Crane (2021), which uses the
same variational principle of BobenkoSpringborn 2004 but additionally applies
### Verbindung zur Literatur Ptolemaic flips to improve the triangulation during optimisation — an idea not yet
implemented in conformallab++ (→ GC-2 in the phase roadmap).
Das Springborn 2020-Papier ("Ideal Hyperbolic Polyhedra and Discrete Uniformization")
ist **in conformallab++ bereits implementiert** es ist die mathematische Grundlage
für den HyperIdeal-Geometriemodus (Phase 2/3). Die geometry-central Implementierung
basiert auf der Weiterentwicklung von Gillespie, Springborn & Crane (2021), die
denselben Variationsprinzip von BobenkoSpringborn 2004 verwendet, aber zusätzlich
Ptolemäische Flips einsetzt, um die Triangulierung während der Optimierung zu
verbessern eine Idee, die in conformallab++ noch nicht implementiert ist (→ GC-2
im Phasen-Roadmap).
--- ---

View File

@@ -97,58 +97,57 @@ Java features from `de.varylab.discreteconformal` not yet in C++:
## ◼ Optional / Hypothetical — geometry-central Cross-Comparison ## ◼ Optional / Hypothetical — geometry-central Cross-Comparison
> **Status: keine geplante Phase — rein explorativ.** > **Status: no planned phase — purely exploratory.**
> Diese Punkte sind keine Voraussetzung für Phase 810. Sie sind > These items are not prerequisites for Phase 810. They are
> interessant, weil geometry-central (Keenan Crane, CMU) auf denselben > of interest because geometry-central (Keenan Crane, CMU) is built on the same
> mathematischen Grundlagen wie conformallab++ aufbaut — insbesondere auf > mathematical foundations as conformallab++ — in particular
> **Springborn 2020** und der direkten Weiterentwicklung durch > **Springborn 2020** and its direct extension by
> **Gillespie, Springborn & Crane (SIGGRAPH 2021)**. > **Gillespie, Springborn & Crane (SIGGRAPH 2021)**.
> Der entscheidende Unterschied: geometry-central löst dasselbe Problem > The key difference: geometry-central solves the same problem
> (diskrete konforme Äquivalenz) mit **intrinsischen Triangulierungen + > (discrete conformal equivalence) using **intrinsic triangulations +
> Ptolemäischen Flips**, während conformallab++ **Newton auf der > Ptolemaic flips**, while conformallab++ applies **Newton on the
> Original-Triangulierung** anwendet. > original triangulation**.
``` ```
GC-1 [optional, jetzt möglich] GC-1 [optional, possible now]
Mathematischer Output-Vergleich Mathematical output comparison
gleiche Testnetze (cathead.obj, brezel.obj, torus_4x4.off) in load the same test meshes (cathead.obj, brezel.obj, torus_4x4.off) into
beide Bibliotheken laden both libraries
UV-Koordinaten, u-Vektor, Residualnorm vergleichen compare UV coordinates, u-vector, residual norm
Normalisierungskonventionen abgleichen (u-Mittelwert, Skalierung) align normalisation conventions (u mean, scaling)
Ziel: unabhängige Kreuz-Validierung der Konvergenzpunkte. Goal: independent cross-validation of convergence points.
Aufwand: kleines Python/C++ Vergleichsskript, kein Bibliotheks-Umbau. Effort: small Python/C++ comparison script, no library restructuring.
GC-2 [optional, sinnvoll nach Phase 8] GC-2 [optional, useful after Phase 8]
Intrinsic Delaunay Pre-Conditioning Intrinsic Delaunay pre-conditioning
Vor dem Newton-Solver: geometry-central SignpostIntrinsicTriangulation before the Newton solver: apply geometry-central SignpostIntrinsicTriangulation
auf die Eingabe anwenden to the input
→ Ptolemäische Flips konditionieren die Hessian-Matrix vor → Ptolemaic flips pre-condition the Hessian matrix
Hypothese: weniger Newton-Iterationen auf nicht-Delaunay-Eingaben hypothesis: fewer Newton iterations on non-Delaunay inputs
Implementierbar als optionaler cmake-Flag: -DWITH_GC_PRECOND=ON implementable as an optional cmake flag: -DWITH_GC_PRECOND=ON
Abhängigkeit: geometry-central als optionale externe Abhängigkeit Dependency: geometry-central as an optional external dependency
(header-only Teile genügen für den Flip-Algorithmus). (header-only parts suffice for the flip algorithm).
GC-3 [hypothetisch, Phase 10+ Forschung] GC-3 [hypothetical, Phase 10+ research]
Ptolemäische Flip-basierter Solver als alternativer Backend Ptolemaic flip-based solver as an alternative backend
Statt Newton: Ptolemäische Flips + penultimate-step Normalisierung instead of Newton: Ptolemaic flips + penultimate-step normalisation
(GillespieSpringbornCrane 2021 Algorithmus) (GillespieSpringbornCrane 2021 algorithm)
Vergleich: Konvergenzradius, Robustheit auf pathologischen Netzen, comparison: convergence radius, robustness on pathological meshes,
numerische Stabilität auf hohen Genus-Flächen numerical stability on high-genus surfaces
r conformallab++ interessant, weil der Newton-Ansatz auf relevant for conformallab++ because the Newton approach can become
stark nicht-Delaunay Netzen (z.B. nach Remeshing) instabil unstable on strongly non-Delaunay meshes (e.g. after remeshing).
werden kann. No implementation planned — conceptual note for Phase 10 research.
Keine Implementierung geplant — Konzeptnotiz für Phase 10-Forschung.
``` ```
**Verbindung zur Literatur:** **Connection to the literature:**
Das Springborn 2020-Papier ("Ideal Hyperbolic Polyhedra and Discrete The Springborn 2020 paper ("Ideal Hyperbolic Polyhedra and Discrete
Uniformization") ist in conformallab++ als HyperIdeal-Geometriemodus Uniformization") is already implemented in conformallab++ as the HyperIdeal
bereits implementiert (Phase 2/3). Die GillespieSpringbornCrane geometry mode (Phase 2/3). The GillespieSpringbornCrane
2021-Erweiterung — die geometry-central implementiert — ergänzt dies um 2021 extension — implemented in geometry-central — augments this with
intrinsische Triangulierungen und macht den Algorithmus robust gegen intrinsic triangulations and makes the algorithm robust against
schlechte Eingangs-Triangulierungen. Beide teilen denselben poor input triangulations. Both share the same
mathematischen Kern (diskrete konforme Äquivalenz, GaussBonnet, mathematical core (discrete conformal equivalence, GaussBonnet,
Variationsprinzip von BobenkoSpringborn 2004). variational principle of BobenkoSpringborn 2004).
--- ---