From 1b6415776ec7e12277399a4d30d54a3a1b05e775 Mon Sep 17 00:00:00 2001 From: Sepehr Date: Sun, 22 Feb 2026 23:24:09 +0100 Subject: [PATCH] chore: restore sprint-status.yaml from on-disk artifact state --- .../sprint-status.yaml | 59 +- bindings/python/solver_control_examples.ipynb | 949 ++++++++++++++++++ 2 files changed, 993 insertions(+), 15 deletions(-) create mode 100644 bindings/python/solver_control_examples.ipynb diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 9def462..725c541 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -1,5 +1,5 @@ # Sprint Status - Entropyk -# Last Updated: 2026-02-21 +# Last Updated: 2026-02-22 # Project: Entropyk # Project Key: NOKEY # Tracking System: file-system @@ -34,7 +34,7 @@ # - SM typically creates next story after previous one is 'done' to incorporate learnings # - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended) -generated: 2026-02-21 +generated: 2026-02-22 project: Entropyk project_key: NOKEY tracking_system: file-system @@ -42,7 +42,7 @@ story_location: _bmad-output/implementation-artifacts development_status: # Epic 1: Extensible Component Framework - epic-1: in-progress + epic-1: done 1-1-component-trait-definition: done 1-2-physical-types-newtype-pattern: done 1-3-port-and-connection-system: done @@ -50,12 +50,9 @@ development_status: 1-5-generic-heat-exchanger-framework: done 1-6-expansion-valve-component: done 1-7-component-state-machine: done - 1-8-auxiliary-transport-components: review + 1-8-auxiliary-transport-components: done 1-11-flow-junctions-flowsplitter-flowmerger: done 1-12-boundary-conditions-flowsource-flowsink: done - - - epic-2: in-progress 2-1-fluid-backend-trait-abstraction: done 2-2-coolprop-integration-sys-crate: done 2-3-tabular-interpolation-backend: done @@ -67,7 +64,7 @@ development_status: epic-1-retrospective: optional # Epic 3: System Topology (Graph) - epic-3: in-progress + epic-3: done 3-1-system-graph-structure: done 3-2-port-compatibility-validation: done 3-3-multi-circuit-machine-definition: done @@ -77,7 +74,7 @@ development_status: epic-3-retrospective: optional # Epic 4: Intelligent Solver Engine - epic-4: in-progress + epic-4: done 4-1-solver-trait-abstraction: done 4-2-newton-raphson-implementation: done 4-3-sequential-substitution-picard-implementation: done @@ -103,15 +100,16 @@ development_status: 6-1-rust-native-api: done 6-2-python-bindings-pyo3: done 6-3-c-ffi-bindings-cbindgen: done - 6-4-webassembly-compilation: in-progress - 6-5-cli-for-batch-execution: backlog + 6-4-webassembly-compilation: review + 6-5-cli-for-batch-execution: done + 6-6-python-solver-configuration-parity: done epic-6-retrospective: optional # Epic 7: Validation & Persistence epic-7: in-progress - 7-1-mass-balance-validation: review - 7-2-energy-balance-validation: backlog - 7-3-traceability-metadata: backlog + 7-1-mass-balance-validation: done + 7-2-energy-balance-validation: done + 7-3-traceability-metadata: review 7-4-debug-verbose-mode: backlog 7-5-json-serialization-deserialization: backlog 7-6-component-calibration-parameters-calib: backlog @@ -120,8 +118,39 @@ development_status: epic-7-retrospective: optional # Epic 8: Component-Fluid Integration - epic-8: in-progress + epic-8: done 8-1-fluid-backend-component-integration: done 1-9-air-coils-evaporatorcoil-condensercoil-post-mvp: done 1-10-pipe-helpers-for-water-and-refrigerant: done epic-8-retrospective: optional + + # Epic 9: Coherence Corrections (Post-Audit) + epic-9: in-progress + 9-1-circuitid-type-unification: done + 9-2-fluidid-type-unification: done + 9-3-expansionvalve-energy-methods: backlog + 9-4-flowsourceflowsink-energy-methods: backlog + 9-5-flowsplitterflowmerger-energy-methods: backlog + 9-6-energy-validation-logging-improvement: done + 9-7-solver-refactoring-split-files: ready-for-dev + 9-8-systemstate-dedicated-struct: in-progress + epic-9-retrospective: optional + + # Epic 11: Advanced HVAC Components + epic-11: in-progress + 11-1-node-passive-probe: done + 11-2-drum-recirculation-drum: ready-for-dev + 11-3-floodedevaporator: backlog + 11-4-floodedcondenser: backlog + 11-5-bphxexchanger-base: backlog + 11-6-bphxevaporator: backlog + 11-7-bphxcondenser: backlog + 11-8-correlationselector: backlog + 11-9-movingboundaryhx-zone-discretization: backlog + 11-10-movingboundaryhx-cache-optimization: backlog + 11-11-vendorbackend-trait: backlog + 11-12-copeland-parser: ready-for-dev + 11-13-swep-parser: ready-for-dev + 11-14-danfoss-parser: ready-for-dev + 11-15-bitzer-parser: ready-for-dev + epic-11-retrospective: optional diff --git a/bindings/python/solver_control_examples.ipynb b/bindings/python/solver_control_examples.ipynb new file mode 100644 index 0000000..22d7ceb --- /dev/null +++ b/bindings/python/solver_control_examples.ipynb @@ -0,0 +1,949 @@ +{ + "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 +} \ No newline at end of file