Skip to content

Commit 86f1db3

Browse files
authored
Individual convergent formulation flags (#120)
* Add option to enable/disable only improved max approximator * Replace use_convergent_formulation with use_area_weighting and use_improved_max_approximator; move physical barrier into BarrierPotential * Update clang-format version to '18' * Update Python tests * Update convergent.rst
1 parent 2c7496b commit 86f1db3

File tree

23 files changed

+437
-267
lines changed

23 files changed

+437
-267
lines changed

.github/workflows/clang-format-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ jobs:
2929
- name: clang-format style check
3030
uses: jidicula/[email protected]
3131
with:
32-
clang-format-version: '17'
32+
clang-format-version: '18'
3333
check-path: ${{ matrix.path }}

docs/source/tutorial/convergent.rst

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,34 @@ Convergent Formulation
55

66
In addition to the original implementation of :cite:t:`Li2020IPC`, we also implement the convergent formulation of :cite:t:`Li2023Convergent`.
77

8-
To enable the convergent formulation, we need to set ``use_convergent_formulation`` before building ``Collisions``:
8+
Fully enabling the convergent formulation requires to set three flags: ``use_area_weighting`` and ``use_improved_max_approximator`` in ``Collisions`` (before calling ``build``) and ``use_physical_barrier`` in ``BarrierPotential``.
99

1010
.. md-tab-set::
1111

1212
.. md-tab-item:: C++
1313

1414
.. code-block:: c++
1515

16-
collisions.set_use_convergent_formulation(true);
16+
collisions.set_use_area_weighting(true);
17+
collisions.set_use_improved_max_approximator(true);
1718
collisions.build(collision_mesh, vertices, dhat);
1819

20+
barrier_potential.set_use_physical_barrier(true);
21+
double b = barrier_potential(collisions, mesh, vertices);
22+
1923
.. md-tab-item:: Python
2024

2125
.. code-block:: python
2226
23-
collisions.use_convergent_formulation = True
27+
collisions.use_area_weighting = True
28+
collisions.use_improved_max_approximator = True
2429
collisions.build(collision_mesh, vertices, dhat)
2530
31+
barrier_potential.use_physical_barrier = True
32+
b = barrier_potential(collisions, mesh, vertices);
33+
2634
.. important::
27-
The variable ``use_convergent_formulation`` should be set before calling ``Collisions::build`` for it to take effect. By default, it is ``false``.
35+
The flags ``use_area_weighting`` and ``use_improved_max_approximator`` should be set before calling ``build`` for them to take effect. By default, they are ``false``.
2836

2937
Technical Details
3038
-----------------
@@ -34,7 +42,7 @@ Technical Details
3442
In order to derive a convergent formulation, we first define a continuous form of our barrier potential :math:`P`. For a surface :math:`\mathcal{S}` embedded in 3D space, we parameterize the surfaces by common (possibly discontinuous) coordinates :math:`u \in \tilde{M} \subset \mathbb{R}^2`, so that :math:`\mathcal{S}(u)` traverses the material points across all surfaces contiguously. The total barrier potential is then
3543

3644
.. math::
37-
P(\mathcal{S})=\frac{1}{2} \int_{u \in \tilde{M}} \max _{v \in \tilde{M} \setminus{ }_r u} b(d(\mathcal{S}(u), \mathcal{S}(v)), \hat{d})~\mathrm{d} u,
45+
P(\mathcal{S})=\frac{1}{2} \int_{u \in \tilde{M}}~\max_{v \in \tilde{M} \setminus{ }_r u} b(d(\mathcal{S}(u), \mathcal{S}(v)), \hat{d})~\mathrm{d} u,
3846
3947
where we define the operator :math:`\setminus_r: \mathcal{P}(\mathbb{R}^2) \times \mathbb{R} \times \mathbb{R}^2 \mapsto \mathcal{P}(\mathbb{R}^2)` to be
4048

@@ -57,6 +65,9 @@ Applying mesh vertices as nodes (and quadrature points), we numerically integrat
5765
5866
where :math:`w_{\bar{x}}` are the quadrature weights, each given by one-third of the sum of the areas (in material space) of the boundary triangles incident to :math:`\bar{x}`.
5967

68+
.. note::
69+
The area weighted quadrature is enabled by setting ``use_area_weighting`` to ``true`` in ``Collisions``.
70+
6071
We next need to smoothly approximate the max operator in the barrier potentials. However, common approaches such as an :math:`L^p`-norm or LogSumExp would decrease sparsity in subsequent numerical solves by increasing the stencil size per collision evaluation. We instead leverage the locality of our barrier function to approximate the max operator by removing duplicate distance pairs. Our resulting approximators for a triangulated surface is
6172

6273
.. math::
@@ -67,6 +78,9 @@ We next need to smoothly approximate the max operator in the barrier potentials.
6778
6879
where :math:`V_{\text{int}} \subseteq V` is the subset of internal surface nodes and :math:`E_{\text{int}} \subseteq E` is the subset of internal surface edges (i.e., edges incident to two triangles). For locally convex regions this estimator is tight while remaining smooth. In turn, for nonconvex regions, it improves over direct summation.
6980

81+
.. note::
82+
The improved max approximator is enabled by setting ``use_improved_max_approximator`` to ``true`` in ``Collisions``.
83+
7084
The corresponding discrete barrier potential is then simply
7185

7286
.. math::
@@ -101,6 +115,9 @@ The barrier stiffness (:math:`\kappa`) then has units of pressure (e.g., :math:`
101115
This implies we can get good solver convergence even when using a fixed :math:`\kappa` by setting it relative to the material's Young's modulus (:math:`\kappa = 0.1 E` works well in many examples).
102116
The intention is to treat the barrier as a thin elastic region around the mesh, and having consistent units makes it easier to pick the stiffness for this "material".
103117

118+
.. note::
119+
The physical barrier is enabled by setting ``use_physical_barrier`` to ``true`` in ``BarrierPotential``.
120+
104121
.. _convergent-friction-formulation:
105122

106123
Friction

python/src/barrier/barrier.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class PyBarrier : public Barrier {
1212
double operator()(const double d, const double dhat) const override { PYBIND11_OVERRIDE_PURE_NAME(double, Barrier, "__call__", operator(), d, dhat); }
1313
double first_derivative(const double d, const double dhat) const override { PYBIND11_OVERRIDE_PURE(double, Barrier, first_derivative, d, dhat); }
1414
double second_derivative(const double d, const double dhat) const override { PYBIND11_OVERRIDE_PURE(double, Barrier, second_derivative, d, dhat); }
15+
double units(const double dhat) const override { PYBIND11_OVERRIDE_PURE(double, Barrier, units, dhat); }
1516
// clang-format on
1617
};
1718

@@ -25,7 +26,8 @@ void define_barrier(py::module_& m)
2526
py::arg("dhat"))
2627
.def(
2728
"second_derivative", &Barrier::second_derivative, py::arg("d"),
28-
py::arg("dhat"));
29+
py::arg("dhat"))
30+
.def("units", &Barrier::units, py::arg("dhat"));
2931

3032
py::class_<ClampedLogBarrier, Barrier, std::shared_ptr<ClampedLogBarrier>>(
3133
m, "ClampedLogBarrier")

python/src/collisions/collisions.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,18 @@ void define_collisions(py::module_& m)
138138
"to_string", &Collisions::to_string, "", py::arg("mesh"),
139139
py::arg("vertices"))
140140
.def_property(
141-
"use_convergent_formulation",
142-
&Collisions::use_convergent_formulation,
143-
&Collisions::set_use_convergent_formulation,
144-
"If the collisions should use the convergent formulation.")
141+
"use_area_weighting", &Collisions::use_area_weighting,
142+
&Collisions::set_use_area_weighting,
143+
"If the Collisions should use the convergent formulation.")
145144
.def_property(
146-
"are_shape_derivatives_enabled",
147-
&Collisions::are_shape_derivatives_enabled,
148-
&Collisions::set_are_shape_derivatives_enabled,
149-
"If the collisions are using the convergent formulation.")
145+
"use_improved_max_approximator",
146+
&Collisions::use_improved_max_approximator,
147+
&Collisions::set_use_improved_max_approximator,
148+
"If the Collisions should use the improved max approximator.")
149+
.def_property(
150+
"enable_shape_derivatives", &Collisions::enable_shape_derivatives,
151+
&Collisions::set_enable_shape_derivatives,
152+
"If the Collisions are using the convergent formulation.")
150153
.def(
151154
"to_string", &Collisions::to_string, py::arg("mesh"),
152155
py::arg("vertices"))

python/src/potentials/barrier_potential.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,26 @@ void define_barrier_potential(py::module_& m)
99
{
1010
py::class_<BarrierPotential>(m, "BarrierPotential")
1111
.def(
12-
py::init<const double>(),
12+
py::init<const double, const bool>(),
1313
R"ipc_Qu8mg5v7(
1414
Construct a barrier potential.
1515
1616
Parameters:
1717
dhat: The activation distance of the barrier.
1818
)ipc_Qu8mg5v7",
19-
py::arg("dhat"))
19+
py::arg("dhat"), py::arg("use_physical_barrier") = false)
2020
.def(
21-
py::init<const std::shared_ptr<Barrier>, const double>(),
21+
py::init<
22+
const std::shared_ptr<Barrier>, const double, const bool>(),
2223
R"ipc_Qu8mg5v7(
2324
Construct a barrier potential.
2425
2526
Parameters:
2627
barrier: The barrier function.
2728
dhat: The activation distance of the barrier.
2829
)ipc_Qu8mg5v7",
29-
py::arg("barrier"), py::arg("dhat"))
30+
py::arg("barrier"), py::arg("dhat"),
31+
py::arg("use_physical_barrier") = false)
3032
.def(
3133
"__call__",
3234
py::overload_cast<

python/tests/test_ipc.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ def check_ipc_derivatives(broad_phase_method, use_convergent_formulation, mesh_n
1616
vertices = mesh.vertices(vertices)
1717

1818
collisions = ipctk.Collisions()
19-
collisions.use_convergent_formulation = use_convergent_formulation
19+
collisions.use_area_weighting = use_convergent_formulation
20+
collisions.use_improved_max_approximator = use_convergent_formulation
2021
collisions.build(mesh, vertices, dhat,
2122
broad_phase_method=broad_phase_method)
2223
assert len(collisions) > 0
2324

24-
B = ipctk.BarrierPotential(dhat)
25+
B = ipctk.BarrierPotential(
26+
dhat, use_physical_barrier=use_convergent_formulation)
2527

2628
grad_b = B.gradient(collisions, mesh, vertices)
2729
fgrad_b = utils.finite_gradient(

src/ipc/barrier/adaptive_stiffness.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ double initial_barrier_stiffness(
4848
assert(std::isfinite(kappa));
4949
}
5050

51-
return std::min(
52-
max_barrier_stiffness, std::max(min_barrier_stiffness, kappa));
51+
return std::clamp(kappa, min_barrier_stiffness, max_barrier_stiffness);
5352
}
5453

5554
// Adaptive κ

src/ipc/barrier/barrier.hpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,30 @@ class Barrier {
1212
Barrier() = default;
1313
virtual ~Barrier() = default;
1414

15+
/// @brief Evaluate the barrier function.
16+
/// @param d Distance.
17+
/// @param dhat Activation distance of the barrier.
18+
/// @return The value of the barrier function at d.
1519
virtual double operator()(const double d, const double dhat) const = 0;
20+
21+
/// @brief Evaluate the first derivative of the barrier function wrt d.
22+
/// @param d Distance.
23+
/// @param dhat Activation distance of the barrier.
24+
/// @retur The value of the first derivative of the barrier function at d.
1625
virtual double
1726
first_derivative(const double d, const double dhat) const = 0;
27+
28+
/// @brief Evaluate the second derivative of the barrier function wrt d.
29+
/// @param d Distance.
30+
/// @param dhat Activation distance of the barrier.
31+
/// @return The value of the second derivative of the barrier function at d.
1832
virtual double
1933
second_derivative(const double d, const double dhat) const = 0;
34+
35+
/// @brief Get the units of the barrier function.
36+
/// @param dhat The activation distance of the barrier.
37+
/// @return The units of the barrier function.
38+
virtual double units(const double dhat) const = 0;
2039
};
2140

2241
// ============================================================================
@@ -106,6 +125,15 @@ class ClampedLogBarrier : public Barrier {
106125
{
107126
return barrier_second_derivative(d, dhat);
108127
}
128+
129+
/// @brief Get the units of the barrier function.
130+
/// @param dhat The activation distance of the barrier.
131+
/// @return The units of the barrier function.
132+
double units(const double dhat) const override
133+
{
134+
// (d - d̂)² = d̂² (d/d̂ - 1)²
135+
return dhat * dhat;
136+
}
109137
};
110138

111139
} // namespace ipc

src/ipc/broad_phase/aabb.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ class AABB {
2020

2121
AABB(const AABB& aabb1, const AABB& aabb2, const AABB& aabb3)
2222
: AABB(
23-
aabb1.min.min(aabb2.min).min(aabb3.min),
24-
aabb1.max.max(aabb2.max).max(aabb3.max))
23+
aabb1.min.min(aabb2.min).min(aabb3.min),
24+
aabb1.max.max(aabb2.max).max(aabb3.max))
2525
{
2626
}
2727

src/ipc/collision_mesh.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ CollisionMesh::CollisionMesh(
1414
const Eigen::MatrixXi& faces,
1515
const Eigen::SparseMatrix<double>& displacement_map)
1616
: CollisionMesh(
17-
std::vector<bool>(rest_positions.rows(), true),
18-
rest_positions,
19-
edges,
20-
faces,
21-
displacement_map)
17+
std::vector<bool>(rest_positions.rows(), true),
18+
rest_positions,
19+
edges,
20+
faces,
21+
displacement_map)
2222
{
2323
}
2424

src/ipc/collisions/collisions.cpp

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ void Collisions::build(
173173
};
174174

175175
tbb::enumerable_thread_specific<CollisionsBuilder> storage(
176-
use_convergent_formulation(), are_shape_derivatives_enabled());
176+
use_area_weighting(), enable_shape_derivatives());
177177

178178
tbb::parallel_for(
179179
tbb::blocked_range<size_t>(size_t(0), candidates.vv_candidates.size()),
@@ -207,7 +207,7 @@ void Collisions::build(
207207
r.end());
208208
});
209209

210-
if (use_convergent_formulation()) {
210+
if (use_improved_max_approximator()) {
211211
if (candidates.ev_candidates.size() > 0) {
212212
// Convert edge-vertex to vertex-vertex
213213
const std::vector<VertexVertexCandidate> vv_candidates =
@@ -276,50 +276,41 @@ void Collisions::build(
276276
Collision& collision = (*this)[ci];
277277
collision.dmin = dmin;
278278
}
279+
}
279280

280-
if (use_convergent_formulation()) {
281-
// NOTE: When using the convergent formulation we want the barrier to
282-
// have units of Pa⋅m, so κ gets units of Pa and the barrier function
283-
// should have units of m. See notebooks/physical_barrier.ipynb for more
284-
// details.
285-
const double barrier_to_physical_barrier_divisor =
286-
dhat * std::pow(dhat + 2 * dmin, 2);
287-
288-
for (size_t ci = 0; ci < size(); ci++) {
289-
Collision& collision = (*this)[ci];
290-
collision.weight /= barrier_to_physical_barrier_divisor;
291-
if (are_shape_derivatives_enabled()) {
292-
collision.weight_gradient /=
293-
barrier_to_physical_barrier_divisor;
294-
}
295-
}
281+
void Collisions::set_use_area_weighting(const bool use_area_weighting)
282+
{
283+
if (!empty() && use_area_weighting != m_use_area_weighting) {
284+
logger().warn("Setting use_area_weighting after building collisions. "
285+
"Re-build collisions for this to have an effect.");
296286
}
287+
288+
m_use_area_weighting = use_area_weighting;
297289
}
298290

299-
void Collisions::set_use_convergent_formulation(
300-
const bool use_convergent_formulation)
291+
void Collisions::set_use_improved_max_approximator(
292+
const bool use_improved_max_approximator)
301293
{
302294
if (!empty()
303-
&& use_convergent_formulation != m_use_convergent_formulation) {
295+
&& use_improved_max_approximator != m_use_improved_max_approximator) {
304296
logger().warn(
305-
"Setting use_convergent_formulation after building collisions. "
297+
"Setting use_improved_max_approximator after building collisions. "
306298
"Re-build collisions for this to have an effect.");
307299
}
308300

309-
m_use_convergent_formulation = use_convergent_formulation;
301+
m_use_improved_max_approximator = use_improved_max_approximator;
310302
}
311303

312-
void Collisions::set_are_shape_derivatives_enabled(
313-
const bool are_shape_derivatives_enabled)
304+
void Collisions::set_enable_shape_derivatives(
305+
const bool enable_shape_derivatives)
314306
{
315-
if (!empty()
316-
&& are_shape_derivatives_enabled != m_are_shape_derivatives_enabled) {
307+
if (!empty() && enable_shape_derivatives != m_enable_shape_derivatives) {
317308
logger().warn(
318309
"Setting enable_shape_derivatives after building collisions. "
319310
"Re-build collisions for this to have an effect.");
320311
}
321312

322-
m_are_shape_derivatives_enabled = are_shape_derivatives_enabled;
313+
m_enable_shape_derivatives = enable_shape_derivatives;
323314
}
324315

325316
// ============================================================================

0 commit comments

Comments
 (0)