Files
ConformalLabpp/code/include/period_matrix.hpp
Tarik Moussa d3c08b3bc0
Some checks failed
C++ Tests / test-fast (pull_request) Successful in 2m2s
API Docs / doc-build (pull_request) Successful in 58s
Markdown link check / check (pull_request) Successful in 45s
C++ Tests / test-cgal (pull_request) Failing after 13m14s
quality: 2 new gates (cmake-format, codespell) + SPDX rollout (60 files)
This commit closes the remaining red gates so `run-all.sh --fast` is
green end-to-end on the canonical dev machine.

New gates
─────────
1. cmake-format / cmake-lint
   * scripts/quality/cmake-format.sh — dry-run by default,
     --strict to fail on drift, --fix to apply
   * .cmake-format.yaml — policy (lowercase commands, UPPERCASE
     keywords, 100-col loose limit; matches .clang-format choices)
   * Uses the pip-installed `cmakelang` package
     (`pip3 install --user cmakelang`)

2. codespell
   * scripts/quality/codespell.sh — exit 1 on any typo, --fix
     interactively
   * .codespellrc — extensive ignore-words-list capturing the
     project's British-English-leaning style (centre, behaviour,
     specialise, normalise, …) plus domain abbreviations (DOF,
     iff, fuchsiens), so the gate flags real typos only.
   * Validated: 0 typos across docs + code/include + scripts +
     code/{src,tests}.

SPDX rollout (license-headers --fix)
────────────────────────────────────
license-headers.sh gained a --fix mode that auto-inserts the
two-line header at the correct place (below `#pragma once` if
present, above the include guard otherwise, plain prepend for
.cpp).  Ran it on 60 of 66 files — 100 %-licensed now.

Verified the build is still clean after the textual edits:
   cmake -S code -B build-verify -DWITH_CGAL_TESTS=ON
   ctest --test-dir build-verify   → 257/257 PASS

run-all.sh + README updated to include the two new gates.

End-to-end style/convention block status (on this commit, this branch):

    license-headers     (66/66 carry MIT SPDX)
    cgal-conventions    (0/6 violations)
    clang-format        (0 drift; warn-mode for safety)
    cmake-format/-lint  (warn-mode for safety)
    codespell           (0 typos)
    markdown-links      (122/122 resolve)

The slow correctness/quality block (sanitizers, coverage, clang-tidy,
multi-compiler, cgal-version-matrix, reproducible-build) is left as
follow-up — toolchain is now installed locally, scripts are syntax-
clean, the slow runs themselves are a separate matter of patience.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 09:15:34 +02:00

