Skip to content

Commit 3af9200

Browse files
authored
Update tutorial documentation (#101)
* Update project to PSD docs * Update simulation.rst
1 parent f46b0af commit 3af9200

File tree

10 files changed

+156
-26
lines changed

10 files changed

+156
-26
lines changed

docs/source/conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020

2121
# -- Project information -----------------------------------------------------
2222

23+
from datetime import datetime
2324
import ipctk
2425

2526
project = "IPC Toolkit"
26-
copyright = '2020-2023, IPC-Sim Organization; MIT License'
27+
copyright = f'2020-{datetime.now().year}, IPC-Sim Organization; MIT License'
2728
author = "Zachary Ferguson"
2829
version = ipctk.__version__
2930

docs/source/cpp-api/utils.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ Positive Semi-Definite Projection
1111
---------------------------------
1212

1313
.. doxygenfunction:: ipc::project_to_psd
14-
.. doxygenfunction:: ipc::project_to_pd
14+
.. doxygenfunction:: ipc::project_to_pd
15+
16+
.. doxygenenum:: ipc::PSDProjectionMethod

docs/source/python-api/utils.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ Positive Semi-Definite Projection
1717
---------------------------------
1818

1919
.. autofunction:: ipctk.project_to_psd
20-
.. autofunction:: ipctk.project_to_pd
20+
.. autofunction:: ipctk.project_to_pd
21+
.. autoclass:: ipctk.PSDProjectionMethod

docs/source/refs.bib

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,11 @@ @article{Ferguson2021RigidIPC
6767
number = 4,
6868
articleno = 183,
6969
note = {\url{https://ipc-sim.github.io/rigid-ipc}}
70-
}
70+
}
71+
@inproceedings{Chen2024Stabler,
72+
title = {Stabler Neo-Hookean Simulation: Absolute Eigenvalue Filtering for Projected Newton},
73+
author = {Honglin Chen and Hsueh-Ti Derek Liu and David I.W. Levin and Changxi Zheng and Alec Jacobson},
74+
year = 2024,
75+
booktitle = {{ACM} {SIGGRAPH} 2024 Conference Proceedings},
76+
note = {\url{https://www.cs.columbia.edu/cg/abs-psd/}}
77+
}

docs/source/tutorial/simulation.rst

Lines changed: 118 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@ From the full (volumetric) mesh vertices and surface edges/faces which index int
3030
Eigen::MatrixXi edges;
3131
igl::edges(faces, edges);
3232

33-
std::vector<bool> is_on_surface = ipc::CollisionMesh::construct_is_on_surface(node_positions.rows(), boundary_edges);
34-
3533
ipc::CollisionMesh collision_mesh =
36-
CollisionMesh::build_from_full_mesh(full_rest_positions, edges, faces);
34+
ipc::CollisionMesh::build_from_full_mesh(full_rest_positions, edges, faces);
3735

3836
.. md-tab-item:: Python
3937

@@ -49,7 +47,69 @@ From the full (volumetric) mesh vertices and surface edges/faces which index int
4947
collision_mesh = ipctk.CollisionMesh.build_from_full_mesh(
5048
full_rest_positions, edges, faces)
5149
52-
This ``CollisionMesh`` can then be used just as any other ``CollisionMesh``. However, when computing the gradient and Hessian of the potentials, the derivatives will be with respect to the surface DOF. If you want the derivatives with respect to the full mesh DOF, then we need to apply the chain rule. Fortunately, the ``CollisionMesh`` class provides a function to do this (``CollisionMesh::to_full_dof``):
50+
This ``CollisionMesh`` can then be used just as any other ``CollisionMesh``. However, when passing the collision mesh to toolkit functions, the vertices have to be the surface vertices. The ``CollisionMesh`` class provides a function to map the full vertices and velocities to the surface vertices and velocities. These are ``CollisionMesh::vertices(full_vertices)`` and ``CollisionMesh::map_displacements(full_displacements)``, respectively.
51+
52+
.. md-tab-set::
53+
54+
.. md-tab-item:: C++
55+
56+
.. code-block:: c++
57+
58+
// Convert full vertices to surface vertices
59+
Eigen::VectorXd vertices = collision_mesh.vertices(full_vertices);
60+
61+
// Construct the set of collisions
62+
ipc::Collisions collisions;
63+
collisions.build(collision_mesh, vertices, dhat);
64+
65+
// Construct a barrier potential
66+
ipc::BarrierPotential B(dhat);
67+
68+
// Evaluate the potential
69+
double b = B(collisions, collision_mesh, vertices);
70+
71+
// Convert full velocities to surface velocities
72+
Eigen::VectorXd velocities = collision_mesh.map_displacements(full_velocities);
73+
74+
// Construct the set of friction collisions
75+
ipc::FrictionCollisions friction_collisions;
76+
friction_collisions.build(collision_mesh, vertices, collisions, B, barrier_stiffness, mu);
77+
78+
// Construct a friction dissipative potential
79+
ipc::FrictionPotential D(epsv);
80+
81+
double d = D(friction_collisions, collision_mesh, velocities);
82+
83+
.. md-tab-item:: Python
84+
85+
.. code-block:: python
86+
87+
# Convert full vertices to surface vertices
88+
vertices = collision_mesh.vertices(full_vertices)
89+
90+
# Construct the set of collisions
91+
collisions = ipctk.Collisions()
92+
collisions.build(collision_mesh, vertices, dhat)
93+
94+
# Construct a barrier potential
95+
B = ipctk.BarrierPotential(dhat)
96+
97+
# Evaluate the potential
98+
b = B(collisions, collision_mesh, vertices)
99+
100+
# Convert full velocities to surface velocities
101+
velocities = collision_mesh.map_displacements(full_velocities)
102+
103+
# Construct the set of friction collisions
104+
friction_collisions = ipctk.FrictionCollisions()
105+
friction_collisions.build(collision_mesh, vertices, collisions, B, barrier_stiffness, mu)
106+
107+
# Construct a friction dissipative potential
108+
D = ipctk.FrictionPotential(epsv)
109+
110+
d = D(friction_collisions, collision_mesh, velocities)
111+
112+
When computing the gradient and Hessian of the potentials, the derivatives will be with respect to the surface DOF. If you want the derivatives with respect to the full mesh DOF, then we need to apply the chain rule. Fortunately, the ``CollisionMesh`` class provides a function to do this (``CollisionMesh::to_full_dof``):
53113

54114
.. md-tab-set::
55115

@@ -77,6 +137,43 @@ This ``CollisionMesh`` can then be used just as any other ``CollisionMesh``. How
77137
hess = B.hessian(collision, collision_mesh, vertices)
78138
hess_full = collision_mesh.to_full_dof(hess)
79139
140+
Codimensional Vertices
141+
^^^^^^^^^^^^^^^^^^^^^^
142+
143+
In some cases, the collision mesh vertices are not the same as the surface vertices of the volumetric mesh vertices. One such case is when simulating codimensional vertices in conjunction with shell or volumetric meshes. In this case, simply calling ``build_from_full_mesh`` will not work as it will ignore the vertices that are not connected to any boundary edge. Instead, you can build a vector of booleans that indicate which vertices are on the surface and pass it to the ``CollisionMesh`` constructor.
144+
145+
.. md-tab-set::
146+
147+
.. md-tab-item:: C++
148+
149+
.. code-block:: c++
150+
151+
// codim_vertices is a vector of indices of the codimensional vertices
152+
Eigen::VectorXi codim_vertices = ...;
153+
154+
// is_on_surface is a vector of booleans indicating which vertices are on the surface
155+
std::vector<bool> is_on_surface = ipc::CollisionMesh::construct_is_on_surface(
156+
full_rest_positions.rows(), boundary_edges, codim_vertices);
157+
158+
// Construct the collision mesh from the is_on_surface vector and full mesh data
159+
ipc::CollisionMesh collision_mesh(
160+
is_on_surface, full_rest_positions, edges, faces);
161+
162+
.. md-tab-item:: Python
163+
164+
.. code-block:: python
165+
166+
# codim_vertices is an array of indices of the codimensional vertices
167+
codim_vertices = ...
168+
169+
# is_on_surface is a list of booleans indicating which vertices are on the surface
170+
is_on_surface = ipctk.CollisionMesh.construct_is_on_surface(
171+
len(full_rest_positions), boundary_edges, codim_vertices)
172+
173+
# Construct the collision mesh from the is_on_surface vector and full mesh data
174+
collision_mesh = ipctk.CollisionMesh(
175+
is_on_surface, full_rest_positions, edges, faces)
176+
80177
Nonlinear Bases and Curved Meshes
81178
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
82179

@@ -118,24 +215,37 @@ While IPC cannot directly handle nonlinear finite element bases and/or curved me
118215
119216
# Collision proxy mesh
120217
# Load the proxy mesh from a file
121-
proxy_mesh = meshio.read("proxy.msh")
218+
proxy_mesh = meshio.read("proxy.obj")
122219
proxy_rest_positions = proxy_mesh.points
123220
proxy_faces = proxy_mesh.cells_dict["triangle"]
124221
proxy_edges = igl.edges(proxy_faces)
125-
# Or build it from the volumetric mesh
222+
# or build it from the volumetric mesh ...
126223
127224
# Linear map from the finite element mesh to the collision proxy
128225
displacement_map = ... # build or load the displacement map
129226
130227
collision_mesh = CollisionMesh(
131228
proxy_rest_positions, proxy_edges, proxy_faces, displacement_map)
132229
133-
We can then map the displacements using ``collision_mesh.map_displacement(fe_displacements)`` or directly get the displaced proxy mesh vertices using ``collision_mesh.displace_vertices(fe_displacements)``. Similarly, we can map forces/gradients using ``collision_mesh.to_full_dof(collision_forces)`` or force Jacobians/potential Hessians using ``collision_mesh.to_full_dof(potential_hessian)``.
230+
We can then map the displacements using ``collision_mesh.map_displacement(fe_displacements)`` or directly get the displaced proxy mesh vertices using ``collision_mesh.displace_vertices(fe_displacements)``. Similarly, we can map forces/potential gradients using ``collision_mesh.to_full_dof(collision_forces)`` or force Jacobians/potential Hessians using ``collision_mesh.to_full_dof(potential_hessian)``.
134231

135232
.. warning::
136233
The function ``CollisionMesh::vertices(full_positions)`` should not be used in this case because the rest positions used to construct the ``CollisionMesh`` are not the same as the finite element mesh's rest positions. Instead, use ``CollisionMesh::displace_vertices(fe_displacements)`` where ``fe_displacements`` is already the solution of the PDE or can be computed as ``fe_displacements = fe_positions - fe_rest_positions`` from deformed and rest positions.
137234

138235
Positive Semi-Definite Projection
139236
---------------------------------
140237

141-
As described by :cite:t:`Li2020IPC`, the Hessian of the potentials can be indefinite. This is problematic when using the Hessian in a Newton step :cite:p:`Li2020IPC`. To remedy this, we can project the Hessian onto the positive semidefinite (PSD) cone. To do this set the optional parameter ``project_hessian_to_psd`` of ``compute_potential_hessian`` to true.
238+
As described by :cite:t:`Li2020IPC`, the Hessian of the potentials can be indefinite. This is problematic when using the Hessian in a Newton step :cite:p:`Li2020IPC`.
239+
To remedy this, we can project the Hessian onto the positive semidefinite (PSD) cone. To do this set the optional parameter ``project_hessian_to_psd`` in ``Potential::hessian`` to one of the following.
240+
241+
.. md-tab-set::
242+
243+
.. md-tab-item:: C++
244+
245+
- ``ProjectToPSD::CLAMP``: Clamp the negative eigenvalues of the Hessian to 0. This is the same as used by :cite:t:`Li2020IPC`.
246+
- ``ProjectToPSD::ABS``: Set the negative eigenvalues of the Hessian to their absolute value. This is the method proposed by :cite:t:`Chen2024Stabler`.
247+
248+
.. md-tab-item:: Python
249+
250+
- ``ProjectToPSD.CLAMP``: Clamp the negative eigenvalues of the Hessian to 0. This is the same as used by :cite:t:`Li2020IPC`.
251+
- ``ProjectToPSD.ABS``: Set the negative eigenvalues of the Hessian to their absolute value. This is the method proposed by :cite:t:`Chen2024Stabler`.

python/src/utils/eigen_ext.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ void define_eigen_ext(py::module_& m)
2828
"Enumeration of implemented PSD projection methods.")
2929
.value("NONE", PSDProjectionMethod::NONE, "No PSD projection")
3030
.value(
31-
"CLAMP", PSDProjectionMethod::CLAMP, "Clamp negative eigenvalues")
31+
"CLAMP", PSDProjectionMethod::CLAMP,
32+
"Clamp negative eigenvalues to zero")
3233
.value("ABS", PSDProjectionMethod::ABS, "Flip negative eigenvalues")
3334
.export_values();
3435

@@ -47,5 +48,5 @@ void define_eigen_ext(py::module_& m)
4748
Returns:
4849
Projected matrix
4950
)ipc_Qu8mg5v7",
50-
py::arg("A"), py::arg("method"));
51+
py::arg("A"), py::arg("method") = PSDProjectionMethod::CLAMP);
5152
}

