Entropyk/_bmad-output/implementation-artifacts/10-2-refrigerant-source-sink.md

196 lines
5.5 KiB
Markdown

# Story 10.2: RefrigerantSource et RefrigerantSink
**Epic:** 10 - Enhanced Boundary Conditions
**Priorité:** P0-CRITIQUE
**Estimation:** 3h
**Statut:** backlog
**Dépendances:** Story 10-1 (Nouveaux types physiques)
---
## Story
> En tant que moteur de simulation thermodynamique,
> Je veux que `RefrigerantSource` et `RefrigerantSink` implémentent le trait `Component`,
> Afin de pouvoir définir des conditions aux limites pour les fluides frigorigènes avec titre.
---
## Contexte
Les fluides frigorigènes (R410A, R134a, CO2, etc.) nécessitent des conditions aux limites spécifiques:
- Possibilité de spécifier le **titre** (vapor quality) au lieu de l'enthalpie
- Validation que le fluide est bien un réfrigérant
- Support des propriétés thermodynamiques via CoolProp
---
## Spécifications Techniques
### RefrigerantSource
```rust
/// Source pour fluides frigorigènes compressibles.
///
/// Impose une pression et une enthalpie (ou titre) fixées sur le port de sortie.
#[derive(Debug, Clone)]
pub struct RefrigerantSource {
/// Identifiant du fluide frigorigène (ex: "R410A", "R134a", "CO2")
fluid_id: String,
/// Pression de set-point [Pa]
p_set: Pressure,
/// Enthalpie de set-point [J/kg]
h_set: Enthalpy,
/// Titre optionnel (vapor quality, 0-1)
vapor_quality: Option<VaporQuality>,
/// Débit massique optionnel [kg/s]
mass_flow: Option<MassFlow>,
/// Port de sortie connecté
outlet: ConnectedPort,
}
impl RefrigerantSource {
/// Crée une source réfrigérant avec pression et enthalpie fixées.
pub fn new(
fluid_id: impl Into<String>,
pressure: Pressure,
enthalpy: Enthalpy,
outlet: ConnectedPort,
) -> Result<Self, ComponentError>;
/// Crée une source réfrigérant avec pression et titre fixés.
/// L'enthalpie est calculée automatiquement via CoolProp.
pub fn with_vapor_quality(
fluid_id: impl Into<String>,
pressure: Pressure,
vapor_quality: VaporQuality,
outlet: ConnectedPort,
) -> Result<Self, ComponentError>;
/// Définit le débit massique imposé.
pub fn set_mass_flow(&mut self, mass_flow: MassFlow);
}
```
### RefrigerantSink
```rust
/// Puits pour fluides frigorigènes compressibles.
///
/// Impose une contre-pression fixe sur le port d'entrée.
#[derive(Debug, Clone)]
pub struct RefrigerantSink {
/// Identifiant du fluide frigorigène
fluid_id: String,
/// Contre-pression [Pa]
p_back: Pressure,
/// Enthalpie de retour optionnelle [J/kg]
h_back: Option<Enthalpy>,
/// Port d'entrée connecté
inlet: ConnectedPort,
}
impl RefrigerantSink {
/// Crée un puits réfrigérant avec contre-pression fixe.
pub fn new(
fluid_id: impl Into<String>,
pressure: Pressure,
inlet: ConnectedPort,
) -> Result<Self, ComponentError>;
/// Définit une enthalpie de retour fixe.
pub fn set_return_enthalpy(&mut self, enthalpy: Enthalpy);
}
```
---
## Implémentation du Trait Component
```rust
impl Component for RefrigerantSource {
fn n_equations(&self) -> usize { 2 }
fn compute_residuals(&self, _state: &SystemState, residuals: &mut ResidualVector)
-> Result<(), ComponentError>
{
residuals[0] = self.outlet.pressure().to_pascals() - self.p_set.to_pascals();
residuals[1] = self.outlet.enthalpy().to_joules_per_kg() - self.h_set.to_joules_per_kg();
Ok(())
}
fn energy_transfers(&self, _state: &SystemState) -> Option<(Power, Power)> {
Some((Power::from_watts(0.0), Power::from_watts(0.0)))
}
fn port_enthalpies(&self, _state: &SystemState) -> Result<Vec<Enthalpy>, ComponentError> {
Ok(vec![self.h_set])
}
fn port_mass_flows(&self, _state: &SystemState) -> Result<Vec<MassFlow>, ComponentError> {
match self.mass_flow {
Some(mdot) => Ok(vec![MassFlow::from_kg_per_s(-mdot.to_kg_per_s())]),
None => Ok(vec![]),
}
}
}
```
---
## Fichiers à Créer/Modifier
| Fichier | Action |
|---------|--------|
| `crates/components/src/flow_boundary/mod.rs` | Créer module avec ré-exports |
| `crates/components/src/flow_boundary/refrigerant.rs` | Créer `RefrigerantSource`, `RefrigerantSink` |
| `crates/components/src/lib.rs` | Exporter les nouveaux types |
---
## Critères d'Acceptation
- [ ] `RefrigerantSource::new()` crée une source avec P et h fixées
- [ ] `RefrigerantSource::with_vapor_quality()` calcule l'enthalpie depuis le titre
- [ ] `RefrigerantSink::new()` crée un puits avec contre-pression
- [ ] `energy_transfers()` retourne `(Power(0), Power(0))`
- [ ] `port_enthalpies()` retourne `[h_set]`
- [ ] `port_mass_flows()` retourne le débit si spécifié
- [ ] Validation que le fluide est un réfrigérant valide
- [ ] Tests unitaires complets
---
## Tests Requis
```rust
#[cfg(test)]
mod tests {
#[test]
fn test_refrigerant_source_new() { /* ... */ }
#[test]
fn test_refrigerant_source_with_vapor_quality() { /* ... */ }
#[test]
fn test_refrigerant_source_energy_transfers_zero() { /* ... */ }
#[test]
fn test_refrigerant_source_port_enthalpies() { /* ... */ }
#[test]
fn test_refrigerant_sink_new() { /* ... */ }
#[test]
fn test_refrigerant_sink_with_return_enthalpy() { /* ... */ }
}
```
---
## Références
- [Architecture Document](../../plans/boundary-condition-refactoring-architecture.md)
- [Story 10-1: Nouveaux types physiques](./10-1-new-physical-types.md)