Skip to content

Consistency analysis regarding the index/indices behavior #6418

Open

Description

@damienmarchal

I just ask Mistral AI for an analysis of the different pattern in sofa regarding the initialization of Data<> d_indices.

I put here the summary to avoid consuming token asking the same question in the future.

Analysis of indices / index Data in SOFA Components

This report analyzes all components inheriting from BaseComponent that have Data fields named "indices" or "index" (members d_indices, d_index, f_indices, etc.). The goal is to identify naming, typing, and initialization pattern inconsistencies across components, with a view toward unification.

The analysis covers:

  • Data declaration (exposed name, member name, type)
  • Constructor initialization patterns
  • Initialization patterns in ::init() and ::reinit()
  • Auto-fill behaviors (full or partial)

1. Summary of Types Used

Local alias Actual type Family
VecId, VecIndex, SetIndex (ROI), SetIndices sofa::type::vector<sofa::Index> Simple Data<std::vector<Index>>
SetIndex (constraints), DataSetIndex, IndexSubsetData TopologySubsetIndices Data-derived class with topology support
PointSubsetData<IndexArray> TopologySubsetData<Point, IndexArray> Data-derived class with topology support
unsigned int, Index, sofa::Index scalar Data<scalar>

2. Components by Declaration Pattern

Pattern 1 — initData(&d_indices, "indices", "description") no default value

The most widespread pattern. Data named "indices", member d_indices, no explicit default value.

Projective Constraints (all use TopologySubsetIndices aliased as SetIndex or IndexSubsetData)

Component Member Actual type Init file
FixedProjectiveConstraint SetIndex d_indices TopologySubsetIndices .inl:39
FixedTranslationProjectiveConstraint SetIndex d_indices TopologySubsetIndices .inl:36
FixedPlaneProjectiveConstraint SetIndex d_indices TopologySubsetIndices .inl:45
PointProjectiveConstraint SetIndex d_indices TopologySubsetIndices .inl:40
PlaneProjectiveConstraint IndexSubsetData d_indices TopologySubsetIndices .inl:38
LineProjectiveConstraint IndexSubsetData d_indices TopologySubsetIndices .inl:38
DirectionProjectiveConstraint IndexSubsetData d_indices TopologySubsetIndices .inl:39
LinearMovementProjectiveConstraint SetIndex d_indices TopologySubsetIndices .inl:40
LinearVelocityProjectiveConstraint SetIndex d_indices TopologySubsetIndices .inl:41
PartialLinearMovementProjectiveConstraint SetIndex d_indices TopologySubsetIndices .inl:42
AffineMovementProjectiveConstraint SetIndex d_indices TopologySubsetIndices .inl:41
ParabolicProjectiveConstraint SetIndex d_indices TopologySubsetIndices .inl:34
HermiteSplineProjectiveConstraint SetIndex d_indices TopologySubsetIndices .inl:34

Lagrangian Constraints

Component Member Actual type Init file
FixedLagrangianConstraint SetIndex d_indices TopologySubsetIndices .inl:35
StopperLagrangianConstraint Data<vector<unsigned int>> d_indices Simple Data<vector<unsigned>> .inl:34

Mappings

Component Member Actual type Init file
SubsetMapping SetIndex d_indices PointSubsetData<IndexArray> (topological) .inl:32
DistanceFromTargetMapping Data<vector<unsigned>> d_indices Simple Data<vector<unsigned>> .inl:39

ForceFields / Mass

Component Member Actual type Init file
ConstantForceField SetIndex d_indices TopologySubsetIndices .inl:38
TorsionForceField Data<VecId> d_indices Simple Data<vector<PointId>> .inl:39
AngularSpringForceField Data<vector<Index>> d_indices Simple Data<vector<Index>> .inl:37
UniformMass DataSetIndex d_indices TopologySubsetIndices .inl:76

Engines (ROI / Select / Analyze / Generate)

Component Member Actual type Init file
BaseROI Data<SetIndex> d_indices Simple Data<vector<Index>> .inl:63
ComplementaryROI Data<SetIndex> d_indices Simple Data<vector<Index>> .inl:46
MeshBoundaryROI Data<SetIndex> d_indices Simple Data<vector<Index>> .cpp:39
MeshSubsetEngine Data<SetIndices> d_indices Simple Data<vector<Index>> .inl:37
SelectConnectedLabelsROI Data<vector<Index>> d_indices Simple Data<vector<Index>> .h:58 (inline)
SelectLabelROI Data<vector<Index>> d_indices Simple Data<vector<Index>> .h:69 (inline)
AverageCoord Data<VecIndex> d_indices Simple Data<vector<Index>> .inl:34
MeshClosingEngine Data<VecSeqIndex> indices Data<vector<vector<Index>>> (no d_ prefix) .h:82 (inline)

