Entropyk/_bmad-output/implementation-artifacts/9-5-flow-splitter-merger-energy-methods.md

307 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Story 9.5: Complétion Epic 7 - FlowSplitter/FlowMerger Energy Methods
**Epic:** 9 - Coherence Corrections (Post-Audit)
**Priorité:** P1-CRITIQUE
**Estimation:** 4h
**Statut:** review
**Dépendances:** Story 9.2 (FluidId unification)
---
## Story
> En tant que moteur de simulation thermodynamique,
> Je veux que `FlowSplitter` et `FlowMerger` implémentent `energy_transfers()` et `port_enthalpies()`,
> Afin que les jonctions soient correctement prises en compte dans le bilan énergétique.
---
## Contexte
L'audit de cohérence a révélé que les composants de jonction (`FlowSplitter`, `FlowMerger`) implémentent `port_mass_flows()` mais **PAS** `energy_transfers()` ni `port_enthalpies()`.
**Conséquence** : Ces composants sont **ignorés silencieusement** dans `check_energy_balance()`.
---
## Problème Actuel
```rust
// crates/components/src/flow_junction.rs
// FlowSplitter et FlowMerger ont:
// - fn port_mass_flows() ✓
// MANQUE:
// - fn port_enthalpies() ✗
// - fn energy_transfers() ✗
```
---
## Solution Proposée
### Physique des jonctions
**FlowSplitter** (diviseur de flux) :
- Un port d'entrée, plusieurs ports de sortie
- Conservation du débit massique : ṁ_in = Σ ṁ_out
- Conservation de l'enthalpie (mélange non-mixing) : h_in = h_out (pour chaque branche)
- Pas de transfert thermique : Q = 0
- Pas de travail : W = 0
**FlowMerger** (collecteur de flux) :
- Plusieurs ports d'entrée, un port de sortie
- Conservation du débit massique : Σ ṁ_in = ṁ_out
- Bilan énergétique : ṁ_out × h_out = Σ (ṁ_in × h_in)
- Pas de transfert thermique : Q = 0
- Pas de travail : W = 0
### Implémentation
```rust
// crates/components/src/flow_junction.rs
impl Component for FlowSplitter {
// ... existing implementations ...
/// Retourne les enthalpies des ports (ordre: inlet, puis outlets).
fn port_enthalpies(
&self,
_state: &SystemState,
) -> Result<Vec<entropyk_core::Enthalpy>, ComponentError> {
let mut enthalpies = Vec::with_capacity(self.ports_outlet.len() + 1);
// Enthalpie du port d'entrée
let h_in = self.port_inlet.enthalpy()
.ok_or_else(|| ComponentError::MissingData {
component: self.name().to_string(),
data: "inlet enthalpy".to_string(),
})?;
enthalpies.push(h_in);
// Enthalpies des ports de sortie
for (i, outlet) in self.ports_outlet.iter().enumerate() {
let h_out = outlet.enthalpy()
.ok_or_else(|| ComponentError::MissingData {
component: self.name().to_string(),
data: format!("outlet {} enthalpy", i),
})?;
enthalpies.push(h_out);
}
Ok(enthalpies)
}
/// Retourne les transferts énergétiques du diviseur.
///
/// Un diviseur de flux est adiabatique:
/// - Q = 0 (pas d'échange thermique)
/// - W = 0 (pas de travail)
fn energy_transfers(&self, _state: &SystemState) -> Option<(Power, Power)> {
Some((Power::from_watts(0.0), Power::from_watts(0.0)))
}
}
impl Component for FlowMerger {
// ... existing implementations ...
/// Retourne les enthalpies des ports (ordre: inlets, puis outlet).
fn port_enthalpies(
&self,
_state: &SystemState,
) -> Result<Vec<entropyk_core::Enthalpy>, ComponentError> {
let mut enthalpies = Vec::with_capacity(self.ports_inlet.len() + 1);
// Enthalpies des ports d'entrée
for (i, inlet) in self.ports_inlet.iter().enumerate() {
let h_in = inlet.enthalpy()
.ok_or_else(|| ComponentError::MissingData {
component: self.name().to_string(),
data: format!("inlet {} enthalpy", i),
})?;
enthalpies.push(h_in);
}
// Enthalpie du port de sortie
let h_out = self.port_outlet.enthalpy()
.ok_or_else(|| ComponentError::MissingData {
component: self.name().to_string(),
data: "outlet enthalpy".to_string(),
})?;
enthalpies.push(h_out);
Ok(enthalpies)
}
/// Retourne les transferts énergétiques du collecteur.
///
/// Un collecteur de flux est adiabatique:
/// - Q = 0 (pas d'échange thermique)
/// - W = 0 (pas de travail)
fn energy_transfers(&self, _state: &SystemState) -> Option<(Power, Power)> {
Some((Power::from_watts(0.0), Power::from_watts(0.0)))
}
}
```
---
## Fichiers à Modifier
| Fichier | Action |
|---------|--------|
| `crates/components/src/flow_junction.rs` | Ajouter `port_enthalpies()` et `energy_transfers()` pour `FlowSplitter` et `FlowMerger` |
---
## Critères d'Acceptation
- [x] `FlowSplitter::energy_transfers()` retourne `Some((Power(0), Power(0)))`
- [x] `FlowMerger::energy_transfers()` retourne `Some((Power(0), Power(0)))`
- [x] `FlowSplitter::port_enthalpies()` retourne `[h_in, h_out1, h_out2, ...]`
- [x] `FlowMerger::port_enthalpies()` retourne `[h_in1, h_in2, ..., h_out]`
- [x] Gestion d'erreur si ports non connectés
- [x] Tests unitaires passent
- [x] `check_energy_balance()` ne skip plus ces composants
---
## Tests Requis
```rust
#[cfg(test)]
mod tests {
use super::*;
use entropyk_core::{Enthalpy, Power, MassFlow};
#[test]
fn test_flow_splitter_energy_transfers_zero() {
let splitter = create_test_splitter(2); // 1 inlet, 2 outlets
let state = SystemState::default();
let (heat, work) = splitter.energy_transfers(&state).unwrap();
assert_eq!(heat.to_watts(), 0.0);
assert_eq!(work.to_watts(), 0.0);
}
#[test]
fn test_flow_merger_energy_transfers_zero() {
let merger = create_test_merger(2); // 2 inlets, 1 outlet
let state = SystemState::default();
let (heat, work) = merger.energy_transfers(&state).unwrap();
assert_eq!(heat.to_watts(), 0.0);
assert_eq!(work.to_watts(), 0.0);
}
#[test]
fn test_flow_splitter_port_enthalpies_count() {
let splitter = create_test_splitter(3); // 1 inlet, 3 outlets
let state = SystemState::default();
let enthalpies = splitter.port_enthalpies(&state).unwrap();
// 1 inlet + 3 outlets = 4 enthalpies
assert_eq!(enthalpies.len(), 4);
}
#[test]
fn test_flow_merger_port_enthalpies_count() {
let merger = create_test_merger(3); // 3 inlets, 1 outlet
let state = SystemState::default();
let enthalpies = merger.port_enthalpies(&state).unwrap();
// 3 inlets + 1 outlet = 4 enthalpies
assert_eq!(enthalpies.len(), 4);
}
#[test]
fn test_flow_splitter_enthalpy_conservation() {
// Pour un splitter idéal: h_in = h_out1 = h_out2 = ...
let splitter = create_test_splitter_with_equal_enthalpies();
let state = SystemState::default();
let enthalpies = splitter.port_enthalpies(&state).unwrap();
let h_in = enthalpies[0];
for h_out in &enthalpies[1..] {
assert_relative_eq!(h_out.to_joules_per_kg(), h_in.to_joules_per_kg());
}
}
}
```
---
## Note sur le Bilan Énergétique des Jonctions
### FlowSplitter
```
Énergie entrante = ṁ_in × h_in
Énergie sortante = Σ ṁ_out_i × h_out_i
Pour un splitter idéal (non-mixing):
h_in = h_out_i (pour tout i)
ṁ_in = Σ ṁ_out_i
Bilan: Énergie_in = Énergie_out ✓
```
### FlowMerger
```
Énergie entrante = Σ ṁ_in_i × h_in_i
Énergie sortante = ṁ_out × h_out
Pour un merger idéal:
ṁ_out = Σ ṁ_in_i
h_out = Σ (ṁ_in_i × h_in_i) / ṁ_out (mélange adiabatique)
Bilan: Énergie_in = Énergie_out ✓
```
---
## Références
- [Epic 7 Story 7.2 - Energy Balance Validation](./7-2-energy-balance-validation.md)
- [Coherence Audit Report](./coherence-audit-remediation-plan.md)
---
## File List
| File | Action |
|------|--------|
| `crates/components/src/flow_junction.rs` | Modified - Added `port_enthalpies()` and `energy_transfers()` for `FlowSplitter` and `FlowMerger` |
---
## Dev Agent Record
### Implementation Plan
- Add `port_enthalpies()` method to `FlowSplitter` returning `[h_in, h_out1, h_out2, ...]`
- Add `port_enthalpies()` method to `FlowMerger` returning `[h_in1, h_in2, ..., h_out]`
- Add `energy_transfers()` method to both returning `Some((Power(0), Power(0)))` (adiabatic components)
- Add comprehensive unit tests for both methods
### Completion Notes
- ✅ Implemented `FlowSplitter::port_enthalpies()` - returns enthalpies from inlet and all outlet ports
- ✅ Implemented `FlowMerger::port_enthalpies()` - returns enthalpies from all inlet ports and outlet port
- ✅ Implemented `FlowSplitter::energy_transfers()` - returns `Some((Power(0), Power(0)))` (adiabatic)
- ✅ Implemented `FlowMerger::energy_transfers()` - returns `Some((Power(0), Power(0)))` (adiabatic)
- ✅ Added 6 new unit tests covering all acceptance criteria
- ✅ All 22 flow_junction tests pass
-`check_energy_balance()` will now include FlowSplitter/FlowMerger components
---
## Change Log
| Date | Change |
|------|--------|
| 2026-02-22 | Completed implementation of `energy_transfers()` and `port_enthalpies()` for FlowSplitter and FlowMerger |