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
- Add a
v_chain boolean field to SectionArrayInput and expose it through SectionArray.
- Wire the
ch_en_V → v_chain mapping in the RTE importer.
- 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.
- 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:
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.
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
dyis locked to 0.The raw RTE input format already carries this information in a
ch_en_Vcolumn, but it is currently silently discarded during import.Proposed changes
v_chainboolean field toSectionArrayInputand expose it throughSectionArray.ch_en_V→v_chainmapping in the RTE importer.v_chainintoMasksandNodesinside the balance model so thatdyis forced to 0 for V-chain nodes at each solver iteration.Step-by-step implementation
Step 1 —
SectionArrayInput: addv_chainfieldIn
src/mechaphlowers/entities/schemas.py, add an optional boolean column toSectionArrayInput:Default to
Falsefor backward compatibility with datasets that do not include this column.Step 2 —
SectionArray: exposev_chainIn
src/mechaphlowers/entities/arrays.py, ensurev_chainflows throughSectionArray.data. If the column is absent, default to a column ofFalse.Step 3 — RTE importer: map
ch_en_VIn
src/mechaphlowers/data/initializer/array_importer.py, add toImporterRte.translation_map_fr:Apply the French boolean encoding (
VRAI/FAUX) mapping the same waysuspensionis handled.Step 4 —
Masks: addis_v_chainmaskIn
src/mechaphlowers/core/models/balance/models/utils_model_ducloux.py, updateMasks.__init__()to accept and store thev_chainarray:No change needed to
compute_dx_dy_dz(): V-chain nodes still derivedzfromdxvia Pythagoras — onlydyis locked, not the geometry formula.Step 5 —
Nodes: clampdy = 0for V-chain nodesIn
src/mechaphlowers/core/models/balance/models/model_ducloux.py:Nodes.__init__(): acceptis_v_chainand pass it toself.masks.state_vectorsetter (or inupdate()): enforceself.dy[self.masks.is_v_chain] = 0after the solver writes back.Step 6 —
nodes_builder(): propagatev_chainfromSectionArrayIn
nodes_builder()at the bottom ofmodel_ducloux.py: extractv_chainfromsection_array.data, defaulting to all-False if the column is absent, and pass it intoNodes.Step 7 — Update docstrings and user documentation
v_chaininSectionArrayInputandSectionArray.docs/user_guide/ug_balance_engine.mdwith an explanation of V-chain support and a usage example.Step 8 — Tests
SectionArrayfixture with at least one V-chain and one standard suspension node.solve_adjustment(): assertdy == 0for all V-chain nodes.solve_change_state(): assertdyremains 0 for V-chain nodes.dyunder transversal wind load.v_chainstill load correctly (backward compatibility).ch_en_Vis 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
src/mechaphlowers/entities/schemas.pyv_chain: boolfield toSectionArrayInputsrc/mechaphlowers/entities/arrays.pyv_chaininSectionArray.data, default toFalseif absentsrc/mechaphlowers/data/initializer/array_importer.py"ch_en_V": "v_chain"toImporterRte.translation_map_fr; apply VRAI/FAUX mappingBalance model layer
src/mechaphlowers/core/models/balance/models/utils_model_ducloux.pyMasks.__init__(): addis_v_chainboolean arraysrc/mechaphlowers/core/models/balance/models/model_ducloux.pyNodes.__init__(): acceptis_v_chain, enforcedy=0instate_vectorsettersrc/mechaphlowers/core/models/balance/models/model_ducloux.pynodes_builder(): extractv_chainfromSectionArray, pass toNodesNo change required
IBalanceModelinterface: V-chain is internal to the model.BalanceEngine: constraint is handled entirely insideNodes.state_vectorformat/size: unchanged.compute_dx_dy_dz(): unchanged —dz = -sqrt(L² - dx² - dy²)naturally simplifies todz = -sqrt(L² - dx²)whendy = 0.Key constraint rule
For a V-chain suspension node:
dy = 0always.dzis derived fromdxviadz = -sqrt(L² - dx²). Onlydxis a free variable for the solver.