Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
c81799b
Add initilisation of the advance anisotropic friction
antoinebou12 Jan 26, 2026
b711166
fix notebook
antoinebou12 Jan 26, 2026
238f64d
Merge branch 'main' into anisotropic_friction
Jan 27, 2026
daa4967
fix clang
Jan 27, 2026
7da5f45
Minor formatting improvements in test_anisotropic_friction.cpp for be…
Jan 27, 2026
044d520
Refactor anisotropic_mu_eff_dtau function to ensure finite results by…
Jan 27, 2026
822c7ed
Add anisotropic friction example script
Jan 27, 2026
ea6f654
remove example
Jan 27, 2026
2ead2c0
Refactor anisotropic_mu_eff_derivatives function to compute gradients…
Jan 27, 2026
2b759e4
Refactor anisotropic friction functions to unify naming conventions a…
Jan 27, 2026
cff3dbb
Refactor test cases in anisotropic friction tests for improved readab…
Jan 27, 2026
9b925ff
Refactor anisotropic friction notebook and tests for improved clarity…
antoinebou12 Jan 27, 2026
785d68c
Refactor anisotropic friction documentation and tests for clarity. Re…
antoinebou12 Jan 28, 2026
b5d6a6d
Add anisotropic friction model and documentation updates
Feb 4, 2026
cbbed82
Merge branch 'anisotropic_friction' of https://github.com/antoinebou1…
Feb 4, 2026
a9eef3a
Improvement to the quality clang and missing M_PI
Feb 4, 2026
40d2cdd
Update style guide and tools documentation for clang-format usage; im…
Feb 4, 2026
63e723b
Add missing tests no_contact_force_multiplier and edge cases
Feb 5, 2026
4ff3dc2
Fix clang and pipeine issues
Feb 5, 2026
2519f02
Update tests/src/tests/friction/test_force_jacobian.cpp
zfergus Feb 6, 2026
d8d466c
Update tests/src/tests/potential/test_friction_potential.cpp
zfergus Feb 6, 2026
0dcf561
Update tests/src/tests/friction/test_force_jacobian.cpp
zfergus Feb 6, 2026
b28d7e9
Fix formatting
zfergus Feb 6, 2026
5140c4a
Merge branch 'main' into anisotropic_friction
zfergus Feb 6, 2026
32acee0
Inline simple math function
zfergus Feb 6, 2026
adc9626
Refactor hessian method to avoid redundant anisotropic scaling calcul…
zfergus Feb 6, 2026
3b74176
Anisotropic friction handling by refining effective coefficient calcu…
Feb 10, 2026
e899d79
Fix merge requests
Feb 10, 2026
a88ac11
Merge main into anisotropic_friction; resolve test_force_jacobian con…
Feb 10, 2026
011132a
Clang format and comments
Feb 11, 2026
9bdb441
Fix build issues
Feb 11, 2026
1fc71c4
missing param
Feb 11, 2026
4a24adf
tangential velocity impact on friction force calculations.
Feb 11, 2026
d5ca5a0
Merge branch 'main' into anisotropic_friction
zfergus Feb 22, 2026
f36fd3b
Update docs
zfergus Feb 22, 2026
800e138
Refactor anisotropic friction handling in tangential potential
zfergus Feb 22, 2026
2bcb988
Merge branch 'main' into anisotropic_friction
zfergus Feb 22, 2026
22bde96
Add anisotropic mu gradient support to tangential
zfergus Feb 22, 2026
b0cbc89
Check gradient is -force in anisotropic test
zfergus Feb 22, 2026
06e48f7
Merge branch 'main' into anisotropic_friction
zfergus Mar 1, 2026
42b9946
Merge origin/main into anisotropic_friction; resolve test_force_jacob…
Apr 13, 2026
c0e2320
Anisotropic friction: lagged matchstick mu, API, tests, and docs
Apr 13, 2026
2bc622c
Merge branch 'anisotropic_friction' of https://github.com/antoinebou1…
Apr 13, 2026
aa028ba
Merge upstream/ipc-sim main; resolve references.bib (Erleben + main e…
Apr 13, 2026
92a66ea
Refactor anisotropic friction API and documentation
Apr 13, 2026
5def3c2
Refactor friction test cases for improved readability
Apr 13, 2026
d4b8063
Clang Format
Apr 13, 2026
0430354
Updated test cases to ensure consistency with the refactored code.
Apr 13, 2026
1dedbf5
Only lagged mode
Apr 13, 2026
55a1367
Small changes in docs and formating
Apr 15, 2026
ee1b1b3
anisotropic friction tests for numerical stability
Apr 15, 2026
2fec756
small fixes clang
Apr 15, 2026
483bba6
Changes based on the copilot review
Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion docs/source/cpp-api/friction.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Friction
========

.. seealso::

:doc:`/tutorials/advanced_friction` explains the friction model, the
static/kinetic transition, and anisotropic usage. Full derivation and
plots are in ``notebooks/anisotropic_friction_math.ipynb``.

Smooth Mollifier
----------------

Expand All @@ -20,4 +26,22 @@ Smooth :math:`\mu`
.. doxygenfunction:: smooth_mu_f1
.. doxygenfunction:: smooth_mu_f2
.. doxygenfunction:: smooth_mu_f1_over_x
.. doxygenfunction:: smooth_mu_f2_x_minus_mu_f1_over_x3
.. doxygenfunction:: smooth_mu_f2_x_minus_mu_f1_over_x3

Anisotropic Friction Helpers
-----------------------------

Effective friction follows an elliptical L2 projection (matchstick cone):
:math:`\mu_{\text{eff}} = \sqrt{(\mu_0 t_0)^2 + (\mu_1 t_1)^2}` with
:math:`t = \tau / \|\tau\|`. Use ``anisotropic_mu_eff_from_tau_aniso`` when you
have :math:`\tau_{\text{aniso}}` and need :math:`\mu_s`, :math:`\mu_k` for the
smooth transition; use ``anisotropic_mu_eff_f`` when you have the unit
direction. Zero ``mu_s_aniso`` and ``mu_k_aniso`` falls back to scalar
:math:`\mu_s`, :math:`\mu_k`. See :cite:t:`Erleben2019Matchstick` for the
Matchstick model; code: `erleben/matchstick <https://github.com/erleben/matchstick>`_.

.. doxygenfunction:: anisotropic_mu_eff_f
.. doxygenfunction:: anisotropic_mu_eff_f_dtau
.. doxygenfunction:: anisotropic_x_from_tau_aniso
.. doxygenfunction:: anisotropic_mu_eff_from_tau_aniso
.. doxygenfunction:: anisotropic_mu_eff_f_grad
7 changes: 7 additions & 0 deletions docs/source/developers/style_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ Code Formatting

We utilize `ClangFormat <https://clang.llvm.org/docs/ClangFormat.html>`_ to automate code formatting. Please format your code before pushing and/or creating a pull request.

The project uses the root ``.clang-format`` (80 columns, WebKit-based).
Under ``tests/``, ``tests/.clang-format`` inherits that style and sets
``SortIncludes: false``. CI runs clang-format 20; format with the same version
locally to avoid formatting check failures (e.g. ``clang-format -i`` using
version 20, or use the pre-commit hook from :doc:`developers/tools`).
clang-tidy uses the same style via ``FormatStyle: file`` (see ``.clang-tidy``).

Additionally, ensure that your code adheres to the project's linting rules. Use the provided linting tools to check for any issues before committing your changes.

Naming conventions
Expand Down
2 changes: 1 addition & 1 deletion docs/source/developers/tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tools for Developers
Using Pre-Commit Hooks
----------------------

Use the ``.pre-commit-config.yaml`` file to apply clang-format on before commits.
Use the ``.pre-commit-config.yaml`` file to apply clang-format before commits. Use clang-format version 20 (see :doc:`developers/style_guide`).

Steps:

Expand Down
19 changes: 18 additions & 1 deletion docs/source/python-api/friction.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Friction
========

.. seealso::

:doc:`/tutorials/advanced_friction` describes the friction model and
anisotropic usage. Additional anisotropic helpers are in :doc:`/cpp-api/friction`.

Smooth Mollifier
----------------

Expand All @@ -19,4 +24,16 @@ Smooth :math:`\mu`
.. autofunction:: ipctk.smooth_mu_f1
.. autofunction:: ipctk.smooth_mu_f2
.. autofunction:: ipctk.smooth_mu_f1_over_x
.. autofunction:: ipctk.smooth_mu_f2_x_minus_mu_f1_over_x3
.. autofunction:: ipctk.smooth_mu_f2_x_minus_mu_f1_over_x3

Anisotropic Friction Helpers
-----------------------------

``anisotropic_mu_eff_f`` and ``anisotropic_mu_eff_f_dtau`` implement the
elliptical L2 (matchstick) model (:cite:t:`Erleben2019Matchstick`). The C++
API provides ``anisotropic_mu_eff_from_tau_aniso``, ``anisotropic_mu_eff_f_grad``,
and related helpers; the solver uses them when you set anisotropic coefficients
on tangential collisions.

.. autofunction:: ipctk.anisotropic_mu_eff_f
.. autofunction:: ipctk.anisotropic_mu_eff_f_dtau
11 changes: 11 additions & 0 deletions docs/source/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,15 @@ @inproceedings{Cohen1995ICOLLIDE
publisher = {ACM},
address = {New York, NY, USA},
note = {\url{https://doi.org/10.1145/199404.199437}}
}
@article{Erleben2019Matchstick,
title = {The Matchstick Model for Anisotropic Friction Cones},
author = {Erleben, K. and Macklin, M. and Andrews, S. and Kry, P. G.},
year = 2019,
journal = {Computer Graphics Forum},
volume = 38,
number = 8,
pages = {1--12},
doi = {10.1111/cgf.13885},
note = {\url{https://github.com/erleben/matchstick}}
}
114 changes: 101 additions & 13 deletions docs/source/tutorials/advanced_friction.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Advanced Friction
=================

This tutorial covers some advanced features of friction in the IPC Toolkit, including
spatially varying coefficients of friction and separate coefficients of friction for static and kinetic (dynamic) friction.
This tutorial describes two advanced friction features: coefficients that vary
over the mesh and separate static and kinetic friction coefficients.

.. seealso::

Expand All @@ -18,7 +18,7 @@ Spatially Varying Coefficients of Friction

Spatially varying coefficient of friction is achieved by assigning coefficients to each vertex in the mesh. However, friction coefficients are not a material property and should instead be assigned to the contact pair. This feature will be replaced with a per-pair friction coefficient in a future release.

You can specify spatially varying coefficients of friction by passing an ``Eigen::VectorXd`` to ``TangentialCollisions::build``. Each entry in the vector corresponds to the coefficient of friction for a specific vertex in the mesh. This allows you to assign different friction coefficients to different parts of the mesh, enabling more realistic simulations of complex materials and surfaces.
You can specify spatially varying coefficients of friction by passing an ``Eigen::VectorXd`` to ``TangentialCollisions::build``. Each entry is the coefficient of friction for one vertex. You can assign different coefficients to different parts of the mesh (e.g. rubber in one region, plastic in another).

You can also provide an optional ``blend_mu`` parameter to blend the coefficient of friction on either side of the contact. The default behavior is to average the coefficients of friction on both sides, but you can specify a custom blending function if needed (e.g., multiplying them or taking the maximum or minimum).

Expand Down Expand Up @@ -79,7 +79,7 @@ where :math:`\lambda` is the contact force magnitude, :math:`T(x) \in \mathbb{R}
1 & \text{otherwise}
\end{cases}

where :math:`\epsilon_v` is a small constant (e.g., ``0.001``). The following plot show the behavior of the function :math:`f_1`:
where :math:`\epsilon_v` is a small constant (e.g., ``0.001``). The following plot shows the behavior of the function :math:`f_1`:

.. figure:: ../_static/img/f1.png
:align: center
Expand All @@ -95,7 +95,7 @@ To create a dissipative potential we integrate :math:`f_1` to obtain a smooth mo
\end{cases}


The following plot show the behavior of the function :math:`f_0`:
The following plot shows the behavior of the function :math:`f_0`:

.. figure:: /_static/img/f0.png
:align: center
Expand All @@ -105,7 +105,7 @@ The following plot show the behavior of the function :math:`f_0`:
Smooth :math:`\mu`
^^^^^^^^^^^^^^^^^^

When adding separate coefficients for static and kinetic friction, we need to maintain the :math:`C^1` continuity of the friction force. This lead us to define a smooth coefficient of friction :math:`\mu(y)` that transitions between the static and kinetic coefficients based on the magnitude of the relative velocity :math:`y = \|\mathbf{u}\|`. The smooth coefficient of friction is defined as
When adding separate coefficients for static and kinetic friction, we need to maintain the :math:`C^1` continuity of the friction force. This leads us to define a smooth coefficient of friction :math:`\mu(y)` that transitions between the static and kinetic coefficients based on the magnitude of the relative velocity :math:`y = \|\mathbf{u}\|`. The smooth coefficient of friction is defined as

.. math::
\mu(y) = \begin{cases}
Expand Down Expand Up @@ -158,13 +158,101 @@ While this approach provides a smooth transition between static and kinetic fric

If you have suggestions for improving this approach or alternative methods, please reach out on our `GitHub Discussions <https://github.com/ipc-sim/ipc-toolkit/discussions>`_.

Future Directions
-----------------
Anisotropic Friction
--------------------

.. seealso::

:doc:`/cpp-api/friction` and :doc:`/python-api/friction` for the anisotropic
helpers. The ``notebooks/anisotropic_friction_math.ipynb`` notebook has the
full derivation and plots.

.. tip::
:title: New Feature

Anisotropic friction uses direction-dependent coefficients (e.g. wood grain,
brushed surfaces).

You can set different friction coefficients along each tangent direction.
Wood (along vs. across the grain) and brushed metal are typical cases.

Anisotropic friction uses an elliptical L2 projection model. For a given
tangential velocity direction :math:`\mathbf{t} =
\boldsymbol{\tau} / \|\boldsymbol{\tau}\|`, the effective friction coefficient
is:

.. math::
\mu_{\text{eff}} = \sqrt{(\mu_0 t_0)^2 + (\mu_1 t_1)^2}

The IPC Toolkit is continuously evolving, and future releases may include:
where :math:`\mu_0` and :math:`\mu_1` are the friction coefficients along the
two tangent basis directions, and :math:`t_0` and :math:`t_1` are the
components of the unit direction vector. This formulation matches the matchstick
(elliptical Coulomb cone) model. See :cite:t:`Erleben2019Matchstick` (Computer
Graphics Forum, 2019; DOI 10.1111/cgf.13885). Code:
`erleben/matchstick <https://github.com/erleben/matchstick>`_.

- Anisotropic friction models that account for direction-dependent friction.
- Velocity-dependent friction models that adjust friction coefficients based on relative velocity magnitude.
- Rolling coefficients of friction for scenarios involving rolling contacts.
Usage
~~~~~

To use anisotropic friction, you can assign anisotropic friction coefficients
to each tangential collision after building the collisions:

.. md-tab-set::

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

.. code-block:: c++

ipc::TangentialCollisions tangential_collisions;
tangential_collisions.build(
collision_mesh, vertices, collisions, B, barrier_stiffness,
mu_s, mu_k);

// Assign anisotropic friction coefficients per collision
for (size_t i = 0; i < tangential_collisions.size(); ++i) {
// Higher friction in first tangent direction, lower in second
tangential_collisions[i].mu_s_aniso = Eigen::Vector2d(0.8, 0.4);
tangential_collisions[i].mu_k_aniso = Eigen::Vector2d(0.6, 0.3);
}

.. md-tab-item:: Python

.. code-block:: python

tangential_collisions = ipctk.TangentialCollisions()
tangential_collisions.build(
collision_mesh, vertices, collisions, B, barrier_stiffness,
mu_s, mu_k)

# Assign anisotropic friction coefficients per collision
for i in range(tangential_collisions.size()):
# Higher friction in first tangent direction, lower in second
tangential_collisions[i].mu_s_aniso = np.array([0.8, 0.4])
tangential_collisions[i].mu_k_aniso = np.array([0.6, 0.3])

Relationship with Other Anisotropy Mechanisms
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Two mechanisms are available:

1. **Velocity scaling** (``mu_aniso``): component-wise scaling of the
tangential velocity before friction; changes the effective speed in the
friction law.

2. **Direction-dependent coefficients** (``mu_s_aniso``, ``mu_k_aniso``):
different :math:`\mu` along each tangent direction.

Use one or both. When both are set, velocity scaling is applied first, then
direction-dependent :math:`\mu` from the scaled velocity direction.

Backward Compatibility
~~~~~~~~~~~~~~~~~~~~~~~

Default zero ``mu_s_aniso`` and ``mu_k_aniso`` means the solver uses the scalar
``mu_s`` and ``mu_k`` values, so existing setups keep working.

Future Directions
-----------------

We encourage community contributions to expand these advanced friction models. Feel free to submit pull requests with your improvements or open a discussion on GitHub to propose new features.
Planned or under discussion: velocity-dependent friction and rolling friction.
See the project's GitHub for current status or to contribute.
27,662 changes: 27,662 additions & 0 deletions notebooks/anisotropic_friction_math.ipynb

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions python/src/collisions/tangential/tangential_collision.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ void define_tangential_collision(py::module_& m)
.def_readwrite(
"mu_k", &TangentialCollision::mu_k,
"Ratio between normal and kinetic tangential forces (e.g., friction coefficient)")
.def_readwrite(
"mu_aniso", &TangentialCollision::mu_aniso,
"Tangential anisotropy scaling in the collision's tangent basis. "
"(1,1) = isotropic (default). Scales tau before evaluating friction.")
.def_readwrite(
"mu_s_aniso", &TangentialCollision::mu_s_aniso,
"Static friction ellipse axes (2D, one per tangent). Zero → scalar mu_s. Matchstick model (CGF 2019, DOI 10.1111/cgf.13885).")
.def_readwrite(
"mu_k_aniso", &TangentialCollision::mu_k_aniso,
"Kinetic friction ellipse axes (2D, one per tangent). Zero → scalar mu_k. Matchstick model (CGF 2019, DOI 10.1111/cgf.13885).")
.def_readwrite("weight", &TangentialCollision::weight, "Weight")
.def_property(
"weight_gradient",
Expand Down
34 changes: 34 additions & 0 deletions python/src/friction/smooth_mu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,38 @@ void define_smooth_mu(py::module_& m)
The value of the expression at y.
)ipc_Qu8mg5v7",
"y"_a, "mu_s"_a, "mu_k"_a, "eps_v"_a);

m.def(
"anisotropic_mu_eff_f", &anisotropic_mu_eff_f,
R"ipc_Qu8mg5v7(
Effective static and kinetic friction along a unit direction for the
elliptical (matchstick) model: μ_eff = sqrt((μ₀ t₀)² + (μ₁ t₁)²).
Matchstick model: Erleben et al., CGF 2019, DOI 10.1111/cgf.13885.

Parameters:
tau_dir: Unit 2D direction (tau / ||tau||).
mu_s_aniso: Static friction ellipse axes (2D).
mu_k_aniso: Kinetic friction ellipse axes (2D).

Returns:
(mu_s_eff, mu_k_eff) along tau_dir. (0, 0) if inputs are zero
(isotropic fallback).
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Python docstring for anisotropic_mu_eff_f describes (0,0) as an "isotropic fallback", but this function only evaluates the elliptical formula along a direction; it doesn't apply scalar μ_s/μ_k when axes are zero. To avoid confusing API users, please clarify that isotropic fallback is handled elsewhere (e.g., via anisotropic_mu_eff_from_tau_aniso / lagged collision coefficients) and that (0,0) is just the result for zero axes.

Suggested change
(mu_s_eff, mu_k_eff) along tau_dir. (0, 0) if inputs are zero
(isotropic fallback).
(mu_s_eff, mu_k_eff) along tau_dir. If the anisotropic axes are
zero, this function returns (0, 0), which is just the result of
evaluating the directional elliptical model with zero axes.
Isotropic fallback, when desired, is handled elsewhere by
higher-level anisotropic friction routines.

Copilot uses AI. Check for mistakes.
)ipc_Qu8mg5v7",
"tau_dir"_a, "mu_s_aniso"_a, "mu_k_aniso"_a);

m.def(
"anisotropic_mu_eff_f_dtau", &anisotropic_mu_eff_f_dtau,
R"ipc_Qu8mg5v7(
∂μ_eff/∂τ for the elliptical model (friction force Jacobians).
Matchstick model: Erleben et al., CGF 2019, DOI 10.1111/cgf.13885.

Parameters:
tau: Tangential velocity (2D) in the tangent plane.
mu_aniso: Ellipse axes (2D).
mu_eff: Effective μ from anisotropic_mu_eff_f (avoids recomputation).

Returns:
∂μ_eff/∂τ as 2D vector. Zero if ||tau|| ≈ 0 or mu_eff ≈ 0.
)ipc_Qu8mg5v7",
"tau"_a, "mu_aniso"_a, "mu_eff"_a);
}
19 changes: 19 additions & 0 deletions src/ipc/collisions/tangential/tangential_collision.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,28 @@ class TangentialCollision : virtual public CollisionStencil {
/// @brief Ratio between normal and kinetic tangential forces (e.g., friction coefficient)
double mu_k = 0;

/// @brief Anisotropic static friction coefficients (2D, one per tangent direction).
/// @note Zero vector → scalar mu_s (backward compatible). Elliptical model;
/// see ipc::friction::smooth_mu and Erleben et al., CGF 2019,
/// DOI 10.1111/cgf.13885.
Comment thread
zfergus marked this conversation as resolved.
Eigen::Vector2d mu_s_aniso = Eigen::Vector2d::Zero();

/// @brief Anisotropic kinetic friction coefficients (2D, one per tangent direction).
/// @note Zero vector → scalar mu_k (backward compatible). Elliptical model;
/// see ipc::friction::smooth_mu and Erleben et al., CGF 2019,
/// DOI 10.1111/cgf.13885.
Eigen::Vector2d mu_k_aniso = Eigen::Vector2d::Zero();

/// @brief Weight
double weight = 1;

/// @brief Tangential anisotropy scaling in the collision's tangent basis.
/// @note Default (1,1) preserves current isotropic behavior.
/// Requires a_i > 0. Values scale tau before friction evaluation.
/// Used with mu_s_aniso/mu_k_aniso by the elliptical model in
/// ipc::friction::smooth_mu.
Eigen::Vector2d mu_aniso = Eigen::Vector2d::Ones();
Comment on lines +121 to +127
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mu_aniso is documented as requiring a_i > 0, but there is currently no validation/enforcement. Negative or zero components can make the scaled-speed norm non-physical and can introduce undefined behavior in derivatives. Consider adding an assert/clamp when computing tau_aniso (or in update_lagged_anisotropic_friction_coefficients) to enforce strictly positive entries.

Copilot uses AI. Check for mistakes.

/// @brief Gradient of weight with respect to all DOF
Eigen::SparseVector<double> weight_gradient;

Expand Down
Loading
Loading