Skip to content

Extremum Sum Potential - #259

Open
fsichetti wants to merge 304 commits into
ipc-sim:mainfrom
fsichetti:upstream-merge
Open

fsichetti wants to merge 304 commits into
ipc-sim:mainfrom
fsichetti:upstream-merge

Conversation

@fsichetti

@fsichetti fsichetti commented Sep 9, 2026 •

Copy link
Copy Markdown

Description

Adds the Extremum Sum Proximity Potential (ESP) as a new ipc/esp/ module, with supporting changes to barriers, candidates, collision meshes, friction and distance-type classification; also renames smooth_contact to gcp with appropriate compatibility aliases.

Mechanism

  • ESPParameters: dhat, the near/far split parameter dbar_factor in [0, 2], the barrier (default NormalizedClampedLogBarrier), the 2D quad_order, an optional 3D face quadrature rule, area_weights and integration_type.
  • ESPCollisions: builds the quadrature collision set from broad-phase candidates.
  • ESPPotential: value, gradient and Hessian, with PSD projection.
    • 2D integrates over edges at Gauss-Lobatto points.
    • 3D integrates over faces (at their vertices or at face quadrature points) and at the closest points of nearby edge-edge pairs.
    • With use_near_far = true (the default), the 3D quadrature weights are normalized to sum to 1, split into near and far parts (NearFarBarrier) when dbar_factor > 0.
  • ArbitraryPointESP<dim>: value, gradient and Hessian of the potential at an arbitrary point, with inclusion-exclusion weights for open and codimensional input.
  • Friction: a TangentialCollisions::build overload that takes ESPCollisions; the existing FrictionPotential consumes the result.
  • Exact distance-type classifiers (point_edge_distance_type_exact, point_triangle_distance_type_exact, edge_edge_distance_type_exact) built on three shared sign predicates, so their classifications agree.

Impact

  • New optional dependency, geogram, on by default.
  • Breaking: the smooth_contact → gcp rename removes most old header paths (see below).
  • Additions to existing classes are defaulted, so existing calls compile unchanged.
  • Python exposes ESPParameters (without the barrier and face-rule settings), ESPCollisions, ESPPotential, the GCP* names and Candidates.build(..., all_types=False); the rest of the new API is C++ only.

API changes

New public types outside ipc/esp/

  • InversePowerBarrier(p): b(d) = h(d) / d^p, where h is a cubic B-spline that vanishes at dhat.
  • NearFarBarrier(base, alpha): splits a barrier into a near and a far part, with a smooth step between alpha * dhat / 2 and alpha * dhat.
  • DistanceTypeConfig, the *_distance_type_exact functions and is_parallel_edge_edge.

Additions to existing classes

  • Candidates::build(..., all_types = false) and BroadPhase::detect_collision_candidates(..., all_types = false): in 3D, all_types also collects vertex-vertex, edge-vertex, edge-face and face-face candidates, for ESP only.
  • Candidates: new ef_candidates, ff_candidates and adjacency sets (convert_candidates_to_sets(), vv_set() … ff_set()).
  • CollisionMesh: a constructor overload with an obstacle_vertex mask; is_obstacle_vertex/edge/face(); face_areas().
  • NormalCollisions::set_skip_obstacles(bool), default false.
  • BarrierPotential(..., use_squared_distance = true): false evaluates the barrier on the unsquared distance, where force_magnitude() throws.
  • CollisionStencil::name().

💥 Breaking changes

  • ipc/smooth_contact/ is renamed to ipc/gcp/, and its classes to GCP*.
    • Deprecated aliases keep the old class names compiling: SmoothCollision, SmoothCollisionTemplate, SmoothCollisions, SmoothCollisionsBuilder, SmoothContactParameters, SmoothContactPotential.
    • TangentialPotential::smooth_contact_force* is renamed to gcp_force*, with deprecated forwarders.
    • Python keeps the old names as aliases of the new classes, without a warning.
    • Only smooth_contact/smooth_collisions.hpp and smooth_contact/smooth_contact_potential.hpp remain, as forwarding headers; the other 15 old headers (and two .tpp files) are gone.
    • TangentialCollision::smooth_collision is renamed to gcp_collision, without an alias.
  • Code that takes the address of Candidates::build or BroadPhase::detect_collision_candidates with the old parameter list (e.g. py::overload_cast) must add the bool.

Known limitations

  • ESP friction does not support gcp_force_jacobian, always area-weights, and ignores the InversePowerBarrier power that ESPPotential applies to edge-edge weights.
  • Python cannot mark obstacle vertices, so integration_type has no effect there.

New dependencies

  • geogram 1.10.1: BSD-3-Clause, fetched with CPM, behind IPC_TOOLKIT_WITH_GEOGRAM (default ON).
    • Linked PRIVATE and used only in distance_type_exact.cpp; no public header includes it.
    • Library-only build (graphics, Lua, TetGen, Triangle and HLBFGS off); its own warnings are silenced on GCC and Clang.
    • geogram's default platform settings build it as a shared library on Linux and macOS, so executables and the ipctk module there need libgeogram at run time.
    • On Linux, geogram also links tbb, and OpenMP when built with GCC.
    • Wheels rely on cibuildwheel's repair step to bundle libgeogram; no PR check builds a wheel.
    • With the option OFF, the classifiers use floating-point predicates and log a warning once; no CI job builds this configuration.

Other changes

  • Fixes MSVC builds on AVX2 machines being compiled for AVX: FindSIMD.cmake no longer emits both /arch:AVX2 and /arch:AVX.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which improves existing functionality)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

New test files

  • test_esp_potential.cpp (30 cases, one hidden): FD derivatives, PSD projection, zero potential on closed meshes, bounded edge-edge terms near edge endpoints, and continuity in dbar_factor.
  • test_arbitrary_point_esp.cpp (8 cases): zero beyond dhat, FD derivatives, and inclusion-exclusion weights on open input.
  • test_obstacles.cpp (5 cases): obstacle flags, obstacle skipping in NormalCollisions, and ESP integration types.
  • test_vertex_matrix_view.cpp (5 cases).

New cases in existing files

  • test_distance_type.cpp (+6): four random tests (10,000 samples each, geogram builds only) compare the classifiers with an exact geogram reference; two check mutual consistency and the use_standard fallback.
  • test_force_jacobian.cpp (+4): ESP friction force against −∇D and friction Hessian against finite differences, in 2D and 3D.
  • test_candidates.cpp (+1, adjacency sets), test_barrier_potential.cpp (+1, unsquared distance), test_barrier.cpp (InversePowerBarrier sections).
  • Python: TestESPPotential and TestGCPAliases.

Reproduce

  • cmake --preset=test && cmake --build --preset=test -j 8 && cd build/test && ctest
  • pip install . && pytest python/tests/test_potentials.py

CI on 03c39d2

  • All 18 checks pass; patch coverage 96.57%.
  • Debug CTest time: 1010 s on Windows, 817 s on Linux, 869 s on macOS.
  • Slowest test: "ESP potential 2D finite differences", 885 s on Windows Debug (59% of the 1500 s timeout).

Test Configuration:

  • OS and Version: macOS 15.5 (24F74), arm64
  • Compiler and Version: Apple clang 17.0.0 (clang-1700.0.13.5)
  • CMake 4.2.3, IPC_TOOLKIT_WITH_GEOGRAM=ON, IPC_TOOLKIT_WITH_CUDA=OFF

Checklist

  • I have followed the project style guide
  • My code follows the clang-format style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

claude added 6 commits October 8, 2026 14:41
The unsquared branches of operator(), gradient() and hessian() returned
the barrier without the stiffness factor, which upstream moved into
BarrierPotential. They now scale by stiffness() as the squared branches
do. The test runs with stiffness 1 and 3.5, with and without the physical
barrier, and checks that force_magnitude_gradient() throws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
The continuous overload accepted all_types but never passed it to the
broad phase. Only the discrete overload, which ESP uses, needs it. The
signature and its Python binding match upstream again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
Fixes:
- The ESP TangentialCollisions::build never reset the lagged friction
  coefficients, which default to 0, so ESP friction was identically zero.
  It now calls reset_lagged_anisotropic_friction_coefficients() like the
  other overloads, and the friction test requires a nonzero force.
- ESPPotential::hessian returned a 0x0 matrix on a 3D mesh without faces.
- ESPCollisions::to_string read the face quadrature point, a virtual
  vertex, out of bounds. Face collisions now print ids and weights only.

Dead code (each removal verified against its callers):
- Edge-edge guards for non-EA_EB types and shared vertices, which the
  builder already filters out, become asserts. The ESP edge-edge distance
  helpers keep only the EA_EB case.
- Zero-weight fallbacks of the near/far normalization, the dbar_factor = 0
  edge-edge terms (the edge-edge pairs are skipped there), skips that
  ee_set/ef_set already guarantee, the obstacle-edge candidate check that
  was always true, the deep copies of the TBB exemplar and the unused copy
  assignment of QuadratureCollisionsBuilder, the uninstantiated
  Edge3P1-Edge3P1 distance and the ESPCollision near/far default bodies
  (now pure virtual).
- The 20-point Gauss-Lobatto rule now uses its table.

