9.3 KiB
9.3 KiB
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 queFlowSplitteretFlowMergerimplémententenergy_transfers()etport_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
// 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
// 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
FlowSplitter::energy_transfers()retourneSome((Power(0), Power(0)))FlowMerger::energy_transfers()retourneSome((Power(0), Power(0)))FlowSplitter::port_enthalpies()retourne[h_in, h_out1, h_out2, ...]FlowMerger::port_enthalpies()retourne[h_in1, h_in2, ..., h_out]- Gestion d'erreur si ports non connectés
- Tests unitaires passent
check_energy_balance()ne skip plus ces composants
Tests Requis
#[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
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 toFlowSplitterreturning[h_in, h_out1, h_out2, ...] - Add
port_enthalpies()method toFlowMergerreturning[h_in1, h_in2, ..., h_out] - Add
energy_transfers()method to both returningSome((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()- returnsSome((Power(0), Power(0)))(adiabatic) - ✅ Implemented
FlowMerger::energy_transfers()- returnsSome((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 |