Skip to content

Extremum Sum Potential - #259

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

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

Conversation

@fsichetti

@fsichetti fsichetti commented Sep 9, 2026 •

Copy link
Copy Markdown

Description

Implementation of the SigAsia paper "Extremum Sum Proximity Potential".

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How Has This Been Tested?

Built and ran the test suite locally on macOS/arm64.
This branch adds the following tests, all passing locally:

New test files

  • tests/src/tests/potential/test_esp_potential.cpp (19 cases, [esp_potential])
    ESP potential in 2D and 3D: convergent-quadrature edge-edge limits under
    three barriers, gradient/Hessian finite differences, Hessian PSD-ness,
    codimensional collisions, adaptive support, NearFarBarrier decomposition.
  • tests/src/tests/potential/test_arbitrary_point_esp.cpp (6 cases,
    [arbitrary_point_esp]) Evaluating the ESP at an arbitrary off-mesh point
    in 2D/3D: zero beyond dhat, FD gradient/Hessian, and that evaluate()
    agrees with operator()/gradient()/hessian().
  • tests/src/tests/potential/test_smooth_clamp.cpp (12 cases,
    [smooth_clamp]) smooth_clamp01 and smooth_clamp_simplex: anchor
    values, saturation, monotonicity, C0/C1 continuity at knots, FD-vs-analytic
    derivatives, and simplex partition-of-unity.
  • tests/src/tests/utils/test_vertex_matrix_view.cpp (5 cases,
    [vertex_matrix_view]) Non-owning two-matrix vertex view: concatenation
    against a naive vstack, aliasing semantics, empty-matrix edge case.

New cases in existing files

  • test_distance_type.cpp: four randomized tests comparing the analytic
    distance-type classifiers against a geogram exact-predicate reference
    (new distance_type_reference.hpp), including the near-parallel edge-edge case.
  • test_force_jacobian.cpp: ESP friction force-Jacobian in 2D and 3D
    ([friction-esp]).
  • test_barrier.cpp: log barrier derivatives.

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

fsichetti and others added 21 commits September 16, 2026 11:03
test_avx_availability() emitted /arch:AVX2 and /arch:AVX from two
independent ifs, so a host detecting both got "/arch:AVX2 /arch:AVX".
/arch: is single-valued on MSVC and the last one wins, so every AVX2-capable
Windows build was compiled as plain AVX. CI reported this as
"SIMD support found: /arch:AVX2;/arch:AVX".

This is not only a performance loss. MSVC has no separate FMA flag -- the
FMA probe adds -mfma only on the GCC/Clang branch -- so /arch:AVX2 is the
only way MSVC enables fused multiply-add. Dropping to /arch:AVX removes FMA,
which changes rounding: a*b+c rounds twice instead of once, so results differ
from the AVX2 build rather than merely being computed more slowly.

Use if/elseif so AVX2 wins when detected, matching the AVX_VERSION selection
a few lines above, which already does this. AVX-only hosts still get
/arch:AVX and hosts with neither still get no flag.

