Skip to content

feat: add V-chain support (2 DOF) to balance engine #462

Description

@adriengoeller

Context

Currently all suspension supports use a standard insulator chain with 3 degrees of freedom (dx, dy, dz). A V-chain is a different hardware configuration where the two chain legs form a V shape, constraining lateral movement: only longitudinal (x) and vertical (z) displacements are free. The transversal displacement dy is locked to 0.

The raw RTE input format already carries this information in a ch_en_V column, but it is currently silently discarded during import.

Proposed changes

  1. Add a v_chain boolean field to SectionArrayInput and expose it through SectionArray.
  2. Wire the ch_en_Vv_chain mapping in the RTE importer.
  3. Propagate v_chain into Masks and Nodes inside the balance model so that dy is forced to 0 for V-chain nodes at each solver iteration.
  4. Apply the constraint only to suspension supports (anchor nodes are unaffected).

Step-by-step implementation

Step 1 — SectionArrayInput: add v_chain field

In src/mechaphlowers/entities/schemas.py, add an optional boolean column to SectionArrayInput:

v_chain: Optional[pdt.Series[bool]] = pa.Field(nullable=True)

Default to False for backward compatibility with datasets that do not include this column.

Step 2 — SectionArray: expose v_chain

In src/mechaphlowers/entities/arrays.py, ensure v_chain flows through SectionArray.data. If the column is absent, default to a column of False.

Step 3 — RTE importer: map ch_en_V

In src/mechaphlowers/data/initializer/array_importer.py, add to ImporterRte.translation_map_fr:

"ch_en_V": "v_chain",

Apply the French boolean encoding (VRAI/FAUX) mapping the same way suspension is handled.

Step 4 — Masks: add is_v_chain mask

In src/mechaphlowers/core/models/balance/models/utils_model_ducloux.py, update Masks.__init__() to accept and store the v_chain array:

self.is_v_chain = v_chain  # boolean array, True for V-chain suspension nodes

No change needed to compute_dx_dy_dz(): V-chain nodes still derive dz from dx via Pythagoras — only dy is locked, not the geometry formula.

Step 5 — Nodes: clamp dy = 0 for V-chain nodes

In src/mechaphlowers/core/models/balance/models/model_ducloux.py:

  • Nodes.__init__(): accept is_v_chain and pass it to self.masks.
  • In the state_vector setter (or in update()): enforce self.dy[self.masks.is_v_chain] = 0 after the solver writes back.
  • The state vector keeps its current format and size — dy entries for V-chain nodes stay in the vector for coherence, but are overridden to 0 after each iteration.

Step 6 — nodes_builder(): propagate v_chain from SectionArray

In nodes_builder() at the bottom of model_ducloux.py: extract v_chain from section_array.data, defaulting to all-False if the column is absent, and pass it into Nodes.

Step 7 — Update docstrings and user documentation

  • Document v_chain in SectionArrayInput and SectionArray.
  • Update docs/user_guide/ug_balance_engine.md with an explanation of V-chain support and a usage example.

Step 8 — Tests

  • A SectionArray fixture with at least one V-chain and one standard suspension node.
  • After solve_adjustment(): assert dy == 0 for all V-chain nodes.
  • After solve_change_state(): assert dy remains 0 for V-chain nodes.
  • Assert non-V-chain nodes have non-zero dy under transversal wind load.
  • Verify datasets without v_chain still load correctly (backward compatibility).
  • Verify ch_en_V is correctly parsed from the RTE importer.

Technical impact analysis

Choices to do

The compute_dx_dy_dz() have the geometrical rule.
The cleanest design is to process different geometrical rule depending on the chain type. But it is easier to code it as dy = 0 for the moment. Moreover, the performance time could be impacted by this choice. To keep the logic, we should have new masks to separate chain and V chain and apply 2 different rules on it.

Data layer

File Change
src/mechaphlowers/entities/schemas.py Add optional v_chain: bool field to SectionArrayInput
src/mechaphlowers/entities/arrays.py Expose v_chain in SectionArray.data, default to False if absent
src/mechaphlowers/data/initializer/array_importer.py Add "ch_en_V": "v_chain" to ImporterRte.translation_map_fr; apply VRAI/FAUX mapping

Balance model layer

File Change
src/mechaphlowers/core/models/balance/models/utils_model_ducloux.py Masks.__init__(): add is_v_chain boolean array
src/mechaphlowers/core/models/balance/models/model_ducloux.py Nodes.__init__(): accept is_v_chain, enforce dy=0 in state_vector setter
src/mechaphlowers/core/models/balance/models/model_ducloux.py nodes_builder(): extract v_chain from SectionArray, pass to Nodes

No change required

  • IBalanceModel interface: V-chain is internal to the model.
  • BalanceEngine: constraint is handled entirely inside Nodes.
  • state_vector format/size: unchanged.
  • Anchor nodes: unaffected.
  • compute_dx_dy_dz(): unchanged — dz = -sqrt(L² - dx² - dy²) naturally simplifies to dz = -sqrt(L² - dx²) when dy = 0.

Key constraint rule

For a V-chain suspension node: dy = 0 always. dz is derived from dx via dz = -sqrt(L² - dx²). Only dx is a free variable for the solver.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    Status
    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions