Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 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
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).
)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::smooth_mu and Erleben et al., CGF 2019,
/// DOI 10.1111/cgf.13885.
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::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::smooth_mu.
Eigen::Vector2d mu_aniso = Eigen::Vector2d::Ones();

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

Expand Down
Loading
Loading