223 lines
6.5 KiB
Markdown
223 lines
6.5 KiB
Markdown
# Story 10.4: AirSource et AirSink avec Propriétés Psychrométriques
|
|
|
|
**Epic:** 10 - Enhanced Boundary Conditions
|
|
**Priorité:** P1-HIGH
|
|
**Estimation:** 4h
|
|
**Statut:** backlog
|
|
**Dépendances:** Story 10-1 (Nouveaux types physiques)
|
|
|
|
---
|
|
|
|
## Story
|
|
|
|
> En tant que moteur de simulation thermodynamique,
|
|
> Je veux que `AirSource` et `AirSink` supportent les propriétés psychrométriques,
|
|
> Afin de pouvoir simuler les côtés air des échangeurs de chaleur (évaporateurs, condenseurs).
|
|
|
|
---
|
|
|
|
## Contexte
|
|
|
|
Les composants côté air (évaporateur air/air, condenseur air/réfrigérant) nécessitent des conditions aux limites avec:
|
|
|
|
- **Température sèche** (dry bulb temperature)
|
|
- **Humidité relative** ou **température bulbe humide**
|
|
- Débit massique d'air
|
|
|
|
Ces propriétés sont essentielles pour:
|
|
- Calcul des échanges thermiques et massiques (condensation sur évaporateur)
|
|
- Dimensionnement des batteries froides/chaudes
|
|
- Simulation des pompes à chaleur air/air et air/eau
|
|
|
|
---
|
|
|
|
## Spécifications Techniques
|
|
|
|
### AirSource
|
|
|
|
```rust
|
|
/// Source pour air humide (côté air des échangeurs).
|
|
///
|
|
/// Impose les conditions de l'air entrant avec propriétés psychrométriques.
|
|
#[derive(Debug, Clone)]
|
|
pub struct AirSource {
|
|
/// Température sèche [K]
|
|
t_dry: Temperature,
|
|
/// Humidité relative [%]
|
|
rh: RelativeHumidity,
|
|
/// Température bulbe humide optionnelle [K]
|
|
t_wet_bulb: Option<Temperature>,
|
|
/// Pression atmosphérique [Pa]
|
|
pressure: Pressure,
|
|
/// Débit massique d'air sec optionnel [kg/s]
|
|
mass_flow: Option<MassFlow>,
|
|
/// Port de sortie connecté
|
|
outlet: ConnectedPort,
|
|
}
|
|
|
|
impl AirSource {
|
|
/// Crée une source d'air avec température sèche et humidité relative.
|
|
pub fn from_dry_bulb_rh(
|
|
temperature_dry: Temperature,
|
|
relative_humidity: RelativeHumidity,
|
|
pressure: Pressure,
|
|
outlet: ConnectedPort,
|
|
) -> Result<Self, ComponentError>;
|
|
|
|
/// Crée une source d'air avec températures sèche et bulbe humide.
|
|
/// L'humidité relative est calculée automatiquement.
|
|
pub fn from_dry_and_wet_bulb(
|
|
temperature_dry: Temperature,
|
|
temperature_wet_bulb: Temperature,
|
|
pressure: Pressure,
|
|
outlet: ConnectedPort,
|
|
) -> Result<Self, ComponentError>;
|
|
|
|
/// Définit le débit massique d'air sec.
|
|
pub fn set_mass_flow(&mut self, mass_flow: MassFlow);
|
|
|
|
/// Retourne l'enthalpie spécifique de l'air humide [J/kg_air_sec].
|
|
pub fn specific_enthalpy(&self) -> Result<Enthalpy, ComponentError>;
|
|
|
|
/// Retourne le rapport d'humidité (kg_vapeur / kg_air_sec).
|
|
pub fn humidity_ratio(&self) -> Result<f64, ComponentError>;
|
|
}
|
|
```
|
|
|
|
### AirSink
|
|
|
|
```rust
|
|
/// Puits pour air humide.
|
|
#[derive(Debug, Clone)]
|
|
pub struct AirSink {
|
|
/// Pression atmosphérique [Pa]
|
|
pressure: Pressure,
|
|
/// Température de retour optionnelle [K]
|
|
t_back: Option<Temperature>,
|
|
/// Port d'entrée connecté
|
|
inlet: ConnectedPort,
|
|
}
|
|
|
|
impl AirSink {
|
|
/// Crée un puits d'air à pression atmosphérique.
|
|
pub fn new(pressure: Pressure, inlet: ConnectedPort) -> Result<Self, ComponentError>;
|
|
|
|
/// Définit une température de retour fixe.
|
|
pub fn set_return_temperature(&mut self, temperature: Temperature);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Calculs Psychrométriques
|
|
|
|
### Formules Utilisées
|
|
|
|
```rust
|
|
/// Pression de saturation de vapeur d'eau (formule de Magnus-Tetens)
|
|
fn saturation_vapor_pressure(t: Temperature) -> Pressure {
|
|
// P_sat = 610.78 * exp(17.27 * T_celsius / (T_celsius + 237.3))
|
|
let t_c = t.to_celsius();
|
|
Pressure::from_pascals(610.78 * (17.27 * t_c / (t_c + 237.3)).exp())
|
|
}
|
|
|
|
/// Rapport d'humidité depuis humidité relative
|
|
fn humidity_ratio_from_rh(
|
|
rh: RelativeHumidity,
|
|
t_dry: Temperature,
|
|
p_atm: Pressure,
|
|
) -> f64 {
|
|
// W = 0.622 * (P_v / (P_atm - P_v))
|
|
// où P_v = RH * P_sat
|
|
let p_sat = saturation_vapor_pressure(t_dry);
|
|
let p_v = p_sat * rh.to_fraction();
|
|
0.622 * p_v.to_pascals() / (p_atm.to_pascals() - p_v.to_pascals())
|
|
}
|
|
|
|
/// Enthalpie spécifique de l'air humide
|
|
fn specific_enthalpy(t_dry: Temperature, w: f64) -> Enthalpy {
|
|
// h = 1.006 * T_celsius + W * (2501 + 1.86 * T_celsius) [kJ/kg]
|
|
let t_c = t_dry.to_celsius();
|
|
Enthalpy::from_joules_per_kg((1.006 * t_c + w * (2501.0 + 1.86 * t_c)) * 1000.0)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Fichiers à Créer/Modifier
|
|
|
|
| Fichier | Action |
|
|
|---------|--------|
|
|
| `crates/components/src/flow_boundary/air.rs` | Créer `AirSource`, `AirSink` |
|
|
| `crates/components/src/flow_boundary/mod.rs` | Ajouter ré-exports |
|
|
|
|
---
|
|
|
|
## Critères d'Acceptation
|
|
|
|
- [ ] `AirSource::from_dry_bulb_rh()` crée une source avec T sèche et HR
|
|
- [ ] `AirSource::from_dry_and_wet_bulb()` calcule HR depuis T bulbe humide
|
|
- [ ] `specific_enthalpy()` retourne l'enthalpie de l'air humide
|
|
- [ ] `humidity_ratio()` retourne le rapport d'humidité
|
|
- [ ] `AirSink::new()` crée un puits à pression atmosphérique
|
|
- [ ] `energy_transfers()` retourne `(Power(0), Power(0))`
|
|
- [ ] Validation de l'humidité relative (0-100%)
|
|
- [ ] Tests unitaires avec valeurs de référence ASHRAE
|
|
|
|
---
|
|
|
|
## Tests Requis
|
|
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
#[test]
|
|
fn test_air_source_from_dry_bulb_rh() { /* ... */ }
|
|
|
|
#[test]
|
|
fn test_air_source_from_wet_bulb() { /* ... */ }
|
|
|
|
#[test]
|
|
fn test_saturation_vapor_pressure() { /* ... */ }
|
|
|
|
#[test]
|
|
fn test_humidity_ratio_calculation() { /* ... */ }
|
|
|
|
#[test]
|
|
fn test_specific_enthalpy_calculation() { /* ... */ }
|
|
|
|
#[test]
|
|
fn test_air_source_psychrometric_consistency() {
|
|
// Vérifier que les calculs sont cohérents avec les tables ASHRAE
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Notes d'Implémentation
|
|
|
|
### Alternative: Utiliser CoolProp
|
|
|
|
CoolProp supporte l'air humide via:
|
|
```rust
|
|
// Air humide avec rapport d'humidité W
|
|
let fluid = format!("Air-W-{}", w);
|
|
PropsSI("H", "T", T, "P", P, &fluid)
|
|
```
|
|
|
|
Cependant, les formules analytiques (Magnus-Tetens) sont plus rapides et suffisantes pour la plupart des applications.
|
|
|
|
### Performance
|
|
|
|
Les calculs psychrométriques doivent être optimisés car ils sont appelés fréquemment dans les boucles de résolution. Éviter les allocations et utiliser des formules approchées si nécessaire.
|
|
|
|
---
|
|
|
|
## Références
|
|
|
|
- [Architecture Document](../../plans/boundary-condition-refactoring-architecture.md)
|
|
- [Story 10-1: Nouveaux types physiques](./10-1-new-physical-types.md)
|
|
- [ASHRAE Fundamentals - Psychrometrics](https://www.ashrae.org/)
|
|
- [CoolProp Humid Air](http://www.coolprop.org/fluid_properties/HumidAir.html)
|