Tests: ESP friction with edge-edge contact, two cubes and zero stiffness;
obstacle integration types with a face rule, in 2D and with dynamic
vertex-vertex candidates; dbar_factor = 0; a 3D mesh without faces; 2D
Hessian projection; an edge-midpoint face rule; collision accessors and
stencils; distance helpers; candidate adjacency sets; and error paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
Rename the ESPParameters constructor parameter dbar_factor_value, which
shadowed the member of the same name, to _dbar_factor like the other
parameters, and use static_cast in ESPCollisionTemplate::vertex_id.

Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
Remove PointPotential::build_collisions_at_face_center and the gradient
and Hessian at the face center, with and without the near/far split.
Nothing in the library or the Python bindings called them; ESPPotential
uses the face-interior-point versions. The test that only checked them
against each other goes with them. The two value functions stay: they
evaluate the potential at every face quadrature point.

Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
evaluate_potential_at_face_center_with_cached_collisions and its _nearfar
version evaluate at any face quadrature point, not only the center. They
now match the gradient, Hessian and build functions, which already use
face_interior_point.

Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
@fsichetti
fsichetti marked this pull request as ready for review October 9, 2026 15:53
fsichetti and others added 3 commits October 9, 2026 11:54
The PSD check ran inside "ESP potential 2D finite differences" for every
configuration, including the 880-DOF mesh, and pushed that test past the
1500 s CTest limit in the Windows Debug job (it passed in 1115 s before).
It now runs on the two small configurations, in under a second.

Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
zfergus and others added 17 commits October 12, 2026 18:44
The test timed out (1500 s) in the Windows Debug job in 4 of the last 5
runs; Linux and macOS Debug take about 850 s. Orders 1 and 14 keep the
two extremes (endpoints only, and the densest rule with points near the
endpoints); orders 2 and 7 stay covered by the 2D no-forces and Hessian
PSD tests. The test takes 59% of its previous time in a local Debug
build.
These new tests ran several times over one code path and made the Debug
CI jobs three times longer than on main.

- ESP potential 2D no forces: quadrature orders 1 and 14, and circles up
  to 200 vertices. The 2000-vertex circle took 97% of the test.
- Convergent Quadrature Gradient and Hessian: dbar_factor 0.3, 1.0 and
  2.0. dhat = 0.15 / min(dbar_factor, 1) keeps the edge-edge support at
  0.15; dividing by dbar_factor left no collisions at 2.0.
- Face Quadrature Gradient and Hessian: drop the interior-degree-2 rule,
  whose interior points the centroid of vertices+centroid already covers.
- Convergent Quadrature Hessian PSD: dbar_factor 0.4 only, since every
  value in (0, 2] takes the same projection path.
- Face Quadrature Hessian PSD: one rule, and dbar_factor 0.4 and 0. Zero
  is the per-stencil projection branch.
- Random distance-type tests: 10,000 samples instead of 100,000. The
  rarest type (edge-edge vertex-vertex, 2.5%) still appears about 250
  times per run.
- Prefix the non-public members of ESPPotential, ArbitraryPointESP,
  ArbitraryPointBVH, ESPCollisionDict and ESPCollisionTemplate with m_.
- Rename the getters ESPParameters::get_quad_rule() to quad_rule() and
  ESPPotential::get_use_near_far() to use_near_far().
- Make ESPParameters' dbar_factor private behind dbar_factor().
- closest_point_uv() takes ea0, ea1, eb0 and eb1.
- Document the ESPPotential and ESPCollisions constructors and maps().
- Remove the unused Vertex2ogc primitive, and the minimum-distance
  tracker that every collision evaluation updated with an atomic
  compare-and-swap but nothing read.
- Cast the six vertex-count asserts that MSVC flagged as signed/unsigned
  comparisons (C4389).
- Geogram compiles its sources with -Weverything on clang, which added
  about 3,000 warnings to the macOS builds. Build it with -w.
- Clear the link directory geogram adds from its source and relative
  build dirs. Under CPM that path does not exist, and the macOS linker
  warned about it.
- Download the two-file PSM zip through CPM and delete its CGAL example
- Rename its namespace to ipc_geogram so it can coexist with a downstream
  geogram (e.g., PolyFEM), and fail configure if any GEO token survives
- Always build the exact predicates: remove IPC_TOOLKIT_WITH_GEOGRAM and
  the floating-point fallback
- Add ExactVector3 to replace geogram's vec3E
- List geogram as a required dependency and regenerate the dependency graph

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Update the lobatto_set and lobatto_compute notices to match
  Burkardt's current MIT-licensed quad_rule release, citing the source
- Port the 2014 lobatto_compute convergence check
- Credit quad_rule in the dependencies docs
- Move sphere.obj and wrapped_sphere.obj to ipc-toolkit-tests-data and
  bump its GIT_TAG
- Fail the test when a mesh cannot be read instead of crashing on an
  empty mesh
- Emit a compiler warning when including the smooth_contact forwarding
  headers
- Raise a DeprecationWarning for the old Smooth* names in Python
  through a module-level __getattr__

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Define GEO_STATIC_LIBS so the header stops emitting dllimport
- Remove trailing whitespace flagged by clang-format
- Apply the skip to the IMPROVED_MAX_APPROX corrections, vertex-vertex
  candidates, and plane-vertex pairs so net weights stay non-negative
- Weight edge-edge and vertex-vertex pairs with one obstacle by the
  non-obstacle primitive's area only, instead of halving the sum

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Record the quadrature rule in ESPCollisions at build time; ESPPotential
  and the ESP friction build throw on a mismatch instead of reading out
  of bounds
- Reject non-positive or non-finite dhat and negative quad_order in
  ESPParameters, and quad_order < 1 in 2D builds
- Reject non-positive or non-finite InversePowerBarrier powers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants