chore: remove deprecated flow_boundary and update docs to match new architecture

This commit is contained in:
Sepehr
2026-03-01 20:00:09 +01:00
parent 20700afce8
commit d88914a44f
105 changed files with 11222 additions and 2994 deletions

View File

@@ -0,0 +1,642 @@
# Exemple Détaillé : Chiller Air-Glycol 2 Circuits avec Screw Économisé + MCHX
## Vue d'ensemble
Ce document détaille la conception et l'implémentation d'un chiller air-glycol complet dans Entropyk, incluant:
- **2 circuits réfrigérants** indépendants
- **Compresseurs Screw économisés** avec contrôle VFD (2560 Hz)
- **Condenseurs MCHX** (Microchannel Heat Exchanger) à air ambiant (35°C)
- **Évaporateurs flooded** avec eau glycolée MEG 35% (entrée 12°C, sortie 7°C)
---
## 1. Architecture du Système
### 1.1 Topologie par Circuit
```
┌─────────────────────────────────────────────────────────────────────┐
│ CIRCUIT N (×2) │
│ │
│ BrineSource(MEG35%, 12°C) │
│ ↓ │
│ ┌─────────────────┐ │
│ │ FloodedEvaporator│ ←── Drum ←── Economizer(flash) │
│ └────────┬────────┘ ↑ │
│ │ │ │
│ ↓ │ │
│ ┌─────────────────────────────┐ │ │
│ │ ScrewEconomizerCompressor │────────┘ │
│ │ (suction, discharge, eco) │ │
│ └────────────┬────────────────┘ │
│ │ │
│ ↓ │
│ ┌────────────────────┐ │
│ │ FlowSplitter (1→2) │ │
│ └────────┬───────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ ↓ ↓ │
│ ┌─────────┐ ┌─────────┐ │
│ │MchxCoil │ │MchxCoil │ ← 2 coils par circuit │
│ │ +Fan │ │ +Fan │ (4 coils total pour 2 circuits) │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ └─────┬─────┘ │
│ ↓ │
│ ┌────────────────────┐ │
│ │ FlowMerger (2→1) │ │
│ └────────┬───────────┘ │
│ │ │
│ ↓ │
│ ┌────────────────────┐ │
│ │ ExpansionValve │ │
│ └────────┬───────────┘ │
│ │ │
│ ↓ │
│ BrineSink(MEG35%, 7°C) │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
### 1.2 Spécifications Techniques
| Paramètre | Valeur | Unité |
|-----------|--------|-------|
| Réfrigérant | R134a | - |
| Nombre de circuits | 2 | - |
| Capacité nominale | 400 | kW |
| Air ambiant | 35 | °C |
| Entrée glycol | 12 | °C |
| Sortie glycol | 7 | °C |
| Type glycol | MEG 35% | - |
| Condenseurs | 4 × MCHX | 15 kW/K chacun |
| Compresseurs | 2 × Screw économisé | ~200 kW/circuit |
| VFD | 2560 | Hz |
---
## 2. Composants Principaux
### 2.1 ScrewEconomizerCompressor
#### 2.1.1 Description Physique
Un compresseur à vis avec port d'injection économiseur opère en deux étages de compression internes:
```
Stage 1:
Suction (P_evap, h_suc) → compression vers P_intermediate
Injection Intermédiaire:
Flash-gas depuis l'économiseur à (P_eco, h_eco) s'injecte dans les lobes
du rotor à la pression intermédiaire. Ceci refroidit le gaz comprimé et
augmente le débit total délivré au stage 2.
Stage 2:
Gaz mélangé (P_intermediate, h_mix) → compression vers P_discharge
Résultat net:
- Capacité supérieure vs. simple mono-étage (~10-20%)
- Meilleur COP (~8-15%) pour mêmes températures condensation/évaporation
- Gamme de fonctionnement étendue (ratios compression plus élevés)
```
#### 2.1.2 Ports (3 total)
| Port | Type | Description |
|------|------|-------------|
| `port_suction` | Entrée | Fluide basse pression depuis évaporateur/drum |
| `port_discharge` | Sortie | Fluide haute pression vers condenseur |
| `port_economizer` | Entrée | Injection flash-gas à pression intermédiaire |
#### 2.1.3 Variables d'État (5 total)
| Index | Variable | Unité | Description |
|-------|----------|-------|-------------|
| 0 | `ṁ_suction` | kg/s | Débit massique aspiration |
| 1 | `ṁ_eco` | kg/s | Débit massique économiseur |
| 2 | `h_suction` | J/kg | Enthalpie aspiration |
| 3 | `h_discharge` | J/kg | Enthalpie refoulement |
| 4 | `W_shaft` | W | Puissance arbre |
#### 2.1.4 Équations (5 total)
```rust
// Équation 1: Débit aspiration (courbe fabricant)
r[0] = _suc_calc(SST, SDT) × (freq/50) - _suction_state
// Équation 2: Débit économiseur
r[1] = x_eco × _suction - _eco_state
// Équation 3: Bilan énergétique (adiabatique)
// ṁ_suc × h_suc + ṁ_eco × h_eco + W/η = ṁ_total × h_dis
let _total = _suc + _eco;
r[2] = _suc × h_suc + _eco × h_eco + W/η_mech - _total × h_dis
// Équation 4: Pression économiseur (moyenne géométrique)
r[3] = P_eco - sqrt(P_suc × P_dis)
// Équation 5: Puissance (courbe fabricant)
r[4] = W_calc(SST, SDT) × (freq/50) - W_state
```
#### 2.1.5 Courbes de Performance
```rust
// Exemple: ~200 kW screw R134a à 50 Hz
// SST reference: +3°C = 276.15 K
// SDT reference: +50°C = 323.15 K
fn make_screw_curves() -> ScrewPerformanceCurves {
ScrewPerformanceCurves::with_fixed_eco_fraction(
// ṁ_suc [kg/s] = 1.20 + 0.003×(SST-276) - 0.002×(SDT-323) + 1e-5×(SST-276)×(SDT-323)
Polynomial2D::bilinear(1.20, 0.003, -0.002, 0.000_01),
// W [W] = 55000 + 200×(SST-276) - 300×(SDT-323) + 0.5×...
Polynomial2D::bilinear(55_000.0, 200.0, -300.0, 0.5),
0.12, // 12% fraction économiseur
)
}
```
#### 2.1.6 Contrôle VFD
```rust
// Le ratio de fréquence affecte linéairement le débit
let frequency_ratio = frequency_hz / nominal_frequency_hz; // ex: 40/50 = 0.8
// Scaling:
// ṁ_suc ∝ frequency_ratio
// W ∝ frequency_ratio
// x_eco = constant (géométrie fixe)
comp.set_frequency_hz(40.0).unwrap();
assert!((comp.frequency_ratio() - 0.8).abs() < 1e-10);
```
#### 2.1.7 Création du Composant
```rust
use entropyk_components::{ScrewEconomizerCompressor, ScrewPerformanceCurves, Polynomial2D};
use entropyk_components::port::{Port, FluidId};
use entropyk_core::{Pressure, Enthalpy};
// Créer les 3 ports connectés
let suc = make_port("R134a", 3.2, 400.0); // P=3.2 bar, h=400 kJ/kg
let dis = make_port("R134a", 12.8, 440.0); // P=12.8 bar, h=440 kJ/kg
let eco = make_port("R134a", 6.4, 260.0); // P=6.4 bar (intermédiaire)
let comp = ScrewEconomizerCompressor::new(
make_screw_curves(),
"R134a",
50.0, // fréquence nominale
0.92, // rendement mécanique
suc,
dis,
eco,
).expect("compressor creation ok");
assert_eq!(comp.n_equations(), 5);
```
---
### 2.2 MchxCondenserCoil
#### 2.2.1 Description Physique
Un MCHX (Microchannel Heat Exchanger) utilise des tubes plats en aluminium extrudé multi-port avec une structure d'ailettes louvrées. Comparé aux condenseurs conventionnels (RTPF):
| Propriété | RTPF | MCHX |
|-----------|------|------|
| UA côté air | Base | +3060% par m² |
| Charge réfrigérant | Base | 2540% |
| Perte de charge air | Base | Similaire |
| Poids | Base | 30% |
| Sensibilité distribution air | Moins | Plus |
#### 2.2.2 Modèle UA Variable
```text
UA_eff = UA_nominal × (ρ_air / ρ_ref)^0.5 × (fan_speed)^n_air
où:
ρ_air = densité air à T_amb [kg/m³]
ρ_ref = densité air de référence (1.12 kg/m³ à 35°C)
n_air = 0.5 (ASHRAE louvered fins)
```
#### 2.2.3 Effet de la Vitesse Ventilateur
```rust
// À 100% vitesse ventilateur
coil.set_fan_speed_ratio(1.0);
let ua_100 = coil.ua_effective(); // = UA_nominal
// À 70% vitesse
coil.set_fan_speed_ratio(0.70);
let ua_70 = coil.ua_effective();
// UA_70 ≈ UA_nom × √0.70 ≈ UA_nom × 0.837
// À 60% vitesse
coil.set_fan_speed_ratio(0.60);
let ua_60 = coil.ua_effective();
// UA_60 ≈ UA_nom × √0.60 ≈ UA_nom × 0.775
```
#### 2.2.4 Effet de la Température Ambiante
```rust
// À 35°C (design)
let coil_35 = MchxCondenserCoil::for_35c_ambient(15_000.0, 0);
let ua_35 = coil_35.ua_effective();
// À 45°C (ambiante élevée)
let mut coil_45 = MchxCondenserCoil::for_35c_ambient(15_000.0, 0);
coil_45.set_air_temperature_celsius(45.0);
let ua_45 = coil_45.ua_effective();
// UA diminue avec la température (densité air diminue)
// Ratio ≈ ρ(45°C)/ρ(35°C) ≈ 1.109/1.12 ≈ 0.99
assert!(ua_45 < ua_35);
```
#### 2.2.5 Création d'une Banque de 4 Coils
```rust
// 4 coils, 15 kW/K chacun
let coils: Vec<MchxCondenserCoil> = (0..4)
.map(|i| MchxCondenserCoil::for_35c_ambient(15_000.0, i))
.collect();
let total_ua: f64 = coils.iter().map(|c| c.ua_effective()).sum();
// ≈ 60 kW/K total
// Simulation anti-override: réduire coil 0 à 70%
coils[0].set_fan_speed_ratio(0.70);
```
---
### 2.3 FloodedEvaporator
#### 2.3.1 Description
L'évaporateur noyé (flooded) maintient un niveau de liquide constant dans la calandre. Le réfrigérant bout à la surface des tubes où circule le fluide secondaire (eau glycolée).
#### 2.3.2 Configuration JSON
```json
{
"type": "FloodedEvaporator",
"name": "evap_0",
"ua": 20000.0,
"refrigerant": "R134a",
"secondary_fluid": "MEG",
"target_quality": 0.7
}
```
#### 2.3.3 Bilan Énergétique
```text
Q_evap = ṁ_ref × (h_out - h_in) (côté réfrigérant)
Q_evap = ṁ_brine × Cp_brine × ΔT_brine (côté secondaire)
où:
ṁ_brine = débit glycol MEG 35% [kg/s]
Cp_brine ≈ 3.6 kJ/(kg·K) à 10°C
ΔT_brine = T_in - T_out = 12 - 7 = 5 K
```
---
## 3. Configuration JSON Complète
### 3.1 Structure du Fichier
```json
{
"name": "Chiller Air-Glycol 2 Circuits",
"description": "Machine frigorifique 2 circuits indépendants",
"fluid": "R134a",
"circuits": [
{
"id": 0,
"components": [ ... ],
"edges": [ ... ]
},
{
"id": 1,
"components": [ ... ],
"edges": [ ... ]
}
],
"solver": {
"strategy": "fallback",
"max_iterations": 150,
"tolerance": 1e-6
},
"metadata": { ... }
}
```
### 3.2 Circuit 0 Détaillé
```json
{
"id": 0,
"components": [
{
"type": "ScrewEconomizerCompressor",
"name": "screw_0",
"fluid": "R134a",
"nominal_frequency_hz": 50.0,
"mechanical_efficiency": 0.92,
"economizer_fraction": 0.12,
"mf_a00": 1.20, "mf_a10": 0.003, "mf_a01": -0.002, "mf_a11": 0.00001,
"pw_b00": 55000.0, "pw_b10": 200.0, "pw_b01": -300.0, "pw_b11": 0.5,
"p_suction_bar": 3.2, "h_suction_kj_kg": 400.0,
"p_discharge_bar": 12.8, "h_discharge_kj_kg": 440.0,
"p_eco_bar": 6.4, "h_eco_kj_kg": 260.0
},
{
"type": "MchxCondenserCoil",
"name": "mchx_0a",
"ua": 15000.0,
"coil_index": 0,
"n_air": 0.5,
"t_air_celsius": 35.0,
"fan_speed_ratio": 1.0
},
{
"type": "MchxCondenserCoil",
"name": "mchx_0b",
"ua": 15000.0,
"coil_index": 1,
"n_air": 0.5,
"t_air_celsius": 35.0,
"fan_speed_ratio": 1.0
},
{
"type": "Placeholder",
"name": "exv_0",
"n_equations": 2
},
{
"type": "FloodedEvaporator",
"name": "evap_0",
"ua": 20000.0,
"refrigerant": "R134a",
"secondary_fluid": "MEG",
"target_quality": 0.7
}
],
"edges": [
{ "from": "screw_0:outlet", "to": "mchx_0a:inlet" },
{ "from": "mchx_0a:outlet", "to": "mchx_0b:inlet" },
{ "from": "mchx_0b:outlet", "to": "exv_0:inlet" },
{ "from": "exv_0:outlet", "to": "evap_0:inlet" },
{ "from": "evap_0:outlet", "to": "screw_0:inlet" }
]
}
```
---
## 4. Tests d'Intégration
### 4.1 Test: Création Screw + Residuals
```rust
#[test]
fn test_screw_compressor_creation_and_residuals() {
let suc = make_port("R134a", 3.2, 400.0);
let dis = make_port("R134a", 12.8, 440.0);
let eco = make_port("R134a", 6.4, 260.0);
let comp = ScrewEconomizerCompressor::new(
make_screw_curves(), "R134a", 50.0, 0.92, suc, dis, eco
).expect("compressor creation ok");
assert_eq!(comp.n_equations(), 5);
// État plausible
let state = vec![1.2, 0.144, 400_000.0, 440_000.0, 55_000.0];
let mut residuals = vec![0.0; 5];
comp.compute_residuals(&state, &mut residuals).expect("ok");
// Tous résiduals finis
for (i, r) in residuals.iter().enumerate() {
assert!(r.is_finite(), "residual[{}] not finite", i);
}
}
```
### 4.2 Test: VFD Scaling
```rust
#[test]
fn test_screw_vfd_scaling() {
let mut comp = /* ... */;
// Pleine vitesse (50 Hz)
let state_full = vec![1.2, 0.144, 400_000.0, 440_000.0, 55_000.0];
comp.compute_residuals(&state_full, &mut r_full).unwrap();
// 80% vitesse (40 Hz)
comp.set_frequency_hz(40.0).unwrap();
assert!((comp.frequency_ratio() - 0.8).abs() < 1e-10);
let state_reduced = vec![0.96, 0.115, 400_000.0, 440_000.0, 44_000.0];
comp.compute_residuals(&state_reduced, &mut r_reduced).unwrap();
}
```
### 4.3 Test: MCHX UA Correction
```rust
#[test]
fn test_mchx_ua_correction_with_fan_speed() {
let mut coil = MchxCondenserCoil::for_35c_ambient(15_000.0, 0);
// 100% → UA nominal
let ua_100 = coil.ua_effective();
// 70% → UA × √0.7
coil.set_fan_speed_ratio(0.70);
let ua_70 = coil.ua_effective();
let expected_ratio = 0.70_f64.sqrt();
let actual_ratio = ua_70 / ua_100;
assert!((actual_ratio - expected_ratio).abs() < 0.02);
}
```
### 4.4 Test: Topologie 2 Circuits
```rust
#[test]
fn test_two_circuit_chiller_topology() {
let mut sys = System::new();
// Circuit 0
let comp0 = /* screw compressor */;
let comp0_node = sys.add_component_to_circuit(
Box::new(comp0), CircuitId::ZERO
).expect("add comp0");
// 2 coils pour circuit 0
for i in 0..2 {
let coil = MchxCondenserCoil::for_35c_ambient(15_000.0, i);
let coil_node = sys.add_component_to_circuit(
Box::new(coil), CircuitId::ZERO
).expect("add coil");
sys.add_edge(comp0_node, coil_node).expect("edge");
}
// Circuit 1 (similaire)
// ...
assert_eq!(sys.circuit_count(), 2);
sys.finalize().expect("finalize should succeed");
}
```
### 4.5 Test: Anti-Override Ventilateur
```rust
#[test]
fn test_fan_anti_override_speed_reduction() {
let mut coil = MchxCondenserCoil::for_35c_ambient(15_000.0, 0);
let ua_100 = coil.ua_effective();
coil.set_fan_speed_ratio(0.80);
let ua_80 = coil.ua_effective();
coil.set_fan_speed_ratio(0.60);
let ua_60 = coil.ua_effective();
// UA décroît avec la vitesse ventilateur
assert!(ua_100 > ua_80);
assert!(ua_80 > ua_60);
// Suit loi puissance: UA ∝ speed^0.5
assert!((ua_80/ua_100 - 0.80_f64.sqrt()).abs() < 0.03);
assert!((ua_60/ua_100 - 0.60_f64.sqrt()).abs() < 0.03);
}
```
### 4.6 Test: Bilan Énergétique Screw
```rust
#[test]
fn test_screw_energy_balance() {
let comp = /* screw avec ports P_suc=3.2, P_dis=12.8, P_eco=6.4 */;
let m_suc = 1.2;
let m_eco = 0.144;
let h_suc = 400_000.0;
let h_dis = 440_000.0;
let h_eco = 260_000.0;
let eta_mech = 0.92;
// W ferme le bilan énergétique:
// m_suc × h_suc + m_eco × h_eco + W/η = (m_suc + m_eco) × h_dis
let w_expected = ((m_suc + m_eco) * h_dis - m_suc * h_suc - m_eco * h_eco) * eta_mech;
let state = vec![m_suc, m_eco, h_suc, h_dis, w_expected];
let mut residuals = vec![0.0; 5];
comp.compute_residuals(&state, &mut residuals).unwrap();
// residual[2] = bilan énergétique ≈ 0
assert!(residuals[2].abs() < 1.0);
}
```
---
## 5. Fichiers Sources
| Fichier | Description |
|---------|-------------|
| `crates/components/src/screw_economizer_compressor.rs` | Implémentation ScrewEconomizerCompressor |
| `crates/components/src/heat_exchanger/mchx_condenser_coil.rs` | Implémentation MchxCondenserCoil |
| `crates/solver/tests/chiller_air_glycol_integration.rs` | Tests d'intégration (10 tests) |
| `crates/cli/examples/chiller_screw_mchx_2circuits.json` | Config JSON complète 2 circuits |
| `crates/cli/examples/chiller_screw_mchx_validate.json` | Config validation 1 circuit |
---
## 6. Commandes CLI
### Validation de Configuration
```bash
# Valider le JSON sans lancer la simulation
entropyk-cli validate --config chiller_screw_mchx_2circuits.json
```
### Lancement de Simulation
```bash
# Avec backend test (développement)
entropyk-cli run --config chiller_screw_mchx_2circuits.json --backend test
# Avec backend tabular (R134a built-in)
entropyk-cli run --config chiller_screw_mchx_2circuits.json --backend tabular
# Avec CoolProp (si disponible)
entropyk-cli run --config chiller_screw_mchx_2circuits.json --backend coolprop
```
### Output JSON
```bash
entropyk-cli run --config chiller_screw_mchx_2circuits.json --output results.json
```
---
## 7. Références
### 7.1 Standards et Corrélations
- **ASHRAE Handbook** — Chapitre 4: Heat Transfer (corrélation louvered fins, n=0.5)
- **AHRI Standard 540** — Performance Rating of Positive Displacement Refrigerant Compressors
- **Bitzer Technical Documentation** — Screw compressor curves (HSK/CSH series)
### 7.2 Stories Connexes
| Story | Description |
|-------|-------------|
| 11-3 | FloodedEvaporator implémentation |
| 12-1 | CLI internal state variables |
| 12-2 | CLI CoolProp backend |
| 12-3 | CLI Screw compressor config |
| 12-4 | CLI MCHX config |
| 12-5 | CLI FloodedEvaporator + Brine |
| 12-6 | CLI Controls (SH, VFD, fan) |
---
## 8. Points d'Attention
### 8.1 État Actuel (Limitations)
1. **Backend TestBackend** — La CLI utilise `TestBackend` qui retourne des zéros. Nécessite CoolProp ou TabularBackend pour des simulations réelles (Story 12.2).
2. **Variables d'État Internes** — Le solveur peut retourner "State dimension mismatch" si les composants complexes ne déclarent pas correctement `internal_state_len()` (Story 12.1).
3. **Port Économiseur** — Dans la config CLI actuelle, le port économiseur du Screw n'est pas connecté à un composant économiseur séparé. Le modèle utilise une fraction fixe (Story 12.3).
### 8.2 Prochaines Étapes
1. Implémenter Story 12.1 (variables internes) pour résoudre le mismatch state/equations
2. Implémenter Story 12.2 (CoolProp backend) pour des propriétés thermodynamiques réelles
3. Ajouter les contrôles (Story 12.6) pour surchauffe cible et VFD
4. Valider la convergence sur un point de fonctionnement nominal

View File

@@ -0,0 +1,319 @@
# Migration Guide: Boundary Conditions
## Overview
The `FlowSource` and `FlowSink` types have been replaced with typed alternatives that provide better type safety and more explicit fluid handling:
| Old Type | New Type | Use Case |
|----------|----------|----------|
| `FlowSource` | `RefrigerantSource` | Refrigerants (R410A, R134a, CO₂, etc.) |
| `FlowSource` | `BrineSource` | Liquid heat transfer fluids (water, glycol, brine) |
| `FlowSource` | `AirSource` | Humid air (HVAC applications) |
| `FlowSink` | `RefrigerantSink` | Refrigerants |
| `FlowSink` | `BrineSink` | Liquid heat transfer fluids |
| `FlowSink` | `AirSink` | Humid air |
## Deprecation Timeline
| Version | Status |
|---------|--------|
| 0.2.0 | Deprecation warnings added |
| 0.3.0 | Old types hidden behind feature flag |
| 1.0.0 | Complete removal of old types |
## Migration Examples
### Water Source
**Before (deprecated):**
```rust
use entropyk_components::FlowSource;
let source = FlowSource::incompressible("Water", 3.0e5, 63_000.0, port)?;
```
**After:**
```rust
use entropyk_components::BrineSource;
use entropyk_core::{Pressure, Temperature, Concentration};
use entropyk_fluids::CoolPropBackend;
use std::sync::Arc;
let backend = Arc::new(CoolPropBackend::new());
let source = BrineSource::new(
"Water", // Fluid name
Pressure::from_pascals(3.0e5), // Pressure
Temperature::from_celsius(15.0), // Temperature
Concentration::zero(), // No glycol
backend.clone(),
port
)?;
```
### Glycol Mixture Source
**Before (deprecated):**
```rust
use entropyk_components::FlowSource;
// MEG at 30% concentration, 3 bar, -5°C
let source = FlowSource::incompressible("MEG", 3.0e5, 20_000.0, port)?;
```
**After:**
```rust
use entropyk_components::BrineSource;
use entropyk_core::{Pressure, Temperature, Concentration};
use entropyk_fluids::CoolPropBackend;
use std::sync::Arc;
let backend = Arc::new(CoolPropBackend::new());
let source = BrineSource::new(
"MEG", // Fluid name
Pressure::from_pascals(3.0e5), // Pressure
Temperature::from_celsius(-5.0), // Temperature
Concentration::from_percent(30.0), // 30% glycol concentration
backend.clone(),
port
)?;
```
### Refrigerant Source
**Before (deprecated):**
```rust
use entropyk_components::FlowSource;
// R410A at 10 bar, enthalpy 280 kJ/kg
let source = FlowSource::compressible("R410A", 10.0e5, 280_000.0, port)?;
```
**After:**
```rust
use entropyk_components::RefrigerantSource;
use entropyk_core::{Pressure, VaporQuality};
use entropyk_fluids::CoolPropBackend;
use std::sync::Arc;
let backend = Arc::new(CoolPropBackend::new());
// Option 1: Using pressure and vapor quality
let source = RefrigerantSource::new(
"R410A",
Pressure::from_pascals(10.0e5),
VaporQuality::from_fraction(0.5), // 50% vapor quality
backend.clone(),
port
)?;
// Option 2: Using saturated vapor (quality = 1)
let source = RefrigerantSource::new(
"R410A",
Pressure::from_pascals(10.0e5),
VaporQuality::saturated_vapor(),
backend.clone(),
port
)?;
```
### Air Source
**Before (deprecated):**
```rust
// Air sources were not available in the old API
```
**After:**
```rust
use entropyk_components::AirSource;
use entropyk_core::{Pressure, Temperature, RelativeHumidity};
// Option 1: From dry-bulb temperature and relative humidity
let source = AirSource::from_dry_bulb_rh(
Temperature::from_celsius(35.0), // Dry-bulb temperature
RelativeHumidity::from_percent(50.0), // 50% relative humidity
Pressure::from_pascals(101_325.0), // Atmospheric pressure
port
)?;
// Option 2: From dry-bulb and wet-bulb temperatures
let source = AirSource::from_dry_and_wet_bulb(
Temperature::from_celsius(35.0), // Dry-bulb temperature
Temperature::from_celsius(25.0), // Wet-bulb temperature
Pressure::from_pascals(101_325.0), // Atmospheric pressure
port
)?;
```
### Water Sink
**Before (deprecated):**
```rust
use entropyk_components::FlowSink;
// Return header: 1.5 bar back-pressure
let sink = FlowSink::incompressible("Water", 1.5e5, None, port)?;
```
**After:**
```rust
use entropyk_components::BrineSink;
use entropyk_core::{Pressure, Concentration};
use entropyk_fluids::CoolPropBackend;
use std::sync::Arc;
let backend = Arc::new(CoolPropBackend::new());
let sink = BrineSink::new(
"Water",
Pressure::from_pascals(1.5e5), // Back-pressure
None, // No fixed temperature (free enthalpy)
None, // No concentration needed when temp is None
backend.clone(),
port
)?;
```
### Refrigerant Sink
**Before (deprecated):**
```rust
use entropyk_components::FlowSink;
// R410A low-side: 8.5 bar
let sink = FlowSink::compressible("R410A", 8.5e5, None, port)?;
```
**After:**
```rust
use entropyk_components::RefrigerantSink;
use entropyk_core::Pressure;
use entropyk_fluids::CoolPropBackend;
use std::sync::Arc;
let backend = Arc::new(CoolPropBackend::new());
let sink = RefrigerantSink::new(
"R410A",
Pressure::from_pascals(8.5e5), // Back-pressure
None, // No fixed vapor quality (free enthalpy)
backend.clone(),
port
)?;
```
### Air Sink
**Before (deprecated):**
```rust
// Air sinks were not available in the old API
```
**After:**
```rust
use entropyk_components::AirSink;
use entropyk_core::Pressure;
// Simple back-pressure sink (free enthalpy)
let sink = AirSink::new(
Pressure::from_pascals(101_325.0), // Atmospheric pressure
port
)?;
// Or with fixed return temperature:
let mut sink = AirSink::new(Pressure::from_pascals(101_325.0), port)?;
sink.set_return_temperature(
Temperature::from_celsius(25.0),
RelativeHumidity::from_percent(50.0)
)?;
```
## Benefits of New Types
### 1. Type Safety
The fluid type is now explicit in the type name, making code more self-documenting:
```rust
// Old: Unclear what type of fluid this is
let source = FlowSource::incompressible("Water", ...);
// New: Clear that this is a brine/water source
let source = BrineSource::new("Water", ...);
```
### 2. Concentration Support
`BrineSource` supports glycol concentration natively:
```rust
let source = BrineSource::new(
"MEG",
Pressure::from_pascals(3.0e5),
Temperature::from_celsius(-10.0),
Concentration::from_percent(40.0), // 40% MEG
backend.clone(),
port
)?;
```
### 3. Vapor Quality Input
`RefrigerantSource` supports vapor quality input instead of raw enthalpy:
```rust
// Saturated vapor at condenser outlet
let source = RefrigerantSource::new(
"R410A",
Pressure::from_pascals(24.0e5),
VaporQuality::saturated_vapor(),
backend.clone(),
port
)?;
// Two-phase mixture
let source = RefrigerantSource::new(
"R410A",
Pressure::from_pascals(8.5e5),
VaporQuality::from_fraction(0.3), // 30% vapor
backend.clone(),
port
)?;
```
### 4. Psychrometrics
`AirSource` supports psychrometric calculations:
```rust
// Outdoor air with humidity
let source = AirSource::from_dry_bulb_rh(
Temperature::from_celsius(35.0),
RelativeHumidity::from_percent(50.0),
Pressure::from_pascals(101_325.0),
port
)?;
```
## Type Aliases Also Deprecated
The following type aliases are also deprecated:
| Old Alias | Replacement |
|-----------|-------------|
| `IncompressibleSource` | `BrineSource` |
| `CompressibleSource` | `RefrigerantSource` |
| `IncompressibleSink` | `BrineSink` |
| `CompressibleSink` | `RefrigerantSink` |
**Note:** `AirSource` and `AirSink` are new types with no deprecated aliases.
## Need Help?
If you encounter issues during migration:
1. Check the API documentation for the new types
2. Review the examples in the `examples/` directory
3. Open an issue on GitHub with the `migration` label