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

9.3 KiB
Raw Blame History

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

// 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() retourne Some((Power(0), Power(0)))
  • FlowMerger::energy_transfers() retourne Some((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 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