python/src/utils/logger.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ using namespace ipc;
77

88
void define_logger(py::module_& m)
99
{
10-
py::enum_<spdlog::level::level_enum>(m, "LoggerLevel")
11-
.value("trace", spdlog::level::level_enum::trace)
12-
.value("debug", spdlog::level::level_enum::debug)
13-
.value("info", spdlog::level::level_enum::info)
14-
.value("warn", spdlog::level::level_enum::warn)
15-
.value("error", spdlog::level::level_enum::err)
16-
.value("critical", spdlog::level::level_enum::critical)
17-
.value("off", spdlog::level::level_enum::off)
10+
py::enum_<spdlog::level::level_enum>(
11+
m, "LoggerLevel", "Enumeration of log levels")
12+
.value("trace", spdlog::level::level_enum::trace, "Trace level")
13+
.value("debug", spdlog::level::level_enum::debug, "Debug level")
14+
.value("info", spdlog::level::level_enum::info, "Info level")
15+
.value("warn", spdlog::level::level_enum::warn, "Warning level")
16+
.value("error", spdlog::level::level_enum::err, "Error level")
17+
.value(
18+
"critical", spdlog::level::level_enum::critical, "Critical level")
19+
.value("off", spdlog::level::level_enum::off, "Off level")
1820
.export_values();
1921

2022
m.def(

src/ipc/collision_mesh.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ CollisionMesh::CollisionMesh(
6060

6161
const int dim = full_rest_positions.cols();
6262

63-
// Selection matrix S ∈ ℝ^{collision×full}
63+
// Initializes m_select_vertices and m_select_dof
6464
init_selection_matrices(dim);
6565

6666
if (displacement_map.size() == 0) {

src/ipc/collision_mesh.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,13 +326,13 @@ class CollisionMesh {
326326

327327
/// @brief Selection matrix S ∈ ℝ^{collision×full} for vertices
328328
Eigen::SparseMatrix<double> m_select_vertices;
329-
/// @brief Selection matrix S ∈ ℝ^{collision×full} for DOF
329+
/// @brief Selection matrix S ∈ ℝ^{(dim*collision)×(dim*full)} for DOF
330330
Eigen::SparseMatrix<double> m_select_dof;
331331

332332
/// @brief Mapping from full displacements to collision displacements
333333
/// @note this is premultiplied by m_select_vertices
334334
Eigen::SparseMatrix<double> m_displacement_map;
335-
/// @brief Mapping from full displacements to collision displacements
335+
/// @brief Mapping from full displacements DOF to collision displacements DOF
336336
/// @note this is premultiplied by m_select_dof
337337
Eigen::SparseMatrix<double> m_displacement_dof_map;
338338

src/ipc/utils/eigen_ext.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,16 @@ project_to_pd(
138138
const Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>& A,
139139
double eps = 1e-8);
140140

141-
enum class PSDProjectionMethod { NONE, CLAMP, ABS };
141+
/// @brief Enumeration of implemented PSD projection methods
142+
enum class PSDProjectionMethod {
143+
NONE, ///< No PSD projection
144+
CLAMP, ///< Clamp negative eigenvalues to zero
145+
ABS ///< Flip negative eigenvalues to positive
146+
};
142147

143148
/// @brief Matrix projection onto positive semi-definite cone
144149
/// @param A Symmetric matrix to project
150+
/// @param method PSD projection method
145151
/// @return Projected matrix
146152
template <
147153
typename _Scalar,

0 commit comments

Comments
 (0)