Pattern 2 — initData(&d_index, (unsigned)0, "index", "description") with default value 0

Data named "index" (singular), member d_index, scalar type, default value 0.

Component Member Type Default Init file
MechanicalStateController Data<unsigned int> d_index unsigned int (unsigned int)0 .inl:47
RigidMapping Data<sofa::Index> d_index sofa::Index (unsigned)0 .inl:121
DeformableOnRigidFrameMapping Data<unsigned int> d_index unsigned int (unsigned)0 .inl:34

Pattern 3 — INCONSISTENT: initData(&d_index, "indices", "description")

Member named d_index (singular) but Data named "indices" (plural), vector type.

Component Member Actual type Data name Init file
SkinningMapping Data<vector<SVector<unsigned int>>> d_index vector<vector<unsigned>> "indices" .inl:42

This is the only case where the member is d_index but the exposed name is "indices". All other components with a d_index member use the name "index".

Pattern 4 — initData(&f_indices, "indices", "description") f_ prefix (engines)

Data named "indices", member f_indices (engine convention: f_ = field/input), no default value.

Component Member Actual type Init file
PlaneROI Data<SetIndex> f_indices Data<vector<Index>> .inl:40
ProximityROI Data<SetIndex> f_indices Data<vector<Index>> .inl:41
PairBoxRoi Data<SetIndex> f_indices Data<vector<Index>> .inl:38
SubsetTopology Data<SetIndex> f_indices Data<vector<Index>> .inl:48
PointsFromIndices Data<SetIndex> f_indices Data<vector<Index>> .inl:32
ValuesFromIndices Data<VecIndex> f_indices Data<vector<Index>> .inl:32
IndicesFromValues Data<VecIndex> f_indices Data<vector<Index>> .inl:33
IndexValueMapper Data<vector<Index>> f_indices Data<vector<Index>> .inl:32
Indices2ValuesMapper Data<vector<Real>> f_indices Data<vector<Real>> .inl:36
MapIndices Data<VecIndex> f_indices Data<vector<Index>> .inl:32

Pattern 5 — this, "indices", "description", DataEngineInput/Output (new vectorData style)

Uses core::objectmodel::vectorData<T> with the modern constructor this, "name", "desc", DataEngineInput.

Component Member Type Data name Init file
ROIValueMapper vectorData<vector<Index>> f_indices vectorData<vector<Index>> "indices" .h:93
MergeROIs vectorData<vector<Index>> f_indices vectorData<vector<Index>> "indices" .h:65
MeshSplittingEngine vectorData<SetIndices> indices vectorData<vector<Index>> "indices" .inl:55

MeshSplittingEngine also has edgeIndices, triangleIndices, quadIndices, tetrahedronIndices, hexahedronIndices — all in vectorData<SetIndices>.

Pattern 6 — std::array<TopologySubsetIndices, 2> initialized in the declaration

Pattern unique to SpringForceField: a std::array of TopologySubsetIndices initialized directly in the member declaration.

Component Member Type File
SpringForceField std::array<TopologySubsetIndices, 2> d_springsIndices array of TopologySubsetIndices .h:148-151
// SpringForceField.h:148-151
std::array<sofa::core::topology::TopologySubsetIndices, 2> d_springsIndices
{
    sofa::core::topology::TopologySubsetIndices {initData(&d_springsIndices[0], "springsIndices1", "List of indices in springs from the first mstate", true, true)},
    sofa::core::topology::TopologySubsetIndices {initData(&d_springsIndices[1], "springsIndices2", "List of indices in springs from the second mstate", true, true)}
};

Note: the exposed names are "springsIndices1" and "springsIndices2", not "indices".

Pattern 7 — Data names different from "indices" / "index"

Component Member Exposed Data name Type Init file
SmoothMeshEngine Data<vector<unsigned int>> input_indices "input_indices" Data<vector<unsigned>> .inl:34
AttachProjectiveConstraint SetIndex f_indices1 "indices1" TopologySubsetIndices .inl:199
AttachProjectiveConstraint SetIndex f_indices2 "indices2" TopologySubsetIndices .inl:200
MergePoints f_indices1 "indices1" — .inl:34
MergePoints f_indices2 "indices2" — .inl:35
NearestPointROI f_indices1 "indices1" — .inl:37
NearestPointROI f_indices2 "indices2" — .inl:38