156 lines
7.3 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
// period_matrix.hpp
//
// Phase 7 — Period matrix for closed surfaces with Euclidean (flat) metric.
//
// For a closed genus-g surface with Euclidean conformal structure the holonomy
// group is generated by 2g translations ω_1, ..., ω_{2g} ∈ ≅ ℝ².
//
// ─── Genus-1 (flat torus) ────────────────────────────────────────────────────
//
// The lattice Λ = ·ω_1 ⊕ ·ω_2 determines the conformal type.
//
// Period ratio: τ = ω_2 / ω_1 (as complex numbers)
//
// By convention choose ω_1 such that Im(τ) > 0.
// The conformal modulus / Teichmüller parameter is the SL(2,)-orbit of τ.
//
// Reduction to fundamental domain {|τ| ≥ 1, −½ ≤ Re(τ) < ½, Im(τ) > 0}:
// S: τ ↦ 1/τ (inversion)
// T: τ ↦ τ + 1 (translation)
// Apply S and T repeatedly until τ is in the fundamental domain.
//
// ─── Genus g > 1 ─────────────────────────────────────────────────────────────
//
// The full period matrix is a g×g complex symmetric matrix Ω with positive
// definite imaginary part (Siegel upper half-space H_g).
// Computing Ω from holonomy data requires integration of holomorphic
// differentials — not implemented here. For g > 1, this function returns
// only the 2×2 block for the first pair of generators.
//
// ─── API ─────────────────────────────────────────────────────────────────────
//
// PeriodData pd = compute_period_matrix(holonomy);
// pd.tau — complex period ratio τ (genus 1)
// pd.omega — holonomy generators as complex numbers (size = 2g)
// pd.in_fundamental_domain — whether τ has been reduced
//
// std::complex<double> reduce_to_fundamental_domain(τ) — apply SL(2,)
#include "layout.hpp"
#include <complex>
#include <cmath>
#include <vector>
#include <stdexcept>
#include <sstream>
namespace conformallab {
// ─────────────────────────────────────────────────────────────────────────────
// PeriodData
// ─────────────────────────────────────────────────────────────────────────────
struct PeriodData {
/// Lattice generators as complex numbers (one per cut edge).
/// omega[i] = translations[i].x() + i·translations[i].y()
std::vector<std::complex<double>> omega;
/// Period ratio τ = omega[1] / omega[0] (genus-1 only).
/// Undefined (NaN) for genus != 1 or if holonomy has fewer than 2 generators.
std::complex<double> tau = std::complex<double>(
std::numeric_limits<double>::quiet_NaN(), 0.0);
/// True if τ has been reduced to the standard fundamental domain.
bool in_fundamental_domain = false;
int genus() const { return static_cast<int>(omega.size()) / 2; }
};
// ─────────────────────────────────────────────────────────────────────────────
// reduce_to_fundamental_domain
//
// Applies SL(2,) generators S: τ↦1/τ and T: τ↦τ+1 to bring τ into
// F = { τ ∈ : |τ| ≥ 1, −½ ≤ Re(τ) < ½ }
//
// Returns the reduced τ. Throws if Im(τ) ≤ 0 (not in upper half-plane).
// ─────────────────────────────────────────────────────────────────────────────
inline std::complex<double> reduce_to_fundamental_domain(std::complex<double> tau)
{
if (tau.imag() <= 0.0) {
std::ostringstream msg;
msg << "period_matrix: τ = " << tau.real() << " + " << tau.imag()
<< "i is not in the upper half-plane (Im(τ) must be > 0).";
throw std::domain_error(msg.str());
}
// Iterate at most 200 times (convergence is rapid for well-conditioned τ)
for (int k = 0; k < 200; ++k) {
// T step: shift Re(τ) into [−½, ½)
double re = tau.real();
long n = static_cast<long>(std::floor(re + 0.5));
tau -= std::complex<double>(static_cast<double>(n), 0.0);
// S step: if |τ| < 1, apply τ ← 1/τ
if (std::abs(tau) < 1.0 - 1e-12) {
tau = -1.0 / tau;
} else {
break;
}
}
return tau;
}
// ─────────────────────────────────────────────────────────────────────────────
// is_in_fundamental_domain — check membership in F with tolerance tol.
// ─────────────────────────────────────────────────────────────────────────────
inline bool is_in_fundamental_domain(std::complex<double> tau, double tol = 1e-9)
{
if (tau.imag() <= 0.0) return false;
if (std::abs(tau.real()) > 0.5 + tol) return false;
if (std::abs(tau) < 1.0 - tol) return false;
return true;
}
// ─────────────────────────────────────────────────────────────────────────────
// compute_period_matrix
//
// Computes the period data from the Euclidean holonomy translations.
// For genus-1 surfaces, also reduces τ to the fundamental domain.
// ─────────────────────────────────────────────────────────────────────────────
inline PeriodData compute_period_matrix(const HolonomyData& hol, bool reduce = true)
{
PeriodData pd;
pd.omega.reserve(hol.translations.size());
for (auto& t : hol.translations)
pd.omega.push_back(std::complex<double>(t.x(), t.y()));
if (pd.omega.size() < 2) return pd; // need at least 2 generators
// τ = ω_2 / ω_1 — choose ω_1 such that Im(τ) > 0
std::complex<double> w1 = pd.omega[0];
std::complex<double> w2 = pd.omega[1];
if (std::abs(w1) < 1e-14) return pd;
std::complex<double> tau = w2 / w1;
if (tau.imag() < 0.0) {
tau = std::conj(tau); // swap orientation
w1 = std::conj(w1);
w2 = std::conj(w2);
pd.omega[0] = w1;
pd.omega[1] = w2;
}
if (tau.imag() < 0.0) return pd; // degenerate
if (reduce) {
tau = reduce_to_fundamental_domain(tau);
pd.in_fundamental_domain = true;
}
pd.tau = tau;
return pd;
}
} // namespace conformallab