# 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 (25–60 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 | 25–60 | 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 | +30–60% par m² |
| Charge réfrigérant | Base | −25–40% |
| 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