3. Initialization Patterns in ::init() / ::reinit()

Pattern A — createTopologyHandler(topology): topology linkage

Components using TopologySubsetIndices systematically call d_indices.createTopologyHandler(_topology) in init() to enable topology change tracking.

Component init() reinit()
FixedProjectiveConstraint createTopologyHandler + checkIndices() checkIndices()
PointProjectiveConstraint createTopologyHandler + validation std::sort(tmp) on indices
PlaneProjectiveConstraint createTopologyHandler + validation normalizes normal vector
LineProjectiveConstraint createTopologyHandler + validation updateJacobian()
DirectionProjectiveConstraint createTopologyHandler + validation normalize + build matrix
LinearMovementProjectiveConstraint createTopologyHandler + x0.resize(0) —
LinearVelocityProjectiveConstraint createTopologyHandler (indices + coordinates) —
PartialLinearMovementProjectiveConstraint createTopologyHandler + x0.resize(0) —
AffineMovementProjectiveConstraint createTopologyHandler + validation —
ParabolicProjectiveConstraint createTopologyHandler + projection computation reinit() calls init()
HermiteSplineProjectiveConstraint createTopologyHandler reinit() calls init()
FixedTranslationProjectiveConstraint createTopologyHandler (indices + coordinates) —
FixedPlaneProjectiveConstraint createTopologyHandler + selectVerticesAlongPlane() —
ConstantForceField createTopologyHandler + m_systemSize = topology->getNbPoints() — (callback updateFromIndices)
SubsetMapping createTopologyHandler if d_handleTopologyChange —
UniformMass createTopologyHandler + supportNewTopologyElements(true) + callback —

Pattern B — Auto-fill if indices not set (isSet() / empty())

When the user does not provide indices, the component auto-generates them (0..N-1).

Component Condition Action
StopperLagrangianConstraint if(!d_indices.isSet()) resize(x.size()) then indices[i] = i for all i
UniformMass if(indices.size()==0) after d_localRange check clear() then push_back(i) for i=0..N-1

Pattern C — Index generation from d_first/d_last or proximity search

Component Logic in init()
SubsetMapping If d_indices empty and d_first != InvalidID → generates [first..last]. Else if empty → proximity search with d_radius between in/out positions
// SubsetMapping.inl:76-90
if (d_indices.getValue().empty() && d_first.getValue() != sofa::InvalidID) {
    indices.resize(last-first+1);
    for (Index i=0; i<indices.size(); ++i)
        indices[i] = first+i;
}
else if (d_indices.getValue().empty()) {
    // proximity search with d_radius
}

Pattern D — Index extension/resizing

Component Logic in init()
DistanceFromTargetMapping If indices.size() < targetPositions.size() → push_back(i) to fill up to target size
SkinningMapping If d_index.size() != numChildren → updateWeights() auto-recomputes

Pattern E — Index validation (bounds)

Several ProjectiveConstraints validate that each index < mstate->getSize() and call removeConstraint(index) if invalid.

Component Validation function
FixedProjectiveConstraint checkIndices() (called in both init() and reinit())
PointProjectiveConstraint inline loop in init()
PlaneProjectiveConstraint inline loop in init()
LineProjectiveConstraint inline loop in init()
DirectionProjectiveConstraint inline loop in init()
AffineMovementProjectiveConstraint inline loop in init()
// Typical pattern (FixedProjectiveConstraint.inl:130+)
const SetIndexArray & indices = d_indices.getValue();
for (unsigned int i=0; i<indices.size(); ++i) {
    const Index index = indices[i];
    if (index >= maxIndex) {
        msg_error() << "Index " << index << " not valid!";
        removeConstraint(index);
    }
}

Pattern F — No index processing in init()/reinit()

Component Behavior
FixedLagrangianConstraint init() → just Inherit1::init(), no reinit()
MechanicalStateController d_index (scalar) used directly in applyController(), never initialized
DeformableOnRigidFrameMapping d_index (scalar) used directly in mappings, never initialized
RigidMapping d_index (scalar) used in getRigidIndex(), init() calls reinit() for d_points
TorsionForceField No init()/reinit() functions at all
AngularSpringForceField init()/reinit() exist but do not touch d_indices

Pattern G — Engines: setDirtyValue() in init(), update() in reinit()

All engines follow this pattern. Indices are computed in doUpdate(), not directly in init()/reinit().