Co-Authored-By: Claude Opus 5 <[email protected]>
Brings in the GPU LBVH broad phase (ipc-sim#260), its profiling (ipc-sim#263), a
scalable_ccd pin bump (ipc-sim#261), and a docs/output fix (ipc-sim#264).

One conflict, in BroadPhase::detect_collision_candidates: upstream documented
that the candidates are cleared first, while this branch had added the
all_types parameter that ESP contact needs. The merged implementation already
contains both -- candidates.clear() and the all_types branch -- so the
declaration keeps our signature with upstream's wording, and documents
all_types, which had no @PARAM entry before.

Co-Authored-By: Claude Opus 5 <[email protected]>
Each predicate wrapper contained its exact-arithmetic fallback, which
kept it from being inlined, so every classification made one
out-of-line call per filter (~8-10) plus nested public point-edge calls.

- Move the exact fallbacks out of line so the filtered fast path of
  every predicate inlines into the classifier.
- Pass raw coordinate pointers and the dimension as a template
  parameter.
- Check DistanceTypeConfig and initialize geogram's PCK once per
  classification instead of once per nested point-edge test.
- Fix the trace message of the cross_dot_cross_2 fallback.

Same filters in the same order; classifications are bitwise identical.
Exact classification is 1.5-2x faster on real scenes (e.g., cloth-ball
edge-edge 80 -> 52 ns), with no measurable change at the ESP level.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ocation

Per-query allocation (a make_shared per collision, a hash map, a collision
dict with its own std::set/std::map, full-stencil gradient and Hessian
arrays) was ~30% of the run time of wildmeshing's smooth offset, which
evaluates this potential millions of times per run.

- Collisions are kept by value in per-thread lists, one per collision type
  (the types ESPCollisionDict stores), cleared between queries.
- The symbolic cancellation is unchanged: same sub-feature reduction, same
  distance-type classification, same integer-weight merge by typed hash,
  done by linear search over the few collisions of one type.
- Only the query vertex's block of each collision's gradient and Hessian is
  accumulated, instead of assembling the whole stencil and extracting it.

Measured on the previous base (3a76d75, before the rename): value,
gradient and Hessian agree with the old code to 3.6e-16 relative
(summation order), no NaN/inf, including points 1e-9 delta above shared
edges; ~2.4x faster per call; wildmeshing's cube offset run 456 s -> 337 s
with a bit-identical output mesh. On this branch: the six
[arbitrary_point_esp] tests pass; [esp_potential] fails the same 17
assertions with and without this commit (test data not downloaded here).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
ESP sums b(distance to each element) with one weight per element. The
weights were fixed at the closed-mesh signs (3D: faces +1, edges -1,
vertices +1; 2D: edges +1, vertices -1), which are right only for a closed
manifold. On an open sheet the potential was exactly 0 beyond a boundary
edge, and an edge in no face or a 2D isolated vertex entered as a negative
barrier.

The weights now follow the counting rule of the ESP supplemental (S2): for
every point of the input, the weights of the elements containing it sum to
one. They are computed once from the connectivity:
- face: 1
- edge: 1 - (faces on it)
- vertex: 1 - (edges at it) + (faces at it)

This gives:
- closed mesh: +1/-1/+1 as before, so results there are unchanged
- boundary edge or vertex of an open sheet, end of an open polyline: 0 (S4)
- edge in no face: 1; vertex between two such edges: -1
- isolated vertex: 1
- edge shared by k faces, 2D vertex with k edges: 1 - k
Elements of weight 0 are skipped.

Tests: on an open sheet, an open polyline, a 3D polyline in no face,
isolated vertices and a 2D junction, the potential equals the barrier of
the distance, and on the sheet its gradient matches finite differences.
With the old weights the new test fails 18 assertions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The search-and-replace in the smooth_contact -> gcp rename rewrote
historical release notes ("Rename GCPPotential to GCPPotential") and
turned the Python naming test into a contradiction: it asserted that
GCPPotential both exists and does not exist.
5f301a2 dropped the std:: prefix, relying on ipc::unordered_map being
reachable through collision_mesh.hpp. It no longer is, so the Python
module failed to compile. Matches upstream again.
ESPCollisions stored its four maps as ipc::unordered_map members and
ESPCollisionDict::initialize took one as a parameter, so the public
esp_collisions.hpp included utils/unordered_map_and_set.hpp and with it
Abseil, a private dependency. tangential_collisions.hpp includes
esp_collisions.hpp, so every friction user needed Abseil on its include
path; the Python bindings failed to compile.

- ESPCollisions holds the maps in ESPCollisions::Maps, behind a
  unique_ptr and a maps() accessor, as Candidates does with
  AdjacencySets. Only library code used the maps.
- initialize() takes a named ESPCollisionPairMap, the same map type as
  before, so iteration order and results are unchanged.
- Both types are defined in the internal esp/esp_collision_maps.hpp.

Also drops an unused map.begin().value() that only compiled with
robin_map, a pair map keyed by std::array<int, 3> instead of index_t,
and a stray Abseil include in quadrature_potential.cpp.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The bindings still targeted the old high-order contact API and no
longer compiled: they bound a use_adaptive_dhat overload of build(),
an operator[] ESPCollisions no longer has, the removed
QuadraturePotential class, and an ESPParameters constructor without
area_weights.

- ESPCollisions.build takes (mesh, vertices, param, broad_phase).
- ESPParameters takes area_weights, and its defaults now match C++
  (integration_type NORMAL).
- ESPPotential takes use_near_far.

Add Python tests for the parameter defaults, the gradient against
central finite differences, and the Hessian shape.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The smooth_contact -> gcp rename broke every downstream user of the
released names (e.g. polyfem uses SmoothCollisions,
SmoothContactParameters and SmoothContactPotential).

- C++: [[deprecated]] aliases SmoothContactParameters, SmoothCollisions,
  SmoothContactPotential, SmoothCollision and SmoothCollisionsBuilder.
- Forwarding headers at ipc/smooth_contact/smooth_collisions.hpp and
  ipc/smooth_contact/smooth_contact_potential.hpp.
- Python: SmoothCollision2, SmoothCollisions, SmoothContactParameters
  and SmoothContactPotential alias the GCP classes.

Replace the SmoothPotential naming test with a check of the aliases.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Remove dead and experimental code:
- Remove ProfileRegistry. Nothing reads it, and the ESP Hessian locked
  its global mutex once per quadrature point.
- Restore gcp/ to main's content plus the deprecated aliases. The
  ESP-only distance helpers move to esp/esp_distance.hpp;
  half_edge_edge_mollifier had no callers. gcp_collisions_builder.cpp
  matches main again: upstream ipc-sim#188 had already reverted the by-value
  shared_ptr parameters.
- Remove ESP adaptive support (AdaptiveSupport, smooth_clamp and their
  tests). It stays on the esp-adaptive-support branch.
- Delete dead code: pair_distance.{hpp,tpp},
  smoothed_offset_potential_linear.h, math/span.hpp (ESPPrimitive gets
  vertex_id(i)), Math::log_barrier (superseded by ESPParameters::barrier
  in c02cc43), CollisionMesh::is_watertight() and its edge-face
  adjacency, whose non-manifold check could never fire, and ESP debug
  counters and helpers.

Fix:
- 3D ESP with default parameters (quad_order 1, empty face rule) built
  neither vertex nor face collisions, keeping only edge-edge contact:
  the build keyed on quad_order and the potential on face_quad_rule.
  Both now key on face_quad_rule; quad_order only sets the 2D rule.
- Revert the EA_EB parallel-edge fallback in core edge_edge_distance and
  the srand(0) added to its test. Its absolute 1e-20 threshold made d²
  26x too large for perpendicular edges 1e-6 long. ESP keeps the
  fallback in edge_edge_distance_parallel_safe.
- Candidates::build no longer copies the CollisionMesh; the
  adjacency-set queries take the mesh instead, and clear() resets them.
  ESPCollisions::build converts its own copy of the candidates instead
  of const_casting the caller's.
- Move ESP friction to esp/esp_tangential_collisions.cpp, and attribute
  edge-edge mollifier weights through edges_to_faces instead of
  scanning every face per pair.
- Only look up obstacle edges when skip_obstacles is set.
- Build: drop the duplicated CUDA block, the tests' Abseil/robin_map
  links and the Eigen recipe's EIGEN_DONT_VECTORIZE default (define it
  on ipc_toolkit with or without SIMD instead), and link geogram
  privately.
- Python: nest IntegrationType under ESPParameters and fix the ESP
  docstrings.

Tests:
- Restore the two commented-out codim normal-collision tests.
- Fix the FV mollification test's cube.obj path; it always skipped.
- Add tests for obstacle flags, skip_obstacles, the ESP integration
  types, BarrierPotential with unsquared distances, and the 3D ESP
  quadrature default.
- Test the exact distance-type classifiers against the reference
  (renamed *_reference), with 100k samples instead of 1M.
- Hide the 15-minute ESP "Expensive" test in every build, and drop the
  assertion-free "Number of Pairs" test and the benchmark_eigen
  experiment.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Deprecated aliases for SmoothCollisionTemplate and
  TangentialPotential::smooth_contact_force{,_jacobian,_jacobian_unit}.
- Fix the warnings in the PR's code: lambdas captured structured
  bindings (a C++20 extension), an old-style cast, a switch without a
  default, and the assert-only total_p_far.
Without geogram, point_edge_distance_type_exact,
point_triangle_distance_type_exact and edge_edge_distance_type_exact
fell back to the standard analytic routines. Those are separate
floating-point formulas, so near a region boundary they can disagree
with each other: the ESP builder classified a face-vertex pair as P_E0
and the Edge3P1-Vertex3 collision then asserted P_E on that edge. With
geogram off, ESP's debug assertions failed and the potential on a
convex sphere was a round-off value instead of exactly zero.

- Without geogram, evaluate the same sign predicates (dot3,
  cross_dot_cross_1/2) in double precision and keep the shared
  classifiers. Each predicate is out of line, so it returns the same
  sign for the same arguments at every call site, and the three
  classifications agree with each other by construction.
- Log a one-time warning that the predicates are not exact.
- ArbitraryPointESP built its collisions with the standard classifiers
  while the collision templates assert with the exact ones; at exact
  ties (e.g. a query point above a boundary vertex) they disagreed,
  with or without geogram. Use the exact classifiers there too.
- Test that the classifiers are mutually consistent near region
  boundaries, in builds with and without geogram.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
- Define EIGEN_DONT_VECTORIZE only with IPC_TOOLKIT_WITH_SIMD again, as
  on main. Defining it unconditionally changed Eigen's object layout for
  every SIMD-off build (including the Linux Python wheels) and
  disagreed with MeshFEMSparse, which still defines it only with SIMD.
- Remove the ESPCollisionTemplate static_assert that required Eigen
  vectorization to be off. The ESP primitives hold no Eigen members,
  and the ESP tests pass with vectorization on.
- Default IPC_TOOLKIT_WITH_GEOGRAM to OFF, so the new dependency is
  opt-in. Without it, the distance-type classifiers use consistent
  floating-point predicates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
tests-data's cube.ply is the same unit cube as the cube.obj this PR
committed under tests/src/tests/potential/ (same vertices and triangles,
only the vertex and face order differ), so load it with
tests::load_mesh like the other tests and delete the copy. The old path
reached it through DATA_DIR / "../src/...", which only worked with the
default IPC_TOOLKIT_TESTS_DATA_DIR.

Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
ESP collisions encode their distance type in their class and assert it
when evaluated (Vertex2-Edge2P1 only at P_E), so evaluating the set
built at V at an FD stencil point across a switch aborted in Debug,
with or without geogram (mesh_1: a P_E pair 3.8e-10 inside its region,
FD step 1e-8).

- Gradient: rebuild the collisions at each perturbed configuration.
  The potential is C1 across switches, so the FD may cross one. mesh_1
  (880 DOFs) checks a directional derivative, as the 3D tests do,
  because a full FD with a rebuild per evaluation is too slow.
- Hessian: keep the FD of the analytic gradient on the set built at V.
  Rebuilding does not work there: the potential is only C1 across a
  switch, so a central difference averages two one-sided Hessians.
  Following the GCP tests, the Hessian is checked only where no stored
  P_E pair is within the FD step of a switch. Rounding leaves pairs
  about 1e-17 from one in the squares (order 14) and mesh_2 (orders 7
  and 14), so those sections check the gradient only.

Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
- Use the near/far split for every 0 < dbar_factor <= 2 (was (0, 1)).
  The far part of NearFarBarrier vanishes identically at 2, so the
  potential is continuous in dbar_factor, and dbar_factor = 1 is now a
  real split. The normalized unsplit branch is only reached at 0, where
  edge-edge terms are skipped, so the combined per-face PSD projection
  covers every case with indefinite weight-derivative terms.
- Cap the edge-edge weight support at dhat: the mollifier and the
  edge-edge cutoff use min(dbar, dhat) (ESPParameters::ee_support()),
  also in the tangential collisions.
- Keep edge-edge collision sets that cancel to empty, so their mollifier
  weight still counts in the face's weighted average; the potential
  jumped when a set changed between empty and non-empty.
- ESPParameters throws std::invalid_argument for dbar_factor outside
  [0, 2] and warns at 0. Default dbar_factor 1.0 -> 0.2 (C++ and Python).
- IPC_TOOLKIT_WITH_GEOGRAM defaults to ON again.
- Tests: dbar_factor range, NearFarBarrier far part at alpha = 2,
  continuity in dbar_factor at 1 and 2, cancelled edge-edge sets; both
  Hessian PSD tests also run on two-cubes-close.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
- ESPParameters::face_quad_rule is now private. Set it with
  set_quad_rule(), which throws std::invalid_argument for a negative or
  non-finite weight, or for weights that sum to zero. A negative weight
  makes that point's barrier term unbounded below as it approaches
  contact (and broke the PSD projection of the per-stencil Hessian
  branches). get_quad_rule() is unchanged.
- Breaking: callers that assigned params.face_quad_rule must call
  params.set_quad_rule() instead.
- Remove the quad_order 6/8 and 10-12 log messages from ESPParameters:
  they describe a downstream triangle-rule table, while here quad_order
  only selects the 2D Gauss-Lobatto rule, whose weights are positive.
- Tests: rule validation; the 3D friction force-jacobian test uses
  set_quad_rule().

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
- set_quad_rule() also throws std::invalid_argument for a negative or
  non-finite barycentric coordinate (point outside the face). Points
  whose coordinates do not sum to 1 (within 1e-10) only log a warning.
- Remove the quad_order > 14 check from ESPParameters: quad_order n
  selects the (n + 1)-point Gauss-Lobatto rule, which is tabulated up to
  19 points and computed beyond, and get_rule() already throws for
  n < 1 when the 2D rule is used. quad_order is unused in 3D.
- Tests: coordinate checks and the warning; Gauss-Lobatto weights are
  positive and sum to 1 for orders 1-25.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
- Each rejected coordinate case now has exactly one bad coordinate, so
  +inf is tested on its own (it was previously rejected via -inf).
- Capture the expected warning by swapping the logger's sinks, so it no
  longer prints to stdout.

Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
Drop the infinite-coordinate case and the warning-capture block; keep
the negative and NaN coordinate cases.

Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.19292% with 1009 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.21%. Comparing base (3817e4a) to head (a2a79a9).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/ipc/esp/quadrature_potential.cpp 73.71% 265 Missing ⚠️
src/ipc/esp/esp_potential.cpp 79.34% 216 Missing ⚠️
src/ipc/esp/esp_tangential_collisions.cpp 65.44% 179 Missing ⚠️
src/ipc/esp/esp_collisions_builder.cpp 80.13% 61 Missing ⚠️
src/ipc/esp/collisions/esp_quadrature.hpp 90.53% 50 Missing ⚠️
src/ipc/esp/collisions/esp_collision_template.cpp 89.54% 48 Missing ⚠️
src/ipc/esp/esp_collisions.cpp 66.17% 46 Missing ⚠️
src/ipc/esp/esp_distance.hpp 48.71% 40 Missing ⚠️
src/ipc/esp/collisions/esp_collision_template.hpp 40.00% 18 Missing ⚠️
src/ipc/candidates/candidates.cpp 89.16% 13 Missing ⚠️
... and 18 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #259      +/-   ##
==========================================
- Coverage   96.92%   93.21%   -3.72%     
==========================================
  Files         194      221      +27     
  Lines       17349    22605    +5256     
  Branches      944     1570     +626     
==========================================
+ Hits        16816    21071    +4255     
- Misses        533     1534    +1001     
Flag Coverage Δ
unittests 93.21% <81.19%> (-3.72%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

claude added 8 commits October 7, 2026 22:56
- MSVC: the generic operator_nearfar, gradient_nearfar and
  hessian_nearfar were defined inline in the ESPCollisionTemplate class
  body, while the 3D versions are explicit specializations in the .cpp
  that the header does not declare. MSVC put the zero stubs in the
  vtable, so every ESP energy on the near/far path was 0.0 on Windows.
  Define the stubs out of line in the .cpp, as operator(), gradient()
  and hessian() already are.
- clang-tidy: ESPCollisions::build takes const ESPParameters&
  (performance-unnecessary-value-param); the Python binding's
  overload_cast follows.
- Debug timeouts: the Hessian PSD tests compute eigenvalues only on the
  DOFs with nonzero Hessian entries (the others are exact zeros).
  Convergent Quadrature Hessian PSD: 555 s -> 15 s locally in Debug.
- The face quadrature tests looped over quad_order, which 3D ignores.
  They now set positive-weight face rules with set_quad_rule(); the PSD
  test covers dbar_factor 1, 0.4 and 0 (48 runs instead of 96; 1691 s
  -> 46 s locally in Debug).
- Docs of ESPCollisions::build and the compute_minimum_distance
  docstring (squared distance) ride along in the touched files.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
- Dependencies page: geogram row (IPC_TOOLKIT_WITH_GEOGRAM, BSD-3) and
  a note on the floating-point fallback; geogram node in
  dependencies.dot (dependencies.svg still needs regenerating).
- Barrier API reference: InversePowerBarrier and NearFarBarrier pages.
- NearFarBarrier: document alpha, the near/far split and the near/far
  methods.
- ESP doc comments: ESPCollision and ESPCollisionDict no longer
  describe GCP, fixed vertex counts or finish_insertion(); the barrier,
  area_weights, use_near_far, normalize_weights and the remaining
  build/hessian parameters are documented; stale comments about
  quad_order, Gauss-Lobatto point counts and "HOP" are fixed.

Claude-Session: https://claude.ai/code/session_01R7sJzFKyZAgyK7X4Vs6mXf
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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

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