Non-Holonomic Constraints

The nonholonomic module provides tools for systems with velocity-dependent constraints that cannot be integrated to position constraints.

Overview

Non-holonomic constraints involve velocities and cannot be written as \(f(q) = 0\). The module implements:

  • Pfaffian Constraints: Linear in velocities \(\sum a_i(q)\dot{q}_i = 0\)

  • Rolling Without Slipping: Wheels, balls, cylinders

  • Knife-Edge Constraint: Chaplygin sleigh dynamics

  • Appell’s Equations: Alternative formulation using acceleration energy

  • Maggi’s Equations: Projection method eliminating multipliers

Theory

Pfaffian Constraints

A non-holonomic constraint has the form:

\[\sum_i a_i(q) \dot{q}_i + b(q, t) = 0\]

If this cannot be integrated to \(f(q) = 0\), it is truly non-holonomic.

d’Alembert-Lagrange Equations

With constraints \(A \dot{q} = 0\), equations of motion become:

\[\frac{d}{dt}\frac{\partial L}{\partial \dot{q}_i} - \frac{\partial L}{\partial q_i} = \sum_j \lambda_j a_{ji}\]

where \(\lambda_j\) are Lagrange multipliers.

Appell’s Equations

Using the Gibbs-Appell function (acceleration energy):

\[S = \frac{1}{2} \sum_{i,j} M_{ij} \ddot{q}_i \ddot{q}_j\]

Appell’s equations are:

\[\frac{\partial S}{\partial \ddot{q}_j} = Q_j\]

This is often simpler for non-holonomic systems.

Usage Examples

Rolling Without Slipping

from mechanics_dsl.domains.classical import NonholonomicSystem, rolling_constraint
import sympy as sp

system = NonholonomicSystem()

# Ball rolling on surface
m = sp.Symbol('m', positive=True)
R = sp.Symbol('R', positive=True)

x = system.get_symbol('x')
theta = system.get_symbol('theta')
x_dot = system.get_symbol('x_dot')
theta_dot = system.get_symbol('theta_dot')

# Kinetic energy
I = sp.Rational(2, 5) * m * R**2  # Solid sphere
T = sp.Rational(1, 2) * m * x_dot**2 + sp.Rational(1, 2) * I * theta_dot**2

system.set_lagrangian(T)

# Rolling constraint: ẋ = R·θ̇
lam = system.add_rolling_constraint('x', 'theta', R)

# Derive equations
eom = system.derive_equations_of_motion(['x', 'theta'])

Chaplygin Sleigh (Knife-Edge)

from mechanics_dsl.domains.classical import NonholonomicSystem, knife_edge_constraint
import sympy as sp

system = NonholonomicSystem()

# Chaplygin sleigh: blade can only move along its direction
m = sp.Symbol('m', positive=True)
I = sp.Symbol('I', positive=True)  # Moment of inertia

x = system.get_symbol('x')
y = system.get_symbol('y')
theta = system.get_symbol('theta')  # Orientation
x_dot = system.get_symbol('x_dot')
y_dot = system.get_symbol('y_dot')
theta_dot = system.get_symbol('theta_dot')

# Kinetic energy
T = sp.Rational(1, 2) * m * (x_dot**2 + y_dot**2) + \
    sp.Rational(1, 2) * I * theta_dot**2

system.set_lagrangian(T)

# Knife-edge constraint: velocity along blade direction
# ẏ·cos(θ) - ẋ·sin(θ) = 0
lam = system.add_knife_edge_constraint('x', 'y', 'theta')

eom = system.derive_equations_of_motion(['x', 'y', 'theta'])

Constraint Matrix Form

from mechanics_dsl.domains.classical import NonholonomicSystem
import sympy as sp

system = NonholonomicSystem()

R = sp.Symbol('R', positive=True)

# Add multiple constraints
system.add_rolling_constraint('x1', 'theta1', R)
system.add_rolling_constraint('y1', 'phi1', R)

# Get constraint matrix A and vector b
# Constraints: A·q̇ + b = 0
A, b = system.get_constraint_matrix(['x1', 'theta1', 'y1', 'phi1'])

print("Constraint matrix A:")
print(A)

Using Appell’s Equations

from mechanics_dsl.domains.classical import AppellEquations
import sympy as sp

appell = AppellEquations()

m = sp.Symbol('m', positive=True)
x_dot = appell.get_symbol('x_dot')
y_dot = appell.get_symbol('y_dot')

# Kinetic energy
T = sp.Rational(1, 2) * m * (x_dot**2 + y_dot**2)

# Compute Gibbs-Appell function (acceleration energy)
S = appell.compute_acceleration_energy(T, ['x', 'y'])

print(f"Gibbs-Appell function: S = {S}")
# S = (1/2)*m*(ẍ² + ÿ²)

# Derive Appell's equations
Q = {'x': sp.Symbol('F_x'), 'y': sp.Symbol('F_y')}
eom = appell.derive_appell_equations(S, Q, ['x', 'y'])

Convenience Functions

from mechanics_dsl.domains.classical import rolling_constraint, knife_edge_constraint
import sympy as sp

R = sp.Symbol('R', positive=True)

# Create rolling constraint object directly
roll = rolling_constraint('x', 'theta', R)
print(f"Constraint: ẋ - R·θ̇ = 0")
print(f"Coefficients: {roll.coefficients}")

# Create knife-edge constraint object
knife = knife_edge_constraint('x', 'y', 'theta')
print(f"Knife-edge: -ẋ·sin(θ) + ẏ·cos(θ) = 0")

API Reference

Classes

class NonholonomicSystem

System with non-holonomic (velocity) constraints.

set_lagrangian(L)

Set the system Lagrangian.

add_constraint(constraint)

Add a NonholonomicConstraint object.

Returns:

Lagrange multiplier symbol

add_rolling_constraint(linear, angular, radius)

Add rolling without slipping: v = R·ω.

add_knife_edge_constraint(x, y, theta)

Add knife-edge constraint: ẏ·cos(θ) - ẋ·sin(θ) = 0.

derive_equations_of_motion(coordinates)

Derive d’Alembert-Lagrange equations with constraints.

get_constraint_matrix(coordinates)

Get constraint matrix A and vector b.

class NonholonomicConstraint

Represents a single non-holonomic constraint.

Parameters:
  • coefficients – Dict mapping coordinate to coefficient a_i(q)

  • inhomogeneous – The b(q,t) term (default 0)

  • name – Optional constraint name

as_matrix_form(coordinates)

Convert to matrix form A·q̇ + b = 0.

class AppellEquations

Appell’s equations using acceleration energy.

compute_acceleration_energy(T, coordinates)

Compute Gibbs-Appell function S from kinetic energy.

derive_appell_equations(S, forces, coordinates)

Derive ∂S/∂q̈ = Q equations.

class MaggiEquations

Maggi’s projection method.

project_equations(euler_lagrange, constraint_matrix, coordinates)

Project EL equations onto constraint manifold.

Functions

rolling_constraint(linear, angular, radius)

Create rolling without slipping constraint.

knife_edge_constraint(x, y, theta)

Create knife-edge (Chaplygin sleigh) constraint.

See Also