Component init() reinit() Index processing
BaseROI auto-link d_positions — in children's doUpdate()
ComplementaryROI vd_setIndices.resize(d_nbSet), setDirtyValue() update() doUpdate(): indices.clear() + computation via std::set
PlaneROI auto-link f_X0, edges, triangles, setDirtyValue() update() in doUpdate()
ProximityROI auto-link f_X0, setDirtyValue() update() in doUpdate()
PairBoxRoi auto-link f_X0, setDirtyValue() update() in doUpdate()
SubsetTopology auto-link f_X0, topology, setDirtyValue() update() in doUpdate()
PointsFromIndices auto-link f_X, setDirtyValue() update() doUpdate(): reads f_indices
ValuesFromIndices f_outStr.setParent(&f_out), setDirtyValue() update() doUpdate(): reads f_indices
IndicesFromValues setDirtyValue() update() doUpdate(): indices.clear() + search
IndexValueMapper setDirtyValue() update() doUpdate(): reads f_indices
Indices2ValuesMapper setDirtyValue() update() doUpdate(): reads f_indices
MapIndices setDirtyValue() update() doUpdate(): reads f_indices
SmoothMeshEngine link topology, setDirtyValue() update() doUpdate(): reads input_indices, if empty → all points
AverageCoord SingleStateAccessor::init(), setDirtyValue() update() doUpdate(): reads d_indices, if empty → all
MeshClosingEngine (inline in .h) update() doUpdate(): oindices.clear() + loop generation
MeshBoundaryROI auto-link triangles/quads, setDirtyValue() update() doUpdate(): indices.clear() + boundary detection
SelectConnectedLabelsROI (inline in .h) — doUpdate(): indices.clear() + conditional push_back
SelectLabelROI (inline in .h) — doUpdate(): indices.clear() + conditional push_back

Pattern H — reinit() calls init() (or vice versa)

Component Relationship
ParabolicProjectiveConstraint reinit() → calls init()
HermiteSplineProjectiveConstraint reinit() → calls init()
AttachProjectiveConstraint init() → calls reinit()
SkinningMapping init() → calls reinit() at the end
RigidMapping init() → calls this->reinit()

Pattern I — Update via callback (DataTracker)

Component Mechanism
ConstantForceField addUpdateCallback("updateFromIndices", {&d_indices}, ...) in constructor → updateFromIndices() checks indicesSize > m_systemSize

Pattern J — d_index scalar: never initialized in init/reinit

Component Usage of d_index
MechanicalStateController Read directly in applyController(): unsigned int i = d_index.getValue()
DeformableOnRigidFrameMapping Read in apply(): (*inRigid)[d_index.getValue()] or [size-1-d_index]
RigidMapping Read in getRigidIndex(): returns d_index.getValue() or size-1-d_index

These components have no initialization logic for d_index in init()/reinit(). The constructor default value 0 is used as-is.


4. Auto-fill 0..N-1 (full or partial)

Full auto-fill 0..N-1 in init() / reinit()

Component Function Condition Code
StopperLagrangianConstraint init() if(!d_indices.isSet()) indices.resize(x.size()); for(i=0;i<x.size();i++) indices[i]=i;
UniformMass initDefaultImpl() (called by init()) if(indices.size()==0) (after d_localRange check) indices.clear(); for(i=0;i<mstate->getSize();i++) indices.push_back(i);

Partial auto-fill (sub-sequence) in init() / reinit()

Component Function Condition Generated sequence
UniformMass initDefaultImpl() d_localRange[0] >= 0 && d_localRange[1] > 0 && d_localRange[1]+1 < getSize() d_localRange[0] .. d_localRange[1]
SubsetMapping init() d_indices empty AND d_first != InvalidID d_first .. d_last (clamped to inSize)
DistanceFromTargetMapping init() indices.size() < targetPositions.size() Fills with i from indices.size() to targetPositions.size()-1 (sequential extension)

Default value [0] in the constructor (not in init()/reinit())

These components set index 0 as the sole default value in the constructor:

Component Member Code
FixedProjectiveConstraint d_indices d_indices.beginEdit()->push_back(0); d_indices.endEdit();
PointProjectiveConstraint d_indices same
PlaneProjectiveConstraint d_indices same
LineProjectiveConstraint d_indices same
DirectionProjectiveConstraint d_indices same
LinearMovementProjectiveConstraint d_indices same + comment // default to indice 0
LinearVelocityProjectiveConstraint d_indices same
PartialLinearMovementProjectiveConstraint d_indices same
FixedTranslationProjectiveConstraint d_indices same
BaseROI d_indices sofa::helper::getWriteOnlyAccessor(d_indices).push_back(0);
BoxROI d_indices same
PlaneROI f_indices f_indices.beginEdit()->push_back(0);
ProximityROI f_indices same
SubsetTopology f_indices same

