{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Entropyk \u2014 Guide Complet: Solveurs, Contr\u00f4le Inverse & API\n",
    "\n",
    "Ce notebook pr\u00e9sente l'**API compl\u00e8te** d'Entropyk pour:\n",
    "\n",
    "1. **Solveurs** : Newton-Raphson, Picard, Fallback\n",
    "2. **Contr\u00f4le Inverse** : Constraints + BoundedVariables\n",
    "3. **Types physiques** : Pressure, Temperature, Enthalpy, MassFlow\n",
    "4. **Composants** : Compressor, Condenser, Evaporator, etc.\n",
    "\n",
    "> \u26a0\ufe0f **Note**: Les composants Python actuels sont des placeholders. Les vrais composants thermodynamiques sont impl\u00e9ment\u00e9s en Rust et n\u00e9cessitent l'activation des features appropri\u00e9es."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import entropyk\n",
    "import numpy as np\n",
    "\n",
    "print(\"=== ENTROPYK API ===\\n\")\n",
    "\n",
    "# Lister toutes les classes disponibles\n",
    "classes = [x for x in dir(entropyk) if not x.startswith('_') and x[0].isupper()]\n",
    "print(\"Classes disponibles:\")\n",
    "for c in sorted(classes):\n",
    "    print(f\"  \u2022 {c}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# PARTIE 1: TYPES PHYSIQUES\n",
    "\n",
    "Types forts avec conversion d'unit\u00e9s automatique."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== PRESSURE ===\\n\")\n",
    "\n",
    "# Cr\u00e9ation avec diff\u00e9rentes unit\u00e9s\n",
    "p1 = entropyk.Pressure(bar=12.0)\n",
    "p2 = entropyk.Pressure(kpa=350.0)\n",
    "p3 = entropyk.Pressure(psi=150.0)\n",
    "p4 = entropyk.Pressure(pa=101325.0)\n",
    "\n",
    "print(f\"p1 = {p1}\")\n",
    "print(f\"  \u2192 {p1.to_bar():.2f} bar = {p1.to_kpa():.1f} kPa = {p1.to_psi():.1f} psi\")\n",
    "\n",
    "print(f\"\\np2 = {p2}\")\n",
    "print(f\"  \u2192 {p2.to_bar():.2f} bar\")\n",
    "\n",
    "# Arithm\u00e9tique\n",
    "p_sum = p1 + entropyk.Pressure(bar=2.0)\n",
    "p_diff = p1 - entropyk.Pressure(bar=2.0)\n",
    "print(f\"\\nArithm\u00e9tique:\")\n",
    "print(f\"  {p1} + 2 bar = {p_sum}\")\n",
    "print(f\"  {p1} - 2 bar = {p_diff}\")\n",
    "\n",
    "# Conversion en float\n",
    "print(f\"\\nfloat(p1) = {float(p1)} Pa\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== TEMPERATURE ===\\n\")\n",
    "\n",
    "# Cr\u00e9ation\n",
    "t1 = entropyk.Temperature(celsius=45.0)\n",
    "t2 = entropyk.Temperature(kelvin=273.15)\n",
    "t3 = entropyk.Temperature(fahrenheit=100.0)\n",
    "\n",
    "print(f\"t1 = {t1}\")\n",
    "print(f\"  \u2192 {t1.to_celsius():.2f}\u00b0C = {t1.to_kelvin():.2f} K = {t1.to_fahrenheit():.2f}\u00b0F\")\n",
    "\n",
    "print(f\"\\nt2 (point de cong\u00e9lation) = {t2}\")\n",
    "print(f\"  \u2192 {t2.to_celsius():.2f}\u00b0C\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== ENTHALPY ===\\n\")\n",
    "\n",
    "h1 = entropyk.Enthalpy(kj_per_kg=420.0)\n",
    "h2 = entropyk.Enthalpy(j_per_kg=250000.0)\n",
    "\n",
    "print(f\"h1 = {h1}\")\n",
    "print(f\"  \u2192 {h1.to_kj_per_kg():.1f} kJ/kg = {h1.to_j_per_kg():.0f} J/kg\")\n",
    "\n",
    "print(f\"\\nh2 = {h2}\")\n",
    "print(f\"  \u2192 {h2.to_kj_per_kg():.1f} kJ/kg\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== MASSFLOW ===\\n\")\n",
    "\n",
    "m1 = entropyk.MassFlow(kg_per_s=0.05)\n",
    "m2 = entropyk.MassFlow(g_per_s=50.0)\n",
    "\n",
    "print(f\"m1 = {m1}\")\n",
    "print(f\"  \u2192 {m1.to_kg_per_s():.4f} kg/s = {m1.to_g_per_s():.1f} g/s\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# PARTIE 2: COMPOSANTS\n",
    "\n",
    "Cr\u00e9ation et configuration des composants du cycle."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== COMPRESSOR ===\\n\")\n",
    "\n",
    "comp = entropyk.Compressor(\n",
    "    speed_rpm=3000.0,           # Vitesse de rotation\n",
    "    displacement=0.0001,        # Cylindr\u00e9e (m\u00b3/rev) = 100 cc\n",
    "    efficiency=0.85,            # Rendement volum\u00e9trique\n",
    "    fluid=\"R134a\",              # Fluide frigorig\u00e8ne\n",
    "    # Coefficients AHRI 540 (optionnel, d\u00e9fauts fournis)\n",
    "    m1=0.85, m2=2.5, m3=500.0, m4=1500.0, m5=-2.5, m6=1.8,\n",
    "    m7=600.0, m8=1600.0, m9=-3.0, m10=2.0\n",
    ")\n",
    "\n",
    "print(f\"Compresseur cr\u00e9\u00e9: {comp}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== CONDENSER ===\\n\")\n",
    "\n",
    "cond = entropyk.Condenser(ua=5000.0)  # Coefficient global W/K\n",
    "print(f\"Condenseur: {cond}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== EVAPORATOR ===\\n\")\n",
    "\n",
    "evap = entropyk.Evaporator(ua=3000.0)  # Coefficient global W/K\n",
    "print(f\"\u00c9vaporateur: {evap}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== EXPANSION VALVE ===\\n\")\n",
    "\n",
    "exv = entropyk.ExpansionValve(\n",
    "    fluid=\"R134a\",\n",
    "    opening=0.5  # Ouverture 0-1\n",
    ")\n",
    "print(f\"Vanne d'expansion: {exv}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== AUTRES COMPOSANTS ===\\n\")\n",
    "\n",
    "# Pipe\n",
    "pipe = entropyk.Pipe()\n",
    "print(f\"Pipe: {pipe}\")\n",
    "\n",
    "# Pump\n",
    "pump = entropyk.Pump()\n",
    "print(f\"Pump: {pump}\")\n",
    "\n",
    "# Fan\n",
    "fan = entropyk.Fan()\n",
    "print(f\"Fan: {fan}\")\n",
    "\n",
    "# Economizer\n",
    "eco = entropyk.Economizer()\n",
    "print(f\"Economizer: {eco}\")\n",
    "\n",
    "# Flow sources/sinks (boundaries)\n",
    "source = entropyk.FlowSource()\n",
    "sink = entropyk.FlowSink()\n",
    "print(f\"FlowSource: {source}\")\n",
    "print(f\"FlowSink: {sink}\")\n",
    "\n",
    "# Flow splitters/mergers\n",
    "splitter = entropyk.FlowSplitter()\n",
    "merger = entropyk.FlowMerger()\n",
    "print(f\"FlowSplitter: {splitter}\")\n",
    "print(f\"FlowMerger: {merger}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# PARTIE 3: SYST\u00c8ME ET TOPOLOGIE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== CR\u00c9ATION DU SYST\u00c8ME ===\\n\")\n",
    "\n",
    "# Cr\u00e9er le syst\u00e8me\n",
    "system = entropyk.System()\n",
    "print(f\"Syst\u00e8me vide: {system}\")\n",
    "\n",
    "# Ajouter les composants\n",
    "comp_idx = system.add_component(entropyk.Compressor(\n",
    "    speed_rpm=3000.0, displacement=0.0001, efficiency=0.85, fluid=\"R134a\"\n",
    "))\n",
    "cond_idx = system.add_component(entropyk.Condenser(ua=5000.0))\n",
    "exv_idx = system.add_component(entropyk.ExpansionValve(fluid=\"R134a\", opening=0.5))\n",
    "evap_idx = system.add_component(entropyk.Evaporator(ua=3000.0))\n",
    "\n",
    "print(f\"\\nComposants ajout\u00e9s:\")\n",
    "print(f\"  Compresseur \u2192 index {comp_idx}\")\n",
    "print(f\"  Condenseur  \u2192 index {cond_idx}\")\n",
    "print(f\"  EXV         \u2192 index {exv_idx}\")\n",
    "print(f\"  \u00c9vaporateur \u2192 index {evap_idx}\")\n",
    "\n",
    "print(f\"\\nSyst\u00e8me: {system}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== CONNEXIONS (EDGES) ===\\n\")\n",
    "\n",
    "# Connecter les composants en cycle\n",
    "e1 = system.add_edge(comp_idx, cond_idx)   # Compresseur \u2192 Condenseur\n",
    "e2 = system.add_edge(cond_idx, exv_idx)    # Condenseur \u2192 EXV\n",
    "e3 = system.add_edge(exv_idx, evap_idx)    # EXV \u2192 \u00c9vaporateur\n",
    "e4 = system.add_edge(evap_idx, comp_idx)   # \u00c9vaporateur \u2192 Compresseur\n",
    "\n",
    "print(f\"Edges cr\u00e9\u00e9s: {e1}, {e2}, {e3}, {e4}\")\n",
    "print(f\"\\nFlux: Comp \u2192 Cond \u2192 EXV \u2192 Evap \u2192 Comp (cycle)\")\n",
    "print(f\"\\nSyst\u00e8me: {system}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== NOMS DE COMPOSANTS ===\\n\")\n",
    "\n",
    "# Enregistrer des noms lisibles pour le contr\u00f4le inverse\n",
    "system.register_component_name(\"evaporator\", evap_idx)\n",
    "system.register_component_name(\"condenser\", cond_idx)\n",
    "system.register_component_name(\"valve\", exv_idx)\n",
    "system.register_component_name(\"compressor\", comp_idx)\n",
    "\n",
    "print(\"Noms enregistr\u00e9s pour le contr\u00f4le inverse:\")\n",
    "print(\"  'evaporator' \u2192 index\", evap_idx)\n",
    "print(\"  'condenser' \u2192 index\", cond_idx)\n",
    "print(\"  'valve' \u2192 index\", exv_idx)\n",
    "print(\"  'compressor' \u2192 index\", comp_idx)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== FINALISATION ===\\n\")\n",
    "\n",
    "system.finalize()\n",
    "\n",
    "print(f\"Syst\u00e8me finalis\u00e9!\")\n",
    "print(f\"  Nodes (composants): {system.node_count}\")\n",
    "print(f\"  Edges (connexions): {system.edge_count}\")\n",
    "print(f\"  State vector length: {system.state_vector_len}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# PARTIE 4: SOLVEURS"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== NEWTON-RAPHSON CONFIG ===\\n\")\n",
    "\n",
    "newton = entropyk.NewtonConfig(\n",
    "    max_iterations=200,      # Max 200 it\u00e9rations\n",
    "    tolerance=1e-6,          # Convergence: ||r|| < 1e-6\n",
    "    line_search=True,        # Recherche lin\u00e9aire activ\u00e9e\n",
    "    timeout_ms=10000         # Timeout: 10 secondes\n",
    ")\n",
    "\n",
    "print(f\"Config: {newton}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== PICARD CONFIG ===\\n\")\n",
    "\n",
    "picard = entropyk.PicardConfig(\n",
    "    max_iterations=500,\n",
    "    tolerance=1e-4,\n",
    "    relaxation=0.5           # 0.5 = sous-relaxation\n",
    ")\n",
    "\n",
    "print(f\"Config: {picard}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== FALLBACK CONFIG (Newton \u2192 Picard) ===\\n\")\n",
    "\n",
    "fallback = entropyk.FallbackConfig(\n",
    "    newton=entropyk.NewtonConfig(max_iterations=100, tolerance=1e-6),\n",
    "    picard=entropyk.PicardConfig(max_iterations=300, tolerance=1e-4, relaxation=0.5)\n",
    ")\n",
    "\n",
    "print(f\"Config: {fallback}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== R\u00c9SOLUTION ===\\n\")\n",
    "\n",
    "# Note: Avec les composants placeholder actuels, le solveur peut\n",
    "# converger imm\u00e9diatement (r\u00e9sidus nuls) ou diverger selon la config\n",
    "\n",
    "try:\n",
    "    result = fallback.solve(system)\n",
    "    \n",
    "    print(f\"\u2705 R\u00e9sultat obtenu:\")\n",
    "    print(f\"   It\u00e9rations: {result.iterations}\")\n",
    "    print(f\"   R\u00e9sidu final: {result.final_residual:.2e}\")\n",
    "    print(f\"   Statut: {result.status}\")\n",
    "    print(f\"   Converg\u00e9: {result.is_converged}\")\n",
    "    \n",
    "except entropyk.SolverError as e:\n",
    "    print(f\"\u274c SolverError: {e}\")\n",
    "except entropyk.TimeoutError as e:\n",
    "    print(f\"\u23f1\ufe0f TimeoutError: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# PARTIE 5: R\u00c9SULTAT ET STATE VECTOR"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== ACC\u00c8S AU R\u00c9SULTAT ===\\n\")\n",
    "\n",
    "try:\n",
    "    result = fallback.solve(system)\n",
    "    \n",
    "    # Propri\u00e9t\u00e9s du r\u00e9sultat\n",
    "    print(f\"It\u00e9rations: {result.iterations}\")\n",
    "    print(f\"R\u00e9sidu final: {result.final_residual:.6e}\")\n",
    "    print(f\"Converg\u00e9: {result.is_converged}\")\n",
    "    print(f\"Statut: {result.status}\")\n",
    "    \n",
    "    # State vector comme liste\n",
    "    state_list = result.state_vector\n",
    "    print(f\"\\nState vector (list): {len(state_list)} \u00e9l\u00e9ments\")\n",
    "    if len(state_list) > 0:\n",
    "        print(f\"  Premiers: {state_list[:min(6, len(state_list))]}\")\n",
    "    \n",
    "    # State vector comme NumPy array\n",
    "    state_array = result.to_numpy()\n",
    "    print(f\"\\nState vector (numpy): shape={state_array.shape}, dtype={state_array.dtype}\")\n",
    "    \n",
    "except entropyk.SolverError as e:\n",
    "    print(f\"Erreur: {e}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== CONVERGENCE STATUS ===\\n\")\n",
    "\n",
    "print(\"Valeurs possibles de result.status:\")\n",
    "print(\"  \u2022 'Converged'          \u2192 Solution trouv\u00e9e\")\n",
    "print(\"  \u2022 'ControlSaturation'  \u2192 Converg\u00e9 mais variable born\u00e9e satur\u00e9e\")\n",
    "print(\"  \u2022 'TimedOut'           \u2192 Timeout atteint\")\n",
    "\n",
    "print(\"\\nExemple de v\u00e9rification:\")\n",
    "print(\"\"\"\n",
    "if result.is_converged:\n",
    "    if result.status == \"Converged\":\n",
    "        print(\"Solution optimale trouv\u00e9e\")\n",
    "    elif result.status == \"ControlSaturation\":\n",
    "        print(\"Contr\u00f4le \u00e0 la borne - objectif non atteignable\")\n",
    "else:\n",
    "    print(\"Non converg\u00e9\")\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# PARTIE 6: CONTR\u00d4LE INVERSE - CONSTRAINTS"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== CONSTRAINT: SUPERHEAT ===\\n\")\n",
    "\n",
    "# Contrainte de surchauffe: Superheat = 5.0 K\n",
    "sh_constraint = entropyk.Constraint.superheat(\n",
    "    id=\"sh_5k\",                  # ID unique\n",
    "    component_id=\"evaporator\",   # Composant cible (nom enregistr\u00e9)\n",
    "    target_value=5.0,            # Surchauffe cible (K)\n",
    "    tolerance=1e-4               # Tol\u00e9rance de convergence\n",
    ")\n",
    "\n",
    "print(f\"Constraint: {sh_constraint}\")\n",
    "print(\"\\nUsage typique:\")\n",
    "print(\"  \u2022 Maintenir 5-10K de surchauffe \u00e0 l'aspiration compresseur\")\n",
    "print(\"  \u2022 Contr\u00f4l\u00e9 par ouverture de vanne EXV\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== CONSTRAINT: SUBCOOLING ===\\n\")\n",
    "\n",
    "sc_constraint = entropyk.Constraint.subcooling(\n",
    "    id=\"sc_3k\",\n",
    "    component_id=\"condenser\",\n",
    "    target_value=3.0,\n",
    "    tolerance=1e-4\n",
    ")\n",
    "\n",
    "print(f\"Constraint: {sc_constraint}\")\n",
    "print(\"\\nUsage typique:\")\n",
    "print(\"  \u2022 Maintenir 2-5K de sous-refroidissement\")\n",
    "print(\"  \u2022 Am\u00e9liore le rendement et \u00e9vite le flash gas\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== CONSTRAINT: CAPACITY ===\\n\")\n",
    "\n",
    "cap_constraint = entropyk.Constraint.capacity(\n",
    "    id=\"cap_10kw\",\n",
    "    component_id=\"evaporator\",\n",
    "    target_value=10000.0,  # Watts\n",
    "    tolerance=10.0\n",
    ")\n",
    "\n",
    "print(f\"Constraint: {cap_constraint}\")\n",
    "print(\"\\nUsage typique:\")\n",
    "print(\"  \u2022 Dimensionnement \u00e0 capacit\u00e9 fixe\")\n",
    "print(\"  \u2022 Contr\u00f4l\u00e9 par vitesse compresseur ou d\u00e9bit\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# PARTIE 7: BOUNDED VARIABLES"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== BOUNDED VARIABLE ===\\n\")\n",
    "\n",
    "# Variable de contr\u00f4le avec bornes physiques\n",
    "exv_opening = entropyk.BoundedVariable(\n",
    "    id=\"exv_opening\",       # ID unique\n",
    "    value=0.5,              # Valeur initiale (50%)\n",
    "    min=0.0,                # Minimum: ferm\u00e9\n",
    "    max=1.0,                # Maximum: ouvert\n",
    "    component_id=\"valve\"    # Composant associ\u00e9\n",
    ")\n",
    "\n",
    "print(f\"Variable: {exv_opening}\")\n",
    "print(\"\\nLe solveur ajustera cette variable entre 0 et 1\")\n",
    "print(\"pour satisfaire la contrainte li\u00e9e.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== EXEMPLE: VITESSE COMPRESSEUR ===\\n\")\n",
    "\n",
    "speed_var = entropyk.BoundedVariable(\n",
    "    id=\"comp_speed\",\n",
    "    value=3000.0,           # 3000 RPM initial\n",
    "    min=1000.0,             # Minimum: 1000 RPM\n",
    "    max=6000.0,             # Maximum: 6000 RPM\n",
    "    component_id=\"compressor\"\n",
    ")\n",
    "\n",
    "print(f\"Variable: {speed_var}\")\n",
    "print(\"\\nContr\u00f4le de capacit\u00e9 par variation de vitesse (inverter)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# PARTIE 8: WORKFLOW COMPLET - CONTR\u00d4LE INVERSE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== WORKFLOW COMPLET ===\\n\")\n",
    "\n",
    "# 1. Cr\u00e9er le syst\u00e8me\n",
    "system = entropyk.System()\n",
    "\n",
    "# 2. Ajouter composants\n",
    "comp_idx = system.add_component(entropyk.Compressor(\n",
    "    speed_rpm=3000.0, displacement=0.0001, efficiency=0.85, fluid=\"R134a\"\n",
    "))\n",
    "cond_idx = system.add_component(entropyk.Condenser(ua=5000.0))\n",
    "exv_idx = system.add_component(entropyk.ExpansionValve(fluid=\"R134a\", opening=0.5))\n",
    "evap_idx = system.add_component(entropyk.Evaporator(ua=3000.0))\n",
    "\n",
    "# 3. Connecter\n",
    "system.add_edge(comp_idx, cond_idx)\n",
    "system.add_edge(cond_idx, exv_idx)\n",
    "system.add_edge(exv_idx, evap_idx)\n",
    "system.add_edge(evap_idx, comp_idx)\n",
    "\n",
    "# 4. Enregistrer les noms\n",
    "system.register_component_name(\"evaporator\", evap_idx)\n",
    "system.register_component_name(\"valve\", exv_idx)\n",
    "\n",
    "# 5. Ajouter la contrainte\n",
    "sh_constraint = entropyk.Constraint.superheat(\n",
    "    id=\"sh_target\",\n",
    "    component_id=\"evaporator\",\n",
    "    target_value=5.0,\n",
    "    tolerance=1e-4\n",
    ")\n",
    "system.add_constraint(sh_constraint)\n",
    "print(f\"1. Contrainte ajout\u00e9e: {sh_constraint}\")\n",
    "\n",
    "# 6. Ajouter la variable born\u00e9e\n",
    "exv_var = entropyk.BoundedVariable(\n",
    "    id=\"exv_opening\",\n",
    "    value=0.5,\n",
    "    min=0.0,\n",
    "    max=1.0,\n",
    "    component_id=\"valve\"\n",
    ")\n",
    "system.add_bounded_variable(exv_var)\n",
    "print(f\"2. Variable born\u00e9e ajout\u00e9e: {exv_var}\")\n",
    "\n",
    "# 7. Lier contrainte \u2192 variable\n",
    "system.link_constraint_to_control(\"sh_target\", \"exv_opening\")\n",
    "print(\"3. Lien cr\u00e9\u00e9: sh_target \u2192 exv_opening\")\n",
    "\n",
    "# 8. Finaliser\n",
    "system.finalize()\n",
    "print(f\"4. Syst\u00e8me finalis\u00e9: {system.state_vector_len} variables\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== R\u00c9SOLUTION AVEC CONTR\u00d4LE INVERSE ===\\n\")\n",
    "\n",
    "config = entropyk.NewtonConfig(\n",
    "    max_iterations=200,\n",
    "    tolerance=1e-6,\n",
    "    line_search=True\n",
    ")\n",
    "\n",
    "try:\n",
    "    result = config.solve(system)\n",
    "    \n",
    "    print(f\"\u2705 R\u00e9sultat:\")\n",
    "    print(f\"   It\u00e9rations: {result.iterations}\")\n",
    "    print(f\"   R\u00e9sidu: {result.final_residual:.2e}\")\n",
    "    print(f\"   Statut: {result.status}\")\n",
    "    \n",
    "    # Si ControlSaturation, la variable est \u00e0 une borne\n",
    "    if str(result.status) == \"ControlSaturation\":\n",
    "        print(\"\\n   \u26a0\ufe0f Variable de contr\u00f4le satur\u00e9e!\")\n",
    "        print(\"   \u2192 Impossible d'atteindre exactement Superheat = 5K\")\n",
    "        print(\"   \u2192 V\u00e9rifier les bornes de la variable\")\n",
    "    \n",
    "except entropyk.SolverError as e:\n",
    "    print(f\"\u274c Erreur: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# PARTIE 9: GESTION D'ERREURS"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== TYPES D'ERREURS ===\\n\")\n",
    "\n",
    "errors = [\n",
    "    (\"entropyk.SolverError\", \"Non-convergence du solveur\"),\n",
    "    (\"entropyk.TimeoutError\", \"Timeout d\u00e9pass\u00e9\"),\n",
    "    (\"entropyk.TopologyError\", \"Erreur de topologie (cycle non ferm\u00e9)\"),\n",
    "    (\"entropyk.EntropykError\", \"Erreur g\u00e9n\u00e9rale\"),\n",
    "]\n",
    "\n",
    "for err_name, desc in errors:\n",
    "    print(f\"  {err_name:25s} \u2192 {desc}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== EXEMPLE GESTION D'ERREURS ===\\n\")\n",
    "\n",
    "print(\"\"\"\n",
    "try:\n",
    "    result = config.solve(system)\n",
    "    \n",
    "    if result.is_converged:\n",
    "        # Solution trouv\u00e9e\n",
    "        state = result.to_numpy()\n",
    "        print(f\"Converg\u00e9 en {result.iterations} it\u00e9rations\")\n",
    "    else:\n",
    "        # Non converg\u00e9 mais \u00e9tat disponible\n",
    "        print(f\"Non converg\u00e9, r\u00e9sidu={result.final_residual:.2e}\")\n",
    "        \n",
    "except entropyk.TimeoutError as e:\n",
    "    print(f\"Timeout: {e}\")\n",
    "    # Augmenter timeout ou simplifier le probl\u00e8me\n",
    "    \n",
    "except entropyk.SolverError as e:\n",
    "    print(f\"Erreur solveur: {e}\")\n",
    "    # Essayer Picard ou changer les guess initiaux\n",
    "    \n",
    "except entropyk.TopologyError as e:\n",
    "    print(f\"Erreur topologie: {e}\")\n",
    "    # V\u00e9rifier les connexions add_edge()\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# PARTIE 10: MULTI-FLUIDES"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=== TEST AVEC DIFF\u00c9RENTS FLUIDES ===\\n\")\n",
    "\n",
    "fluids = [\n",
    "    \"R134a\",    # HFC standard\n",
    "    \"R32\",      # HFC low-GWP\n",
    "    \"R290\",     # Propane (naturel)\n",
    "    \"R744\",     # CO2 (transcritique)\n",
    "    \"R1234yf\",  # HFO\n",
    "    \"R454B\",    # M\u00e9lange\n",
    "]\n",
    "\n",
    "for fluid in fluids:\n",
    "    try:\n",
    "        s = entropyk.System()\n",
    "        c = s.add_component(entropyk.Compressor(\n",
    "            speed_rpm=3000, displacement=0.0001, efficiency=0.85, fluid=fluid\n",
    "        ))\n",
    "        cd = s.add_component(entropyk.Condenser(ua=5000))\n",
    "        ex = s.add_component(entropyk.ExpansionValve(fluid=fluid, opening=0.5))\n",
    "        ev = s.add_component(entropyk.Evaporator(ua=3000))\n",
    "        \n",
    "        s.add_edge(c, cd)\n",
    "        s.add_edge(cd, ex)\n",
    "        s.add_edge(ex, ev)\n",
    "        s.add_edge(ev, c)\n",
    "        s.finalize()\n",
    "        \n",
    "        print(f\"  {fluid:10s} \u2192 \u2705 OK ({s.state_vector_len} vars)\")\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"  {fluid:10s} \u2192 \u274c {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# R\u00c9SUM\u00c9 API"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"\"\"\n",
    "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n",
    "\u2551                     R\u00c9SUM\u00c9 API ENTROPYK PYTHON                              \u2551\n",
    "\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n",
    "\u2551                                                                              \u2551\n",
    "\u2551  SYST\u00c8ME                                                                     \u2551\n",
    "\u2551  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500     \u2551\n",
    "\u2551  system = entropyk.System()                                                  \u2551\n",
    "\u2551  idx = system.add_component(comp)                                            \u2551\n",
    "\u2551  system.add_edge(src_idx, tgt_idx)                                           \u2551\n",
    "\u2551  system.register_component_name(\"name\", idx)                                 \u2551\n",
    "\u2551  system.finalize()                                                           \u2551\n",
    "\u2551                                                                              \u2551\n",
    "\u2551  COMPOSANTS                                                                  \u2551\n",
    "\u2551  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500     \u2551\n",
    "\u2551  Compressor(speed_rpm, displacement, efficiency, fluid)                      \u2551\n",
    "\u2551  Condenser(ua)                                                               \u2551\n",
    "\u2551  Evaporator(ua)                                                              \u2551\n",
    "\u2551  ExpansionValve(fluid, opening)                                              \u2551\n",
    "\u2551  Pipe(), Pump(), Fan(), Economizer(), FlowSource(), FlowSink()               \u2551\n",
    "\u2551                                                                              \u2551\n",
    "\u2551  CONTR\u00d4LE INVERSE                                                            \u2551\n",
    "\u2551  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500     \u2551\n",
    "\u2551  Constraint.superheat(id, component_id, target_value, tolerance)             \u2551\n",
    "\u2551  Constraint.subcooling(id, component_id, target_value, tolerance)            \u2551\n",
    "\u2551  Constraint.capacity(id, component_id, target_value, tolerance)              \u2551\n",
    "\u2551  BoundedVariable(id, value, min, max, component_id)                          \u2551\n",
    "\u2551  system.add_constraint(constraint)                                           \u2551\n",
    "\u2551  system.add_bounded_variable(variable)                                       \u2551\n",
    "\u2551  system.link_constraint_to_control(constraint_id, variable_id)               \u2551\n",
    "\u2551                                                                              \u2551\n",
    "\u2551  SOLVEURS                                                                    \u2551\n",
    "\u2551  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500     \u2551\n",
    "\u2551  NewtonConfig(max_iterations, tolerance, line_search, timeout_ms)            \u2551\n",
    "\u2551  PicardConfig(max_iterations, tolerance, relaxation)                         \u2551\n",
    "\u2551  FallbackConfig(newton, picard)                                              \u2551\n",
    "\u2551  result = config.solve(system)                                               \u2551\n",
    "\u2551                                                                              \u2551\n",
    "\u2551  R\u00c9SULTAT                                                                    \u2551\n",
    "\u2551  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500     \u2551\n",
    "\u2551  result.iterations        \u2192 int    (nombre d'it\u00e9rations)                     \u2551\n",
    "\u2551  result.final_residual    \u2192 float  (r\u00e9sidu L2 final)                         \u2551\n",
    "\u2551  result.status            \u2192 str    (\"Converged\"/\"ControlSaturation\"/...)    \u2551\n",
    "\u2551  result.is_converged      \u2192 bool   (True si converg\u00e9)                        \u2551\n",
    "\u2551  result.state_vector      \u2192 list   ([P0, h0, P1, h1, ...])                   \u2551\n",
    "\u2551  result.to_numpy()        \u2192 np.ndarray                                       \u2551\n",
    "\u2551                                                                              \u2551\n",
    "\u2551  TYPES PHYSIQUES                                                             \u2551\n",
    "\u2551  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500     \u2551\n",
    "\u2551  Pressure(bar=, kpa=, psi=, pa=)                                             \u2551\n",
    "\u2551  Temperature(celsius=, kelvin=, fahrenheit=)                                 \u2551\n",
    "\u2551  Enthalpy(kj_per_kg=, j_per_kg=)                                             \u2551\n",
    "\u2551  MassFlow(kg_per_s=, g_per_s=)                                               \u2551\n",
    "\u2551                                                                              \u2551\n",
    "\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Advanced Configuration & SolverStrategy\n",
    "\n",
    "Entropyk exposes fine-grained control over the solver behavior, matching the Rust core.\n",
    "You can use `SolverStrategy` for a unified interface, and advanced configs like `JacobianFreezingConfig` to optimize Newton-Raphson speed by reusing the Jacobian matrix across iterations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Jacobian Freezing speeds up convergence by reusing the Jacobian matrix\n",
    "jf_config = entropyk.JacobianFreezingConfig(max_frozen_iters=5, threshold=0.1)\n",
    "\n",
    "# Custom convergence criteria\n",
    "cc = entropyk.ConvergenceCriteria(\n",
    "    pressure_tolerance_pa=5.0,\n",
    "    mass_balance_tolerance_kgs=1e-6,\n",
    "    energy_balance_tolerance_w=1e-3\n",
    ")\n",
    "\n",
    "# Warm Starting: providing an initial guess\n",
    "initial_guess = [0.0] * system.state_vector_len\n",
    "\n",
    "strategy = entropyk.SolverStrategy.newton(\n",
    "    max_iterations=150,\n",
    "    tolerance=1e-5,\n",
    "    line_search=True,\n",
    "    jacobian_freezing=jf_config,\n",
    "    convergence_criteria=cc,\n",
    "    initial_state=initial_guess,\n",
    ")\n",
    "\n",
    "try:\n",
    "    res_advanced = strategy.solve(system)\n",
    "    print(f\"\\nStatus: {res_advanced.status}\")\n",
    "    print(f\"Iterations: {res_advanced.iterations}\")\n",
    "except entropyk.SolverError as e:\n",
    "    print(f\"Solver failed: {e}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}