ChSystem::RemoveRedundantConstraints does not refresh the constraint Jacobian before its QR decomposition

34 views
Skip to first unread message

Pieter Machtelinckx

unread,
Aug 4, 2026, 5:59:17 AM (14 days ago) Aug 4
to ProjectChrono

Hi,

While working on an automotive project with Chrono, I found what appears to be an issue in ChSystem::RemoveRedundantConstraints().

The function does not appear to refresh the solver-side ChConstraint Jacobians before performing its QR decomposition. In a freshly initialized system, the Jacobian rows for ChLinkLock-family constraints have not yet been loaded and contain zeros. After previous solver operations, they may instead contain stale or coincidentally current values.

As a result, on a freshly initialized system, valid independent constraints can be classified as linearly dependent.

Reproducer: I tested a double-wishbone suspension corner with:
  • 22 constraints

  • mobility 2

  • expected constraint rank: 22

The current call reports:

Rank: 1 Dependent constraints: 21

If I add the following immediately before calling RemoveRedundantConstraints():

sys.LoadConstraintJacobians();

the result becomes:

Rank: 22 Dependent constraints: 0

I also tested a deliberately duplicated constraint. Without loading the Jacobians first, the function reports all 22 constraints as dependent, so the single genuine redundancy cannot be distinguished from the spurious ones.

In this case, DoAssembly() returned ABSTOL_RESIDUAL without entering StateSolveCorrection(), so the Jacobians were not loaded as a side effect of assembly.

Suspected cause:

In ChSystem.cpp, around lines 1439–1448, RemoveRedundantConstraints() calls DescriptorPrepareInject(), resizes the matrix, and calls setZeroValues(), but does not call LoadConstraintJacobians() before extracting the constraint Jacobian matrix.

ChSystemDescriptor::PasteConstraintsJacobianMatrixInto() documents loaded constraint Jacobians as a precondition in ChSystemDescriptor.h, around lines 336–338.

By comparison, ChSystem::GetConstraintJacobianMatrix(), around lines 1387–1391, performs the operations in this order:

DescriptorPrepareInject(); LoadConstraintJacobians(); descriptor->PasteConstraintsJacobianMatrixInto(...);

Neither Update() path appears to load the solver-side Jacobians, and I could not find a demo, test, or internal caller that guarantees they have already been loaded before RemoveRedundantConstraints() is called.

Would this be considered intentional behavior, with the caller expected to load the Jacobians first, or is it a bug?

A possible one-line fix would be to add:

LoadConstraintJacobians();

immediately after DescriptorPrepareInject() inside RemoveRedundantConstraints().

This ensures that the QR decomposition operates on the current constraint Jacobians rather than zero or previously stored values.

Reply all
Reply to author
Forward
0 new messages