Files
ConformalLabpp/code/include/CGAL/Discrete_inversive_distance.h
Tarik Moussa 7d10500811
Some checks failed
C++ Tests / test-fast (pull_request) Successful in 2m7s
C++ Tests / test-fast (push) Successful in 2m19s
C++ Tests / test-cgal (pull_request) Failing after 11m6s
C++ Tests / test-cgal (push) Has been skipped
API Docs / doc-build (pull_request) Successful in 41s
Phase 8b-Lite: CGAL entries for all 5 DCE models + layout wrapper
Completes the CGAL public API surface so all five discrete-conformal
functionals are reachable from <CGAL/Discrete_*.h>, not only Euclidean.

CGAL test count: 219 → 227 (+8).  Zero skips.

New public headers
──────────────────
* CGAL/Discrete_conformal_map.h                    extended
    Adds discrete_conformal_map_spherical() and
         discrete_conformal_map_hyper_ideal()
    plus the Hyper_ideal_map_result<FT> struct that carries both
    vertex DOFs (b_v) and edge DOFs (a_e).

* CGAL/Discrete_circle_packing.h                   new (180 lines)
    Face-based BPS-2010 circle packing.  Provides
         Default_cp_euclidean_traits<Mesh, K>
         Circle_packing_result<FT>
         discrete_circle_packing_euclidean()

* CGAL/Discrete_inversive_distance.h               new (180 lines)
    Vertex-based Luo-2004 packing.  Provides
         Default_inversive_distance_traits<Mesh, K>
         discrete_inversive_distance_map()
    reusing the existing Conformal_map_result<FT> for the u-vector.

* CGAL/Conformal_layout.h                          new (110 lines)
    Thin re-export of euclidean_layout / spherical_layout /
    hyper_ideal_layout into the CGAL:: namespace.

Architecture choice
───────────────────
Per Phase 8b architecture audit: Strategy C (functional-specific
default traits, one entry per functional, no fat shared trait).
Documented in each header's docblock.  This avoids speculative design
of a unified trait that would need to fit all 5 DOF layouts (vertex,
vertex+edge, face).

Conformal_map_traits.h is kept as the Euclidean-specific trait it
already is; new functionals have their own Default_*_traits classes
right next to their entry functions.

Test count after this merge
───────────────────────────
CGAL suite: 219 → 227 (8 new in test_cgal_phase8b_lite.cpp covering
all four new entries + the Euclidean+layout round-trip).

After-the-merge user contract
─────────────────────────────
A user can now write any of these and get a valid Newton-converged result:

  #include <CGAL/Discrete_conformal_map.h>
  auto r = CGAL::discrete_conformal_map_euclidean(mesh);
  auto r = CGAL::discrete_conformal_map_spherical(mesh);
  auto r = CGAL::discrete_conformal_map_hyper_ideal(mesh);

  #include <CGAL/Discrete_circle_packing.h>
  auto r = CGAL::discrete_circle_packing_euclidean(mesh);

  #include <CGAL/Discrete_inversive_distance.h>
  auto r = CGAL::discrete_inversive_distance_map(mesh);

  #include <CGAL/Conformal_layout.h>
  auto layout = CGAL::euclidean_layout(mesh, r.x, maps);

Not in this PR (intentionally deferred)
───────────────────────────────────────
* 8a.2 — Generic FaceGraph specialisation (still Surface_mesh-only).
* 8c   — User_manual + PackageDescription.txt (CGAL-submission prep).
* 8d   — CGAL-format test directory  (CGAL-submission prep).
* 8e   — YAML pipeline + CLI flag    (orthogonal).
* Named-parameter chaining (`a.b().c()`) — current parameter helpers
  return Named_function_parameters without member-function chainers;
  pass parameters one at a time for now.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 21:24:26 +02:00

192 lines
7.6 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.

// Copyright (c) 2024-2026 Tarik Moussa.
// SPDX-License-Identifier: MIT
//
// Package: conformallab++ / Discrete_conformal_map (Phase 8b-Lite, 2026-05-21)
/*!
\file CGAL/Discrete_inversive_distance.h
\ingroup PkgConformalMapRef
User-facing entry for the **vertex-based** inversive-distance circle-
packing functional of Luo (2004), with the Bowers-Stephenson (2004)
initialisation. See `inversive_distance_functional.hpp` for the
underlying algorithm and `doc/roadmap/research-track.md` (item 9a.2)
for the research-track classification — this functional has **no Java
original** (verified empirically), it is from-the-literature research.
DOF structure
─────────────
* Per-vertex `u_i = log r_i` (compatible with the classical Euclidean
trait).
* Per-edge constant `I_ij` computed once by Bowers-Stephenson from the
input mesh geometry (handled internally by
`compute_inversive_distance_init_from_mesh`).
Because the per-edge constant has a different meaning from the
Euclidean `λ°_e`, this entry has its own default-trait class
`Default_inversive_distance_traits`.
*/
#ifndef CGAL_DISCRETE_INVERSIVE_DISTANCE_H
#define CGAL_DISCRETE_INVERSIVE_DISTANCE_H
#include <CGAL/Conformal_map/internal/parameters.h>
#include <CGAL/Kernel_traits.h>
#include <CGAL/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Simple_cartesian.h>
#include <boost/graph/graph_traits.hpp>
#include <CGAL/Discrete_conformal_map.h> // for Conformal_map_result<FT>
#include "../inversive_distance_functional.hpp"
#include "../newton_solver.hpp"
namespace CGAL {
// ── Default traits for Inversive-Distance ────────────────────────────────────
template <typename TriangleMesh,
typename Kernel_ = CGAL::Simple_cartesian<double>>
struct Default_inversive_distance_traits;
template <typename K>
struct Default_inversive_distance_traits<CGAL::Surface_mesh<typename K::Point_3>, K>
{
using Kernel = K;
using FT = typename K::FT;
using Point_3 = typename K::Point_3;
using Triangle_mesh = CGAL::Surface_mesh<Point_3>;
using Vertex_descriptor = typename boost::graph_traits<Triangle_mesh>::vertex_descriptor;
using Edge_descriptor = typename boost::graph_traits<Triangle_mesh>::edge_descriptor;
// Inversive-distance specific property maps.
using Vertex_index_pmap = typename Triangle_mesh::template Property_map<Vertex_descriptor, int>;
using Theta_v_pmap = typename Triangle_mesh::template Property_map<Vertex_descriptor, FT>;
using R0_pmap = typename Triangle_mesh::template Property_map<Vertex_descriptor, FT>;
using I_e_pmap = typename Triangle_mesh::template Property_map<Edge_descriptor, FT>;
};
// ── Entry function ────────────────────────────────────────────────────────────
/*!
\ingroup PkgConformalMapRef
Compute the Luo-2004 vertex-based inversive-distance circle packing of `mesh`.
The per-edge constant `I_ij` is computed once at the start from the input
3-D geometry via the Bowers-Stephenson identity
`I_ij = (_ij² r_i² r_j²) / (2 r_i r_j)`,
with `r_i^(0) = (1/3) min{_e : e adj v_i}` as the default initial radii.
The user can override the initial radii by writing into the `r0`
property map before calling this function.
\tparam TriangleMesh A `CGAL::Surface_mesh<P>`.
\tparam NamedParameters Optional CGAL named-parameter pack.
\param mesh Input triangle mesh.
\param np Named parameters:
- `vertex_curvature_map(pmap)` — per-vertex Θ_v target.
- `fixed_vertex_map(pmap)` — pinning override.
- `gradient_tolerance(ε)` — Newton stop.
- `max_iterations(n)` — Newton iteration cap.
\returns A `Conformal_map_result<FT>` with `u_per_vertex[v] = log r_v`
(the converged log-radius at each vertex).
\pre `mesh` is a triangle mesh with positive edge lengths.
\pre The user-supplied or natural-theta Θ satisfies GaussBonnet.
\note Convergence is sensitive to the initial point and to extreme
`I_ij` values. For testing purposes the natural-theta default
(Θ_v shifted so that u = 0 is the equilibrium) always converges
in zero iterations.
*/
template <typename TriangleMesh,
typename CGAL_NP_TEMPLATE_PARAMETERS>
auto discrete_inversive_distance_map(
TriangleMesh& mesh,
const CGAL_NP_CLASS& np = parameters::default_values())
{
using Point_type = typename TriangleMesh::Point;
using Default_kernel = typename CGAL::Kernel_traits<Point_type>::Kernel;
using Default_traits = Default_inversive_distance_traits<TriangleMesh, Default_kernel>;
using Traits = typename internal_np::Lookup_named_param_def<
internal_np::geom_traits_t,
CGAL_NP_CLASS,
Default_traits>::type;
using FT = typename Traits::FT;
Conformal_map_result<FT> result;
auto maps = ::conformallab::setup_inversive_distance_maps(mesh);
::conformallab::compute_inversive_distance_init_from_mesh(mesh, maps);
auto theta_param = parameters::get_parameter(
np, Conformal_map::internal_np::vertex_curvature_map);
constexpr bool has_theta = !std::is_same_v<
decltype(theta_param), internal_np::Param_not_found>;
if constexpr (has_theta) {
for (auto v : mesh.vertices())
maps.theta_v[v] = get(theta_param, v);
}
// Pin first vertex by default; user can override with fixed_vertex_map.
constexpr int FREE = 0;
for (auto v : mesh.vertices()) maps.v_idx[v] = FREE;
auto pin_param = parameters::get_parameter(
np, Conformal_map::internal_np::fixed_vertex_map);
constexpr bool has_pin = !std::is_same_v<
decltype(pin_param), internal_np::Param_not_found>;
bool any_pinned = false;
if constexpr (has_pin) {
for (auto v : mesh.vertices())
if (get(pin_param, v)) { maps.v_idx[v] = -1; any_pinned = true; }
}
if (!any_pinned) {
auto it = mesh.vertices().begin();
if (it != mesh.vertices().end()) { maps.v_idx[*it] = -1; any_pinned = true; }
}
int idx = 0;
for (auto v : mesh.vertices())
if (maps.v_idx[v] != -1) maps.v_idx[v] = idx++;
const FT tol = parameters::choose_parameter(
parameters::get_parameter(np, Conformal_map::internal_np::gradient_tolerance),
FT(1e-10));
const int max_iter = parameters::choose_parameter(
parameters::get_parameter(np, Conformal_map::internal_np::max_iterations),
200);
// Natural-theta default.
std::vector<double> x0(static_cast<std::size_t>(idx), 0.0);
if constexpr (!has_theta) {
auto G0 = ::conformallab::inversive_distance_gradient(mesh, x0, maps);
for (auto v : mesh.vertices()) {
const int j = maps.v_idx[v];
if (j >= 0) maps.theta_v[v] -= G0[static_cast<std::size_t>(j)];
}
}
auto nr = ::conformallab::newton_inversive_distance(mesh, x0, maps, tol, max_iter);
result.u_per_vertex.assign(num_vertices(mesh), FT(0));
for (auto v : mesh.vertices()) {
const int j = maps.v_idx[v];
if (j >= 0) result.u_per_vertex[v.idx()] = nr.x[static_cast<std::size_t>(j)];
}
result.iterations = nr.iterations;
result.gradient_norm = nr.grad_inf_norm;
result.converged = nr.converged;
return result;
}
} // namespace CGAL
#endif // CGAL_DISCRETE_INVERSIVE_DISTANCE_H