Note: this is not a 0..N-1 auto-fill, but a default value [0] (a single index).

Equivalent behavior via d_fixAll (without filling the vector)

Component Mechanism Effect
FixedProjectiveConstraint d_fixAll=true Loop for(i=0; i<mstate->getSize(); i++) in projectMatrix()/projectResponse() — ignores d_indices
FixedLagrangianConstraint d_fixAll=true m_cid.resize(getSize()) and dofIdx = lineNumber — implicitly uses 0..N-1

Fallback "all indices" in doUpdate() (engines)

Component Member Condition Behavior
AverageCoord d_indices indices.empty() n = coord.size(), iterates coord[i] for i=0..n-1
SmoothMeshEngine input_indices indices.size()==0 Processes all points (loop over out.size())

Auto-fill in recomputeIndices() (internal member, not a Data)

Component Internal member Source Data Function
RestShapeSpringsForceField m_indices d_points recomputeIndices(): if m_indices.empty() → push_back(i) for i=0..N-1
PolynomialRestShapeSpringsForceField m_indices d_points same

These two components have no d_indices Data — the fill concerns an internal member m_indices computed from d_points.


5. Pattern Summary Table

Pattern Description Components
A createTopologyHandler(topology) 13 ProjectiveConstraints + ConstantForceField + SubsetMapping + UniformMass
B Auto-fill 0..N-1 if empty StopperLagrangianConstraint, UniformMass, AverageCoord
C Generation from first/last or proximity SubsetMapping
D Extension/resizing DistanceFromTargetMapping, SkinningMapping
E Bounds validation + removeConstraint 6 ProjectiveConstraints
F No processing in init/reinit FixedLagrangianConstraint, MechanicalStateController, DeformableOnRigidFrameMapping, RigidMapping, TorsionForceField, AngularSpringForceField
G Engine: setDirtyValue() / update() 18 engines
H reinit() ↔ init() (circular call) Parabolic, HermiteSpline, Attach, SkinningMapping, RigidMapping
I DataTracker callback ConstantForceField
J d_index scalar without init MechanicalStateController, DeformableOnRigidFrameMapping, RigidMapping

6. Key Inconsistencies Identified

Issue Affected components Detail
Member d_index but Data "indices" SkinningMapping Only case where singular member = plural name
Member d_indices but Data "index" None —
Member without d_ prefix MeshClosingEngine (indices) Data named "indices", member indices
Member input_indices, Data "input_indices" SmoothMeshEngine Full name differs
Two fields indices1/indices2 AttachProjectiveConstraint, MergePoints, NearestPointROI Two-index-set pattern
Scalar vs vector type MechanicalStateController, RigidMapping, DeformableOnRigidFrameMapping (scalar) vs all others (vector) d_index = scalar, d_indices = vector
Topological vs simple type Constraints + UniformMass + ConstantForceField + SubsetMapping (topological) vs StopperLagrangianConstraint + BaseROI + engines (simple Data<vector<Index>>) TopologySubsetIndices vs Data<vector<Index>>
vectorData vs Data ROIValueMapper, MergeROIs, MeshSplittingEngine (vectorData) vs all others (Data) Two different APIs for engines
Init in .h vs .inl vs .cpp SelectConnectedLabelsROI/SelectLabelROI/MeshClosingEngine (.h) vs MeshBoundaryROI (.cpp) vs others (.inl) Placement inconsistency
Two opposite semantics for "empty" StopperLagrangianConstraint + UniformMass (empty = all) vs ProjectiveConstraints (empty = none, except constructor default [0]) Radically different behavior if user touches nothing
Constructor default [0] vs auto-fill in init() ProjectiveConstraints ([0] in constructor, isSet()==true) vs StopperLagrangianConstraint (checks !isSet() in init()) "Not set" detection inconsistency
UniformMass has two priority levels d_localRange (partial) > full auto-fill > user indices User indices are overwritten by d_localRange if set
SubsetMapping has 3 sources of truth d_indices > d_first/d_last > proximity search Priority not documented in the Data
reinit() calls init() ParabolicProjectiveConstraint, HermiteSplineProjectiveConstraint Dangerous pattern (full reinitialization on every reinit)
init() calls reinit() AttachProjectiveConstraint, SkinningMapping, RigidMapping Unusual inverted logic
No reinit() at all FixedLagrangianConstraint, StopperLagrangianConstraint, MechanicalStateController, UniformMass, TorsionForceField —
d_index never validated MechanicalStateController, DeformableOnRigidFrameMapping, RigidMapping No check that index < mstate size

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions