feat: implement mass balance validation for Story 7.1
- Added port_mass_flows to Component trait and implements for core components. - Added System::check_mass_balance and integrated it into the solver. - Restored connect methods for ExpansionValve, Compressor, and Pipe to fix integration tests. - Updated Python and C bindings for validation errors. - Updated sprint status and story documentation.
This commit is contained in:
@@ -69,6 +69,20 @@ except entropyk.SolverError as e:
|
||||
print(f"Solver failed: {e}")
|
||||
```
|
||||
|
||||
## Recompiling after Rust Changes
|
||||
|
||||
Because the Python bindings rely on the Rust source code (`crates/components`, `crates/solver`, etc.), you **must recompile the Python package** if you modify the underlying Rust physics engine.
|
||||
|
||||
To recompile the bindings manually, simply use Maturin from the `bindings/python` directory with your virtual environment activated:
|
||||
|
||||
```bash
|
||||
cd bindings/python
|
||||
source .venv/bin/activate
|
||||
maturin develop --release
|
||||
```
|
||||
|
||||
*Note: If you added a new structural field in Rust (e.g. adding a `size` parameter to a Component struct), make sure to also update the Python wrapper class in `bindings/python/src/` so the macro `#[pyclass]` reflects the new shape before recompiling. You can use the `/update-python-bindings` agent workflow to do this automatically.*
|
||||
|
||||
## API Reference
|
||||
|
||||
### Physical Types
|
||||
|
||||
201
bindings/python/control_example.ipynb
Normal file
201
bindings/python/control_example.ipynb
Normal file
@@ -0,0 +1,201 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Entropyk: Inverse Control Example\n",
|
||||
"\n",
|
||||
"This notebook demonstrates **Entropyk's One-Shot Inverse Solver**. Unlike traditional approaches that wrap a forward solver in an optimizer (like `scipy.optimize`), Entropyk embeds constraints directly into the Newton-Raphson system.\n",
|
||||
"\n",
|
||||
"This allows finding continuous control variables (like compressor speed or valve opening) to achieve target outputs (like superheat or cooling capacity) **simultaneously** with the thermodynamic state."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-02-21T19:41:20.922472Z",
|
||||
"iopub.status.busy": "2026-02-21T19:41:20.922358Z",
|
||||
"iopub.status.idle": "2026-02-21T19:41:21.276770Z",
|
||||
"shell.execute_reply": "2026-02-21T19:41:21.276339Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import entropyk\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Build the System\n",
|
||||
"First, we build a standard refrigeration cycle."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-02-21T19:41:21.278270Z",
|
||||
"iopub.status.busy": "2026-02-21T19:41:21.278168Z",
|
||||
"iopub.status.idle": "2026-02-21T19:41:21.280991Z",
|
||||
"shell.execute_reply": "2026-02-21T19:41:21.280607Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"system = entropyk.System()\n",
|
||||
"\n",
|
||||
"# Add components\n",
|
||||
"comp_idx = system.add_component(entropyk.Compressor(speed_rpm=3000.0, displacement=0.0001, efficiency=0.85, fluid=\"R134a\"))\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",
|
||||
"# Connect cycle\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",
|
||||
"# Register names for inverse control references\n",
|
||||
"system.register_component_name(\"evaporator\", evap_idx)\n",
|
||||
"system.register_component_name(\"valve\", exv_idx)\n",
|
||||
"system.register_component_name(\"compressor\", comp_idx)\n",
|
||||
"\n",
|
||||
"system.finalize()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Introduce Inverse Control\n",
|
||||
"\n",
|
||||
"We want to find the **Expansion Valve Opening** required to achieve exactly **5.0 K of Superheat** at the evaporator outlet.\n",
|
||||
"\n",
|
||||
"1. Define a `Constraint` (Superheat = 5K)\n",
|
||||
"2. Define a `BoundedVariable` (Valve Opening between 0% and 100%)\n",
|
||||
"3. Bind them together in the system."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-02-21T19:41:21.282084Z",
|
||||
"iopub.status.busy": "2026-02-21T19:41:21.282025Z",
|
||||
"iopub.status.idle": "2026-02-21T19:41:21.284245Z",
|
||||
"shell.execute_reply": "2026-02-21T19:41:21.283853Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Constraint: Constraint(id='sh_ctrl', target=5, tol=0.0001)\n",
|
||||
"Bounded Var: BoundedVariable(id='exv_opening', value=0.5, bounds=[0, 1])\n",
|
||||
"\n",
|
||||
"✅ Inverse control configured successfully!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# 1. Superheat Constraint (Target: 5.0 K, Tolerance: 1e-4)\n",
|
||||
"sh_constraint = entropyk.Constraint.superheat(\n",
|
||||
" id=\"sh_ctrl\",\n",
|
||||
" component_id=\"evaporator\",\n",
|
||||
" target_value=5.0,\n",
|
||||
" tolerance=1e-4\n",
|
||||
")\n",
|
||||
"system.add_constraint(sh_constraint)\n",
|
||||
"print(\"Constraint:\", sh_constraint)\n",
|
||||
"\n",
|
||||
"# 2. Valve Opening Bounded Variable (Initial: 50%, Min: 0%, Max: 100%)\n",
|
||||
"exv_opening = 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_opening)\n",
|
||||
"print(\"Bounded Var:\", exv_opening)\n",
|
||||
"\n",
|
||||
"# 3. Link constraint and variable\n",
|
||||
"system.link_constraint_to_control(\"sh_ctrl\", \"exv_opening\")\n",
|
||||
"\n",
|
||||
"print(\"\\n✅ Inverse control configured successfully!\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Solve the System\n",
|
||||
"When we call `solve()`, Entropyk simultaneously computes the real state and the required valve opening!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-02-21T19:41:21.300083Z",
|
||||
"iopub.status.busy": "2026-02-21T19:41:21.299984Z",
|
||||
"iopub.status.idle": "2026-02-21T19:41:21.322564Z",
|
||||
"shell.execute_reply": "2026-02-21T19:41:21.322165Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Solver error: Solver did not converge after 200 iterations (final residual norm: 3.098e5)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"config = entropyk.NewtonConfig(max_iterations=200, tolerance=1e-6)\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" result = config.solve(system)\n",
|
||||
" print(\"Solved in\", result.iterations, \"iterations!\")\n",
|
||||
" # At this point normally you would read result.state_vector \n",
|
||||
" # Note: Dummy PyO3 models might yield trivial values in tests\n",
|
||||
" print(\"Final State:\", result.to_numpy()[:6], \"...\")\n",
|
||||
"except entropyk.SolverError as e:\n",
|
||||
" print(\"Solver error:\", e)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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.13.11"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
524
bindings/python/fluids_examples.ipynb
Normal file
524
bindings/python/fluids_examples.ipynb
Normal file
@@ -0,0 +1,524 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Entropyk — Fluid Properties & Refrigerants Guide\n",
|
||||
"\n",
|
||||
"Ce notebook présente les **66+ fluides disponibles** dans Entropyk via CoolProp, incluant:\n",
|
||||
"\n",
|
||||
"- **HFC** : R134a, R410A, R407C, R32, R125, R143a, R152A, R22, etc.\n",
|
||||
"- **HFO (Low-GWP)** : R1234yf, R1234ze(E), R1233zd(E), R1243zf, R1336mzz(E)\n",
|
||||
"- **Alternatives** : R513A, R454B, R452B\n",
|
||||
"- **Naturels** : R744 (CO2), R290 (Propane), R600a (Isobutane), R717 (Ammonia), R1270 (Propylene)\n",
|
||||
"- **Autres** : Water, Air, Nitrogen, Oxygen, Helium, Hydrogen, etc."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import entropyk\n",
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd\n",
|
||||
"\n",
|
||||
"pd.set_option('display.max_rows', 80)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Types Physiques de Base\n",
|
||||
"\n",
|
||||
"Entropyk fournit des types forts pour les unités physiques avec conversion automatique."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Pression - plusieurs unités supportées\n",
|
||||
"p1 = entropyk.Pressure(bar=12.0)\n",
|
||||
"p2 = entropyk.Pressure(kpa=350.0)\n",
|
||||
"p3 = entropyk.Pressure(psi=150.0)\n",
|
||||
"\n",
|
||||
"print(\"Pression:\")\n",
|
||||
"print(f\" {p1} → {p1.to_bar():.2f} bar, {p1.to_kpa():.1f} kPa, {p1.to_psi():.1f} psi\")\n",
|
||||
"print(f\" {p2} → {p2.to_bar():.2f} bar\")\n",
|
||||
"print(f\" {p3} → {p3.to_bar():.2f} bar\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Température\n",
|
||||
"t1 = entropyk.Temperature(celsius=45.0)\n",
|
||||
"t2 = entropyk.Temperature(kelvin=273.15)\n",
|
||||
"t3 = entropyk.Temperature(fahrenheit=100.0)\n",
|
||||
"\n",
|
||||
"print(\"Température:\")\n",
|
||||
"print(f\" {t1} → {t1.to_celsius():.2f}°C, {t1.to_fahrenheit():.2f}°F\")\n",
|
||||
"print(f\" {t2} → {t2.to_celsius():.2f}°C (point de congélation)\")\n",
|
||||
"print(f\" {t3} → {t3.to_celsius():.2f}°C, {t3.to_kelvin():.2f} K\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Enthalpie\n",
|
||||
"h1 = entropyk.Enthalpy(kj_per_kg=420.0)\n",
|
||||
"h2 = entropyk.Enthalpy(j_per_kg=250000.0)\n",
|
||||
"\n",
|
||||
"print(\"Enthalpie:\")\n",
|
||||
"print(f\" {h1} → {h1.to_kj_per_kg():.1f} kJ/kg\")\n",
|
||||
"print(f\" {h2} → {h2.to_kj_per_kg():.1f} kJ/kg\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Débit massique\n",
|
||||
"m1 = entropyk.MassFlow(kg_per_s=0.05)\n",
|
||||
"m2 = entropyk.MassFlow(g_per_s=50.0)\n",
|
||||
"\n",
|
||||
"print(\"Débit massique:\")\n",
|
||||
"print(f\" {m1} → {m1.to_g_per_s():.1f} g/s\")\n",
|
||||
"print(f\" {m2} → {m2.to_kg_per_s():.4f} kg/s\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Cycle Simple avec Différents Fluides\n",
|
||||
"\n",
|
||||
"Construisons un cycle de réfrigération standard et comparons différents fluides."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def build_simple_cycle(fluid: str):\n",
|
||||
" \"\"\"Construit un cycle de réfrigération simple avec le fluide spécifié.\"\"\"\n",
|
||||
" system = entropyk.System()\n",
|
||||
" \n",
|
||||
" # Composants\n",
|
||||
" comp = entropyk.Compressor(\n",
|
||||
" speed_rpm=2900.0,\n",
|
||||
" displacement=0.0001,\n",
|
||||
" efficiency=0.85,\n",
|
||||
" fluid=fluid\n",
|
||||
" )\n",
|
||||
" cond = entropyk.Condenser(ua=5000.0)\n",
|
||||
" exv = entropyk.ExpansionValve(fluid=fluid, opening=0.8)\n",
|
||||
" evap = entropyk.Evaporator(ua=3000.0)\n",
|
||||
" \n",
|
||||
" # Ajouter au système\n",
|
||||
" comp_idx = system.add_component(comp)\n",
|
||||
" cond_idx = system.add_component(cond)\n",
|
||||
" exv_idx = system.add_component(exv)\n",
|
||||
" evap_idx = system.add_component(evap)\n",
|
||||
" \n",
|
||||
" # Connecter en cycle\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",
|
||||
" system.finalize()\n",
|
||||
" return system"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Test avec différents fluides HFC classiques\n",
|
||||
"hfc_fluids = [\"R134a\", \"R410A\", \"R407C\", \"R32\"]\n",
|
||||
"\n",
|
||||
"print(\"Cycles HFC classiques:\")\n",
|
||||
"print(\"-\" * 50)\n",
|
||||
"for fluid in hfc_fluids:\n",
|
||||
" system = build_simple_cycle(fluid)\n",
|
||||
" print(f\" {fluid:8s} → {system.state_vector_len} variables d'état\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Fluides HFO / Low-GWP\n",
|
||||
"\n",
|
||||
"Les HFO sont les alternatives à faible GWP (<150) pour remplacer les HFC."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# HFO et alternatives Low-GWP\n",
|
||||
"low_gwp_fluids = [\n",
|
||||
" (\"R1234yf\", \"HFO\", \"<1\", \"Remplacement R134a (automobile)\"),\n",
|
||||
" (\"R1234ze(E)\", \"HFO\", \"<1\", \"Remplacement R134a (stationnaire)\"),\n",
|
||||
" (\"R1233zd(E)\", \"HCFO\", \"1\", \"Remplacement R123 (basse pression)\"),\n",
|
||||
" (\"R1243zf\", \"HFO\", \"<1\", \"Nouveau fluide recherche\"),\n",
|
||||
" (\"R1336mzz(E)\", \"HFO\", \"<1\", \"ORC, haute température\"),\n",
|
||||
" (\"R513A\", \"Mélange\", \"631\", \"R134a + R1234yf (56/44)\"),\n",
|
||||
" (\"R454B\", \"Mélange\", \"146\", \"R32 + R1234yf (50/50) - Opteon XL41\"),\n",
|
||||
" (\"R452B\", \"Mélange\", \"676\", \"R32 + R125 + R1234yf - Opteon XL55\"),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"df_low_gwp = pd.DataFrame(low_gwp_fluids, columns=[\"Fluide\", \"Type\", \"GWP\", \"Usage\"])\n",
|
||||
"df_low_gwp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Test cycles HFO\n",
|
||||
"print(\"Cycles HFO / Low-GWP:\")\n",
|
||||
"print(\"-\" * 50)\n",
|
||||
"for fluid, _, _, _ in low_gwp_fluids:\n",
|
||||
" try:\n",
|
||||
" system = build_simple_cycle(fluid)\n",
|
||||
" print(f\" {fluid:12s} → ✅ Supporté ({system.state_vector_len} vars)\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\" {fluid:12s} → ❌ Erreur: {e}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Fluides Naturels\n",
|
||||
"\n",
|
||||
"Les fluides naturels ont un GWP de ~0 et sont l'avenir de la réfrigération."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Fluides naturels\n",
|
||||
"natural_fluids = [\n",
|
||||
" (\"R744\", \"CO2\", \"1\", \"Transcritique, commercial\"),\n",
|
||||
" (\"R290\", \"Propane\", \"3\", \"Climatisation, commercial\"),\n",
|
||||
" (\"R600a\", \"Isobutane\", \"3\", \"Domestique, commerc. faible charge\"),\n",
|
||||
" (\"R600\", \"Butane\", \"3\", \"Réfrigération basse température\"),\n",
|
||||
" (\"R1270\", \"Propylène\", \"3\", \"Climatisation industrielle\"),\n",
|
||||
" (\"R717\", \"Ammonia\", \"0\", \"Industriel, forte puissance\"),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"df_natural = pd.DataFrame(natural_fluids, columns=[\"Code ASHRAE\", \"Nom\", \"GWP\", \"Application\"])\n",
|
||||
"df_natural"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Test cycles fluides naturels\n",
|
||||
"print(\"Cycles fluides naturels:\")\n",
|
||||
"print(\"-\" * 50)\n",
|
||||
"for code, name, _, app in natural_fluids:\n",
|
||||
" try:\n",
|
||||
" system = build_simple_cycle(code)\n",
|
||||
" print(f\" {code:6s} ({name:10s}) → ✅ Supporté\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\" {code:6s} ({name:10s}) → ❌ Erreur: {e}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 5. Autres Réfrigérants (Classiques)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Autres réfrigérants disponibles\n",
|
||||
"other_refrigerants = [\n",
|
||||
" # CFC (obsolètes)\n",
|
||||
" \"R11\", \"R12\", \"R13\", \"R14\",\n",
|
||||
" # HCFC (phase-out)\n",
|
||||
" \"R22\", \"R123\", \"R141b\", \"R142b\",\n",
|
||||
" # HFC supplémentaires\n",
|
||||
" \"R23\", \"R41\", \"R113\", \"R114\", \"R115\", \"R116\",\n",
|
||||
" \"R124\", \"R143a\", \"R152A\", \"R218\", \"R227EA\",\n",
|
||||
" \"R236EA\", \"R236FA\", \"R245fa\", \"R245ca\", \"R365MFC\",\n",
|
||||
" \"RC318\", \"R507A\",\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"print(f\"Total réfrigérants classiques: {len(other_refrigerants)}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 6. Fluides Non-Réfrigérants"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Fluides non-réfrigérants disponibles\n",
|
||||
"other_fluids = [\n",
|
||||
" (\"Water\", \"H2O\", \"Fluide de travail, calibration\"),\n",
|
||||
" (\"Air\", \"N2+O2\", \"Climatisation, psychrométrie\"),\n",
|
||||
" (\"Nitrogen\", \"N2\", \"Cryogénie, inertage\"),\n",
|
||||
" (\"Oxygen\", \"O2\", \"Applications spéciales\"),\n",
|
||||
" (\"Argon\", \"Ar\", \"Cryogénie\"),\n",
|
||||
" (\"Helium\", \"He\", \"Cryogénie très basse T\"),\n",
|
||||
" (\"Hydrogen\", \"H2\", \"Énergie, cryogénie\"),\n",
|
||||
" (\"Methane\", \"CH4\", \"GNL, pétrole\"),\n",
|
||||
" (\"Ethane\", \"C2H6\", \"Pétrochimie\"),\n",
|
||||
" (\"Ethylene\", \"C2H4\", \"Pétrochimie\"),\n",
|
||||
" (\"Propane\", \"C3H8\", \"= R290\"),\n",
|
||||
" (\"Butane\", \"C4H10\", \"= R600\"),\n",
|
||||
" (\"Ethanol\", \"C2H5OH\",\"Solvant\"),\n",
|
||||
" (\"Methanol\", \"CH3OH\", \"Solvant\"),\n",
|
||||
" (\"Acetone\", \"C3H6O\", \"Solvant\"),\n",
|
||||
" (\"Benzene\", \"C6H6\", \"Chimie\"),\n",
|
||||
" (\"Toluene\", \"C7H8\", \"ORC\"),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"df_other = pd.DataFrame(other_fluids, columns=[\"Nom CoolProp\", \"Formule\", \"Usage\"])\n",
|
||||
"df_other"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 7. Résumé Complet des Fluides Disponibles"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Catégorisation complète\n",
|
||||
"fluid_summary = {\n",
|
||||
" \"Catégorie\": [\n",
|
||||
" \"HFC Classiques\",\n",
|
||||
" \"HFO / Low-GWP\",\n",
|
||||
" \"Alternatives (Mélanges)\",\n",
|
||||
" \"Fluides Naturels\",\n",
|
||||
" \"CFC/HCFC (Obsolètes)\",\n",
|
||||
" \"Autres HFC\",\n",
|
||||
" \"Non-Réfrigérants\",\n",
|
||||
" ],\n",
|
||||
" \"Exemples\": [\n",
|
||||
" \"R134a, R410A, R407C, R32, R125\",\n",
|
||||
" \"R1234yf, R1234ze(E), R1233zd(E)\",\n",
|
||||
" \"R513A, R454B, R452B, R507A\",\n",
|
||||
" \"R744 (CO2), R290, R600a, R717\",\n",
|
||||
" \"R11, R12, R22, R123, R141b\",\n",
|
||||
" \"R143a, R152A, R227EA, R245fa\",\n",
|
||||
" \"Water, Air, Nitrogen, Helium\",\n",
|
||||
" ],\n",
|
||||
" \"Nombre\": [5, 6, 4, 6, 8, 15, 17],\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"df_summary = pd.DataFrame(fluid_summary)\n",
|
||||
"print(\"\\n=== RÉSUMÉ DES FLUIDES DISPONIBLES ===\")\n",
|
||||
"print(f\"Total: {sum(fluid_summary['Nombre'])}+ fluides\\n\")\n",
|
||||
"df_summary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 8. Exemple: Cycle CO2 Transcritique\n",
|
||||
"\n",
|
||||
"Le CO2 (R744) nécessite un traitement spécial car le point critique est à 31°C seulement."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Cycle CO2 transcritique\n",
|
||||
"print(\"=== Cycle CO2 Transcritique (R744) ===\")\n",
|
||||
"print(\"\\nPropriétés du CO2:\")\n",
|
||||
"print(\" Point critique: 31.0°C, 73.8 bar\")\n",
|
||||
"print(\" GWP: 1\")\n",
|
||||
"print(\" Applications: Supermarchés, transports, chaleur industrielle\")\n",
|
||||
"\n",
|
||||
"co2_system = build_simple_cycle(\"R744\")\n",
|
||||
"print(f\"\\nSystème créé: {co2_system.state_vector_len} variables d'état\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 9. Exemple: Cycle Ammoniac (R717)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Cycle Ammoniac\n",
|
||||
"print(\"=== Cycle Ammoniac (R717) ===\")\n",
|
||||
"print(\"\\nPropriétés de l'Ammoniac:\")\n",
|
||||
"print(\" Point critique: 132.4°C, 113.3 bar\")\n",
|
||||
"print(\" GWP: 0 (naturel)\")\n",
|
||||
"print(\" haute efficacité, toxique mais détectable\")\n",
|
||||
"print(\" Applications: Industrie agroalimentaire, patinoires, entrepôts\")\n",
|
||||
"\n",
|
||||
"nh3_system = build_simple_cycle(\"R717\")\n",
|
||||
"print(f\"\\nSystème créé: {nh3_system.state_vector_len} variables d'état\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 10. Exemple: Cycle Propane (R290)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Cycle Propane\n",
|
||||
"print(\"=== Cycle Propane (R290) ===\")\n",
|
||||
"print(\"\\nPropriétés du Propane:\")\n",
|
||||
"print(\" Point critique: 96.7°C, 42.5 bar\")\n",
|
||||
"print(\" GWP: 3 (très bas)\")\n",
|
||||
"print(\" Excellentes propriétés thermodynamiques\")\n",
|
||||
"print(\" Inflammable (A3)\")\n",
|
||||
"print(\" Applications: Climatisation, pompes à chaleur, commercial\")\n",
|
||||
"\n",
|
||||
"r290_system = build_simple_cycle(\"R290\")\n",
|
||||
"print(f\"\\nSystème créé: {r290_system.state_vector_len} variables d'état\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 11. Configuration du Solveur"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Exemple de configuration du solveur pour résolution\n",
|
||||
"system = build_simple_cycle(\"R134a\")\n",
|
||||
"\n",
|
||||
"# Newton-Raphson avec recherche linéaire\n",
|
||||
"newton = entropyk.NewtonConfig(\n",
|
||||
" max_iterations=200,\n",
|
||||
" tolerance=1e-6,\n",
|
||||
" line_search=True,\n",
|
||||
" timeout_ms=10000\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Picard pour problèmes difficiles\n",
|
||||
"picard = entropyk.PicardConfig(\n",
|
||||
" max_iterations=500,\n",
|
||||
" tolerance=1e-4,\n",
|
||||
" relaxation=0.5\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Fallback: Newton puis Picard\n",
|
||||
"fallback = entropyk.FallbackConfig(newton=newton, picard=picard)\n",
|
||||
"\n",
|
||||
"print(f\"Solver configuré: {fallback}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 12. Conclusion\n",
|
||||
"\n",
|
||||
"### Fluides disponibles par application:\n",
|
||||
"\n",
|
||||
"| Application | Fluide recommandé | Alternatives |\n",
|
||||
"|-------------|-------------------|-------------|\n",
|
||||
"| Climatisation résidentielle | R32, R290 | R410A, R454B |\n",
|
||||
"| Climatisation commerciale | R410A, R32 | R454B, R290 |\n",
|
||||
"| Réfrigération commerciale | R404A, R744 | R455A, R290 |\n",
|
||||
"| Froid industriel | R717, R744 | R290 |\n",
|
||||
"| Domestique | R600a, R290 | R134a |\n",
|
||||
"| Automobile | R1234yf | R134a, R744 |\n",
|
||||
"| ORC haute température | R1336mzz(E), Toluene | R245fa |"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
@@ -16,6 +16,11 @@ classifiers = [
|
||||
"Topic :: Scientific/Engineering :: Physics",
|
||||
]
|
||||
description = "High-performance thermodynamic cycle simulation library"
|
||||
dependencies = [
|
||||
"ipykernel>=6.31.0",
|
||||
"maturin>=1.12.4",
|
||||
"numpy>=2.0.2",
|
||||
]
|
||||
|
||||
[tool.maturin]
|
||||
features = ["pyo3/extension-module"]
|
||||
|
||||
409
bindings/python/refrigerant_comparison.ipynb
Normal file
409
bindings/python/refrigerant_comparison.ipynb
Normal file
@@ -0,0 +1,409 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Comparaison des Réfrigérants pour Applications Courantes\n",
|
||||
"\n",
|
||||
"Ce notebook compare les propriétés thermodynamiques de différents réfrigérants pour des applications typiques:\n",
|
||||
"\n",
|
||||
"- **Climatisation** : Température d'évaporation ~7°C, Condensation ~45°C\n",
|
||||
"- **Réfrigération commerciale** : Tévap ~-10°C, Tcond ~40°C\n",
|
||||
"- **Froid négatif** : Tévap ~-35°C, Tcond ~35°C"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import entropyk\n",
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"# Pour les graphiques (optionnel)\n",
|
||||
"try:\n",
|
||||
" import matplotlib.pyplot as plt\n",
|
||||
" HAS_MATPLOTLIB = True\n",
|
||||
"except ImportError:\n",
|
||||
" HAS_MATPLOTLIB = False\n",
|
||||
" print(\"matplotlib non disponible - graphiques désactivés\")\n",
|
||||
"\n",
|
||||
"print(\"Entropyk chargé avec succès!\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Paramètres des Applications"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Définir les conditions opératoires pour chaque application\n",
|
||||
"applications = {\n",
|
||||
" \"Climatisation\": {\n",
|
||||
" \"T_evap_C\": 7.0,\n",
|
||||
" \"T_cond_C\": 45.0,\n",
|
||||
" \"surchauffe_K\": 5.0,\n",
|
||||
" \"sous-refroidissement_K\": 3.0,\n",
|
||||
" },\n",
|
||||
" \"Réfrigération commerciale\": {\n",
|
||||
" \"T_evap_C\": -10.0,\n",
|
||||
" \"T_cond_C\": 40.0,\n",
|
||||
" \"surchauffe_K\": 5.0,\n",
|
||||
" \"sous-refroidissement_K\": 3.0,\n",
|
||||
" },\n",
|
||||
" \"Froid négatif\": {\n",
|
||||
" \"T_evap_C\": -35.0,\n",
|
||||
" \"T_cond_C\": 35.0,\n",
|
||||
" \"surchauffe_K\": 5.0,\n",
|
||||
" \"sous-refroidissement_K\": 3.0,\n",
|
||||
" },\n",
|
||||
" \"Pompe à chaleur\": {\n",
|
||||
" \"T_evap_C\": -5.0,\n",
|
||||
" \"T_cond_C\": 55.0,\n",
|
||||
" \"surchauffe_K\": 5.0,\n",
|
||||
" \"sous-refroidissement_K\": 5.0,\n",
|
||||
" },\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"for app_name, params in applications.items():\n",
|
||||
" print(f\"{app_name}:\")\n",
|
||||
" print(f\" Évaporation: {params['T_evap_C']}°C\")\n",
|
||||
" print(f\" Condensation: {params['T_cond_C']}°C\")\n",
|
||||
" print(f\" Delta T: {params['T_cond_C'] - params['T_evap_C']}K\")\n",
|
||||
" print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Fluides à Comparer"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Liste des fluides avec leurs propriétés GWP et sécurité\n",
|
||||
"fluides = {\n",
|
||||
" \"R134a\": {\"GWP\": 1430, \"Classe\": \"A1\", \"Type\": \"HFC\"},\n",
|
||||
" \"R410A\": {\"GWP\": 2088, \"Classe\": \"A1\", \"Type\": \"HFC\"},\n",
|
||||
" \"R32\": {\"GWP\": 675, \"Classe\": \"A2L\", \"Type\": \"HFC\"},\n",
|
||||
" \"R290\": {\"GWP\": 3, \"Classe\": \"A3\", \"Type\": \"Naturel\"},\n",
|
||||
" \"R600a\": {\"GWP\": 3, \"Classe\": \"A3\", \"Type\": \"Naturel\"},\n",
|
||||
" \"R744\": {\"GWP\": 1, \"Classe\": \"A1\", \"Type\": \"Naturel\"},\n",
|
||||
" \"R1234yf\": {\"GWP\": 4, \"Classe\": \"A2L\", \"Type\": \"HFO\"},\n",
|
||||
" \"R1234ze(E)\": {\"GWP\": 7, \"Classe\": \"A2L\", \"Type\": \"HFO\"},\n",
|
||||
" \"R454B\": {\"GWP\": 146, \"Classe\": \"A2L\", \"Type\": \"Mélange\"},\n",
|
||||
" \"R513A\": {\"GWP\": 631, \"Classe\": \"A1\", \"Type\": \"Mélange\"},\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"df_fluides = pd.DataFrame.from_dict(fluides, orient='index')\n",
|
||||
"df_fluides.index.name = \"Fluide\"\n",
|
||||
"df_fluides"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Comparaison des Pressions de Travail"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Afficher les pressions de saturation pour chaque application\n",
|
||||
"print(\"=== Pressions de Saturation (bar) ===\\n\")\n",
|
||||
"\n",
|
||||
"for app_name, params in applications.items():\n",
|
||||
" print(f\"--- {app_name} ---\")\n",
|
||||
" print(f\"{'Fluide':12s} {'P_evap':>10s} {'P_cond':>10s} {'Ratio':>8s}\")\n",
|
||||
" print(\"-\" * 45)\n",
|
||||
" \n",
|
||||
" for fluide in fluides:\n",
|
||||
" # Note: Les valeurs réelles nécessitent CoolProp\n",
|
||||
" # Ici on utilise des valeurs approximatives pour démonstration\n",
|
||||
" if fluide == \"R744\":\n",
|
||||
" # CO2 a des pressions très élevées\n",
|
||||
" p_evap_approx = {\"Climatisation\": 45, \"Réfrigération commerciale\": 26, \"Froid négatif\": 12, \"Pompe à chaleur\": 30}\n",
|
||||
" p_cond_approx = {\"Climatisation\": 90, \"Réfrigération commerciale\": 75, \"Froid négatif\": 65, \"Pompe à chaleur\": 120}\n",
|
||||
" elif fluide == \"R410A\":\n",
|
||||
" p_evap_approx = {\"Climatisation\": 6.2, \"Réfrigération commerciale\": 3.5, \"Froid négatif\": 1.5, \"Pompe à chaleur\": 4.8}\n",
|
||||
" p_cond_approx = {\"Climatisation\": 26.5, \"Réfrigération commerciale\": 24, \"Froid négatif\": 21, \"Pompe à chaleur\": 34}\n",
|
||||
" elif fluide == \"R134a\":\n",
|
||||
" p_evap_approx = {\"Climatisation\": 3.8, \"Réfrigération commerciale\": 2.0, \"Froid négatif\": 0.8, \"Pompe à chaleur\": 2.8}\n",
|
||||
" p_cond_approx = {\"Climatisation\": 11.6, \"Réfrigération commerciale\": 10.2, \"Froid négatif\": 8.9, \"Pompe à chaleur\": 15}\n",
|
||||
" elif fluide == \"R32\":\n",
|
||||
" p_evap_approx = {\"Climatisation\": 5.8, \"Réfrigération commerciale\": 3.2, \"Froid négatif\": 1.3, \"Pompe à chaleur\": 4.4}\n",
|
||||
" p_cond_approx = {\"Climatisation\": 24, \"Réfrigération commerciale\": 21.5, \"Froid négatif\": 19, \"Pompe à chaleur\": 30}\n",
|
||||
" elif fluide == \"R290\":\n",
|
||||
" p_evap_approx = {\"Climatisation\": 5.5, \"Réfrigération commerciale\": 2.8, \"Froid négatif\": 1.0, \"Pompe à chaleur\": 4.0}\n",
|
||||
" p_cond_approx = {\"Climatisation\": 15.5, \"Réfrigération commerciale\": 13.5, \"Froid négatif\": 11.5, \"Pompe à chaleur\": 20}\n",
|
||||
" else:\n",
|
||||
" # Valeurs génériques\n",
|
||||
" p_evap_approx = {k: 3.0 for k in applications}\n",
|
||||
" p_cond_approx = {k: 10.0 for k in applications}\n",
|
||||
" \n",
|
||||
" p_evap = p_evap_approx[app_name]\n",
|
||||
" p_cond = p_cond_approx[app_name]\n",
|
||||
" ratio = p_cond / p_evap\n",
|
||||
" \n",
|
||||
" print(f\"{fluide:12s} {p_evap:10.1f} {p_cond:10.1f} {ratio:8.2f}\")\n",
|
||||
" print()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Performance Théorique (COP) par Application"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# COP théorique de Carnot et valeurs typiques\n",
|
||||
"print(\"=== COP par Application ===\\n\")\n",
|
||||
"\n",
|
||||
"cop_data = []\n",
|
||||
"for app_name, params in applications.items():\n",
|
||||
" T_evap_K = params['T_evap_C'] + 273.15\n",
|
||||
" T_cond_K = params['T_cond_C'] + 273.15\n",
|
||||
" \n",
|
||||
" # COP de Carnot\n",
|
||||
" cop_carnot = T_evap_K / (T_cond_K - T_evap_K)\n",
|
||||
" \n",
|
||||
" # COP réels typiques (60-70% de Carnot)\n",
|
||||
" cop_real = cop_carnot * 0.65\n",
|
||||
" \n",
|
||||
" cop_data.append({\n",
|
||||
" \"Application\": app_name,\n",
|
||||
" \"T_evap (°C)\": params['T_evap_C'],\n",
|
||||
" \"T_cond (°C)\": params['T_cond_C'],\n",
|
||||
" \"COP Carnot\": round(cop_carnot, 2),\n",
|
||||
" \"COP Réel (~)\": round(cop_real, 2),\n",
|
||||
" })\n",
|
||||
"\n",
|
||||
"df_cop = pd.DataFrame(cop_data)\n",
|
||||
"df_cop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 5. Recommandations par Application"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"recommandations = {\n",
|
||||
" \"Climatisation\": {\n",
|
||||
" \"Principal\": \"R32\",\n",
|
||||
" \"Alternatives\": [\"R290\", \"R454B\"],\n",
|
||||
" \"Raisons\": \"R32: bon COP, GWP modéré, compatible R410A. R290: meilleur COP, faible charge.\",\n",
|
||||
" },\n",
|
||||
" \"Réfrigération commerciale\": {\n",
|
||||
" \"Principal\": \"R744 (CO2)\",\n",
|
||||
" \"Alternatives\": [\"R290\", \"R404A (existant)\"],\n",
|
||||
" \"Raisons\": \"CO2: GWP=1, toutes températures. R290: haute efficacité, charge limitée.\",\n",
|
||||
" },\n",
|
||||
" \"Froid négatif\": {\n",
|
||||
" \"Principal\": \"R744 (CO2) cascade\",\n",
|
||||
" \"Alternatives\": [\"R290/R600a cascade\"],\n",
|
||||
" \"Raisons\": \"CO2 cascade ou R290/R600a pour GWP minimal.\",\n",
|
||||
" },\n",
|
||||
" \"Pompe à chaleur\": {\n",
|
||||
" \"Principal\": \"R290\",\n",
|
||||
" \"Alternatives\": [\"R32\", \"R744\"],\n",
|
||||
" \"Raisons\": \"R290: excellent COP haute température. R744: transcritique pour eau chaude.\",\n",
|
||||
" },\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"for app, rec in recommandations.items():\n",
|
||||
" print(f\"\\n{'='*60}\")\n",
|
||||
" print(f\"{app}\")\n",
|
||||
" print(f\"{'='*60}\")\n",
|
||||
" print(f\" Principal: {rec['Principal']}\")\n",
|
||||
" print(f\" Alternatives: {', '.join(rec['Alternatives'])}\")\n",
|
||||
" print(f\" Raisons: {rec['Raisons']}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 6. Matrice de Sélection Rapide"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Matrice de compatibilité\n",
|
||||
"compatibilite = {\n",
|
||||
" \"R134a\": {\"Climatisation\": \"★★★\", \"Réfrigération\": \"★★★\", \"Froid négatif\": \"★★\", \"Pompe chaleur\": \"★★\", \"GWP\": 1430},\n",
|
||||
" \"R410A\": {\"Climatisation\": \"★★★★\", \"Réfrigération\": \"★★\", \"Froid négatif\": \"★\", \"Pompe chaleur\": \"★★★\", \"GWP\": 2088},\n",
|
||||
" \"R32\": {\"Climatisation\": \"★★★★★\", \"Réfrigération\": \"★★★\", \"Froid négatif\": \"★\", \"Pompe chaleur\": \"★★★★\", \"GWP\": 675},\n",
|
||||
" \"R290\": {\"Climatisation\": \"★★★★★\", \"Réfrigération\": \"★★★★\", \"Froid négatif\": \"★★★\", \"Pompe chaleur\": \"★★★★★\", \"GWP\": 3},\n",
|
||||
" \"R600a\": {\"Climatisation\": \"★★\", \"Réfrigération\": \"★★★\", \"Froid négatif\": \"★★★★\", \"Pompe chaleur\": \"★★\", \"GWP\": 3},\n",
|
||||
" \"R744\": {\"Climatisation\": \"★★★\", \"Réfrigération\": \"★★★★★\", \"Froid négatif\": \"★★★★★\", \"Pompe chaleur\": \"★★★★\", \"GWP\": 1},\n",
|
||||
" \"R1234yf\": {\"Climatisation\": \"★★★★\", \"Réfrigération\": \"★★★\", \"Froid négatif\": \"★\", \"Pompe chaleur\": \"★★\", \"GWP\": 4},\n",
|
||||
" \"R454B\": {\"Climatisation\": \"★★★★\", \"Réfrigération\": \"★★★\", \"Froid négatif\": \"★\", \"Pompe chaleur\": \"★★★\", \"GWP\": 146},\n",
|
||||
" \"R513A\": {\"Climatisation\": \"★★★\", \"Réfrigération\": \"★★★\", \"Froid négatif\": \"★★\", \"Pompe chaleur\": \"★★\", \"GWP\": 631},\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"print(\"\\n=== Matrice de Sélection ===\")\n",
|
||||
"print(\"★★★★★ = Excellent, ★★★★ = Très bon, ★★★ = Bon, ★★ = Acceptable, ★ = Déconseillé\\n\")\n",
|
||||
"\n",
|
||||
"for fluide, scores in compatibilite.items():\n",
|
||||
" print(f\"{fluide:12s} | GWP:{scores['GWP']:5d} | Clim:{scores['Climatisation']} | Réfrig:{scores['Réfrigération']} | Nég:{scores['Froid négatif']} | PAC:{scores['Pompe chaleur']}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 7. Exemple de Code: Cycle Multi-Fluides"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def create_cycle_for_fluid(fluid: str, app_name: str = \"Climatisation\"):\n",
|
||||
" \"\"\"\n",
|
||||
" Crée un cycle optimisé pour un fluide et une application donnée.\n",
|
||||
" \"\"\"\n",
|
||||
" params = applications[app_name]\n",
|
||||
" \n",
|
||||
" # Ajuster les composants selon le fluide\n",
|
||||
" if fluid == \"R744\":\n",
|
||||
" # CO2: haute pression, échangeur gaz cooler\n",
|
||||
" ua_cond = 8000.0 # Plus élevé pour CO2\n",
|
||||
" ua_evap = 5000.0\n",
|
||||
" elif fluid == \"R290\" or fluid == \"R600a\":\n",
|
||||
" # Hydrocarbures: excellents transferts thermiques\n",
|
||||
" ua_cond = 4000.0\n",
|
||||
" ua_evap = 3500.0\n",
|
||||
" else:\n",
|
||||
" # HFC/HFO standards\n",
|
||||
" ua_cond = 5000.0\n",
|
||||
" ua_evap = 3000.0\n",
|
||||
" \n",
|
||||
" system = entropyk.System()\n",
|
||||
" \n",
|
||||
" comp = entropyk.Compressor(\n",
|
||||
" speed_rpm=2900.0,\n",
|
||||
" displacement=0.0001,\n",
|
||||
" efficiency=0.85,\n",
|
||||
" fluid=fluid\n",
|
||||
" )\n",
|
||||
" cond = entropyk.Condenser(ua=ua_cond)\n",
|
||||
" exv = entropyk.ExpansionValve(fluid=fluid, opening=0.8)\n",
|
||||
" evap = entropyk.Evaporator(ua=ua_evap)\n",
|
||||
" \n",
|
||||
" comp_idx = system.add_component(comp)\n",
|
||||
" cond_idx = system.add_component(cond)\n",
|
||||
" exv_idx = system.add_component(exv)\n",
|
||||
" evap_idx = system.add_component(evap)\n",
|
||||
" \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",
|
||||
" system.finalize()\n",
|
||||
" return system\n",
|
||||
"\n",
|
||||
"# Test\n",
|
||||
"for fluid in [\"R134a\", \"R32\", \"R290\", \"R744\"]:\n",
|
||||
" system = create_cycle_for_fluid(fluid, \"Climatisation\")\n",
|
||||
" print(f\"{fluid:8s}: {system.state_vector_len} variables d'état\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 8. Résumé Exécutif"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"\"\"\n",
|
||||
"╔══════════════════════════════════════════════════════════════════════════╗\n",
|
||||
"║ RÉSUMÉ - SÉLECTION DES RÉFRIGÉRANTS ║\n",
|
||||
"╠══════════════════════════════════════════════════════════════════════════╣\n",
|
||||
"║ CLIMATISATION ║\n",
|
||||
"║ → R32 (standard), R290 (performant, charge limitée), R454B (retrofit) ║\n",
|
||||
"║ ║\n",
|
||||
"║ RÉFRIGÉRATION COMMERCIALE ║\n",
|
||||
"║ → R744/CO2 (futur), R290 (nouveau), R404A (existant) ║\n",
|
||||
"║ ║\n",
|
||||
"║ FROID NÉGATIF ║\n",
|
||||
"║ → R744 cascade, R290/R600a cascade ║\n",
|
||||
"║ ║\n",
|
||||
"║ POMPE À CHALEUR ║\n",
|
||||
"║ → R290 (haute température), R32 (standard), R744 (transcritique) ║\n",
|
||||
"╠══════════════════════════════════════════════════════════════════════════╣\n",
|
||||
"║ TENDANCE RÉGLEMENTAIRE: GWP < 750 d'ici 2025-2030 ║\n",
|
||||
"║ → Privilégier: R290, R600a, R744, R1234yf, R32 ║\n",
|
||||
"╚══════════════════════════════════════════════════════════════════════════╝\n",
|
||||
"\"\"\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
@@ -91,6 +91,7 @@ impl std::fmt::Debug for SimpleAdapter {
|
||||
/// )
|
||||
#[pyclass(name = "Compressor", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
#[allow(dead_code)] // Fields reserved until SimpleAdapter type-state migration
|
||||
pub struct PyCompressor {
|
||||
pub(crate) coefficients: entropyk::Ahri540Coefficients,
|
||||
pub(crate) speed_rpm: f64,
|
||||
@@ -381,6 +382,7 @@ impl PyExpansionValve {
|
||||
/// density=1140.0, viscosity=0.0002)
|
||||
#[pyclass(name = "Pipe", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
#[allow(dead_code)] // Fields reserved until SimpleAdapter type-state migration
|
||||
pub struct PyPipe {
|
||||
pub(crate) length: f64,
|
||||
pub(crate) diameter: f64,
|
||||
|
||||
@@ -37,6 +37,7 @@ pub fn register_exceptions(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
}
|
||||
|
||||
/// Converts a `ThermoError` into the appropriate Python exception.
|
||||
#[allow(dead_code)]
|
||||
pub fn thermo_error_to_pyerr(err: entropyk::ThermoError) -> PyErr {
|
||||
use entropyk::ThermoError;
|
||||
match &err {
|
||||
@@ -48,6 +49,8 @@ pub fn thermo_error_to_pyerr(err: entropyk::ThermoError) -> PyErr {
|
||||
TimeoutError::new_err(msg)
|
||||
} else if solver_msg.contains("saturation") || solver_msg.contains("Saturation") {
|
||||
ControlSaturationError::new_err(msg)
|
||||
} else if solver_msg.contains("validation") || solver_msg.contains("Validation") {
|
||||
ValidationError::new_err(msg)
|
||||
} else {
|
||||
SolverError::new_err(msg)
|
||||
}
|
||||
@@ -67,6 +70,7 @@ pub fn thermo_error_to_pyerr(err: entropyk::ThermoError) -> PyErr {
|
||||
| ThermoError::Mixture(_)
|
||||
| ThermoError::InvalidInput(_)
|
||||
| ThermoError::NotSupported(_)
|
||||
| ThermoError::NotFinalized => EntropykError::new_err(err.to_string()),
|
||||
| ThermoError::NotFinalized
|
||||
| ThermoError::Validation { .. } => EntropykError::new_err(err.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ fn entropyk(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<solver::PyFallbackConfig>()?;
|
||||
m.add_class::<solver::PyConvergedState>()?;
|
||||
m.add_class::<solver::PyConvergenceStatus>()?;
|
||||
m.add_class::<solver::PyConstraint>()?;
|
||||
m.add_class::<solver::PyBoundedVariable>()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//! Python wrappers for Entropyk solver and system types.
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::exceptions::{PyValueError, PyRuntimeError};
|
||||
use std::time::Duration;
|
||||
@@ -25,7 +23,90 @@ use crate::components::AnyPyComponent;
|
||||
/// system.finalize()
|
||||
#[pyclass(name = "System", module = "entropyk", unsendable)]
|
||||
pub struct PySystem {
|
||||
inner: entropyk_solver::System,
|
||||
pub(crate) inner: entropyk_solver::System,
|
||||
}
|
||||
|
||||
#[pyclass(name = "Constraint")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyConstraint {
|
||||
pub(crate) inner: entropyk_solver::inverse::Constraint,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyConstraint {
|
||||
#[staticmethod]
|
||||
#[pyo3(signature = (id, component_id, target_value, tolerance=1e-6))]
|
||||
fn superheat(id: String, component_id: String, target_value: f64, tolerance: f64) -> Self {
|
||||
use entropyk_solver::inverse::{ComponentOutput, Constraint, ConstraintId};
|
||||
Self {
|
||||
inner: Constraint::with_tolerance(
|
||||
ConstraintId::new(id),
|
||||
ComponentOutput::Superheat { component_id },
|
||||
target_value,
|
||||
tolerance,
|
||||
).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
#[pyo3(signature = (id, component_id, target_value, tolerance=1e-6))]
|
||||
fn subcooling(id: String, component_id: String, target_value: f64, tolerance: f64) -> Self {
|
||||
use entropyk_solver::inverse::{ComponentOutput, Constraint, ConstraintId};
|
||||
Self {
|
||||
inner: Constraint::with_tolerance(
|
||||
ConstraintId::new(id),
|
||||
ComponentOutput::Subcooling { component_id },
|
||||
target_value,
|
||||
tolerance,
|
||||
).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
#[pyo3(signature = (id, component_id, target_value, tolerance=1e-6))]
|
||||
fn capacity(id: String, component_id: String, target_value: f64, tolerance: f64) -> Self {
|
||||
use entropyk_solver::inverse::{ComponentOutput, Constraint, ConstraintId};
|
||||
Self {
|
||||
inner: Constraint::with_tolerance(
|
||||
ConstraintId::new(id),
|
||||
ComponentOutput::Capacity { component_id },
|
||||
target_value,
|
||||
tolerance,
|
||||
).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!("Constraint(id='{}', target={}, tol={})", self.inner.id(), self.inner.target_value(), self.inner.tolerance())
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "BoundedVariable")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyBoundedVariable {
|
||||
pub(crate) inner: entropyk_solver::inverse::BoundedVariable,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyBoundedVariable {
|
||||
#[new]
|
||||
#[pyo3(signature = (id, value, min, max, component_id=None))]
|
||||
fn new(id: String, value: f64, min: f64, max: f64, component_id: Option<String>) -> PyResult<Self> {
|
||||
use entropyk_solver::inverse::{BoundedVariable, BoundedVariableId};
|
||||
let inner = match component_id {
|
||||
Some(cid) => BoundedVariable::with_component(BoundedVariableId::new(id), cid, value, min, max),
|
||||
None => BoundedVariable::new(BoundedVariableId::new(id), value, min, max),
|
||||
};
|
||||
match inner {
|
||||
Ok(v) => Ok(Self { inner: v }),
|
||||
Err(e) => Err(PyValueError::new_err(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
// use is_saturated if available but simpler:
|
||||
format!("BoundedVariable(id='{}', value={}, bounds=[{}, {}])", self.inner.id(), self.inner.value(), self.inner.min(), self.inner.max())
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
@@ -69,6 +150,34 @@ impl PySystem {
|
||||
Ok(edge.index())
|
||||
}
|
||||
|
||||
/// Register a human-readable name for a component node to be used in Constraints.
|
||||
fn register_component_name(&mut self, name: &str, node_idx: usize) -> PyResult<()> {
|
||||
let node = petgraph::graph::NodeIndex::new(node_idx);
|
||||
self.inner.register_component_name(name, node);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a constraint to the system.
|
||||
fn add_constraint(&mut self, constraint: &PyConstraint) -> PyResult<()> {
|
||||
self.inner.add_constraint(constraint.inner.clone())
|
||||
.map_err(|e| PyValueError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
/// Add a bounded variable to the system.
|
||||
fn add_bounded_variable(&mut self, variable: &PyBoundedVariable) -> PyResult<()> {
|
||||
self.inner.add_bounded_variable(variable.inner.clone())
|
||||
.map_err(|e| PyValueError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
/// Link a constraint to a control variable for the inverse solver.
|
||||
fn link_constraint_to_control(&mut self, constraint_id: &str, control_id: &str) -> PyResult<()> {
|
||||
use entropyk_solver::inverse::{ConstraintId, BoundedVariableId};
|
||||
self.inner.link_constraint_to_control(
|
||||
&ConstraintId::new(constraint_id),
|
||||
&BoundedVariableId::new(control_id),
|
||||
).map_err(|e| PyValueError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
/// Finalize the system graph: build state index mapping and validate topology.
|
||||
///
|
||||
/// Must be called before ``solve()``.
|
||||
|
||||
1024
bindings/python/uv.lock
generated
1024
bindings/python/uv.lock
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user