Files
Entropyk/simulation_interactive.ipynb
sepehr 5bd180b5b8
Some checks failed
CI / check (push) Has been cancelled
Snapshot WIP: solver HP epic progress, BPHX/HX physics, BMAD skill refresh.
Capture uncommitted solver robustness work (regularization, domain errors, linear solver lifecycle, tube DP/MSH), web workbench updates, and synced BMAD skills across IDE agent folders before starting BPHX pressure-drop.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-19 16:35:31 +02:00

291 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"id": "390509bb",
"metadata": {},
"source": [
"# 🌡️ Entropyk — Simulateur interactif\n",
"\n",
"Configurez les paramètres du cycle frigorifique R410A via les sliders, puis cliquez **▶ Simuler**."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "164510d5",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ Prêt\n"
]
}
],
"source": [
"import json, subprocess, tempfile, os\n",
"import ipywidgets as widgets\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.patches as mpatches\n",
"from IPython.display import display, clear_output\n",
"\n",
"CLI = r\"C:\\Users\\serameza\\impact\\dev\\Entropyk-main\\entropyk\\target\\debug\\entropyk-cli.exe\"\n",
"BASE_CONFIG = r\"C:\\Users\\serameza\\impact\\dev\\Entropyk-main\\entropyk\\crates\\cli\\examples\\chiller_r410a_full_physics.json\"\n",
"print('✅ Prêt')"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c55d1ff0",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "36f6e74bf5a64e9f8a4972460fbc2b00",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(HTML(value='<h3>⚙️ Paramètres du cycle</h3>'), FloatSlider(value=50.0, description='T condensat…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# ── Widgets de saisie ────────────────────────────────────────────────────────\n",
"style = {'description_width': '200px'}\n",
"layout = widgets.Layout(width='500px')\n",
"\n",
"w_t_cond = widgets.FloatSlider(value=50, min=30, max=70, step=1,\n",
" description='T condensation (°C)', style=style, layout=layout)\n",
"\n",
"w_t_evap = widgets.FloatSlider(value=2, min=-20, max=20, step=1,\n",
" description='T évaporation (°C)', style=style, layout=layout)\n",
"\n",
"w_eta = widgets.FloatSlider(value=0.75, min=0.50, max=0.95, step=0.01,\n",
" description='η isentropique ()', style=style, layout=layout,\n",
" readout_format='.2f')\n",
"\n",
"w_superheat = widgets.FloatSlider(value=5, min=0, max=20, step=0.5,\n",
" description='Surchauffe (K)', style=style, layout=layout)\n",
"\n",
"w_ua_cond = widgets.IntSlider(value=5000, min=1000, max=20000, step=500,\n",
" description='UA condenseur (W/K)', style=style, layout=layout)\n",
"\n",
"w_ua_evap = widgets.IntSlider(value=6000, min=1000, max=20000, step=500,\n",
" description='UA évaporateur (W/K)', style=style, layout=layout)\n",
"\n",
"btn_run = widgets.Button(description='▶ Simuler', button_style='success',\n",
" layout=widgets.Layout(width='200px', height='40px'))\n",
"\n",
"out = widgets.Output()\n",
"\n",
"display(\n",
" widgets.VBox([\n",
" widgets.HTML('<h3>⚙️ Paramètres du cycle</h3>'),\n",
" w_t_cond, w_t_evap, w_eta, w_superheat,\n",
" widgets.HTML('<h3>🔁 Échangeurs</h3>'),\n",
" w_ua_cond, w_ua_evap,\n",
" btn_run,\n",
" out\n",
" ])\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "fd926e11",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"🎯 Cliquez ▶ Simuler dans la cellule précédente\n"
]
}
],
"source": [
"def parse_cli_output(text):\n",
" \"\"\"Extract edges and status from CLI stdout.\"\"\"\n",
" edges, status, residual, iterations = [], 'UNKNOWN', None, None\n",
" for line in text.splitlines():\n",
" line = line.strip()\n",
" if 'Status:' in line:\n",
" status = 'CONVERGED' if 'CONVERGED' in line else 'DIVERGED'\n",
" if 'Residual:' in line:\n",
" try: residual = float(line.split()[-1])\n",
" except: pass\n",
" if 'Iterations:' in line:\n",
" try: iterations = int(line.split()[-1])\n",
" except: pass\n",
" if line.startswith('Edge'):\n",
" # Edge 0: P = 30.711 bar, h = 433.16 kJ/kg\n",
" try:\n",
" parts = line.split(':')\n",
" idx = int(parts[0].replace('Edge','').strip())\n",
" nums = [float(x.split()[0]) for x in parts[1].split(',')]\n",
" p_bar, h_kjkg = nums[0], nums[1]\n",
" edges.append({'idx': idx, 'P_bar': p_bar, 'H_kJkg': h_kjkg})\n",
" except: pass\n",
" return {'status': status, 'residual': residual, 'iterations': iterations, 'edges': edges}\n",
"\n",
"\n",
"def run_simulation(b):\n",
" with out:\n",
" clear_output(wait=True)\n",
"\n",
" # Build config from widgets\n",
" t_cond_k = w_t_cond.value + 273.15\n",
" t_evap_k = w_t_evap.value + 273.15\n",
"\n",
" with open(BASE_CONFIG) as f:\n",
" cfg = json.load(f)\n",
"\n",
" for comp in cfg['circuits'][0]['components']:\n",
" if comp['type'] == 'IsentropicCompressor':\n",
" comp['isentropic_efficiency'] = w_eta.value\n",
" comp['t_cond_k'] = t_cond_k\n",
" comp['t_evap_k'] = t_evap_k\n",
" comp['superheat_k'] = w_superheat.value\n",
" elif comp['type'] == 'Condenser':\n",
" comp['ua'] = w_ua_cond.value\n",
" comp['t_sat_k'] = t_cond_k\n",
" elif comp['type'] == 'IsenthalpicExpansionValve':\n",
" comp['t_evap_k'] = t_evap_k\n",
" elif comp['type'] == 'Evaporator':\n",
" comp['ua'] = w_ua_evap.value\n",
" comp['t_sat_k'] = t_evap_k\n",
" comp['superheat_k'] = w_superheat.value\n",
"\n",
" with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:\n",
" json.dump(cfg, f, indent=2)\n",
" tmp_path = f.name\n",
"\n",
" try:\n",
" result = subprocess.run([CLI, 'run', '--config', tmp_path],\n",
" capture_output=True, text=True, timeout=120)\n",
" output = result.stdout + result.stderr\n",
" data = parse_cli_output(output)\n",
"\n",
" if data['status'] == 'CONVERGED':\n",
" print(f\"✅ CONVERGÉ en {data['iterations']} itérations | résidu = {data['residual']:.2e}\")\n",
" print()\n",
"\n",
" edges = data['edges']\n",
" labels = ['Sortie compresseur', 'Sortie condenseur', 'Sortie EXV', 'Sortie évaporateur']\n",
" print(f\"{'Point':<25} {'P (bar)':>10} {'h (kJ/kg)':>12}\")\n",
" print('-' * 50)\n",
" for e in edges[:4]:\n",
" lbl = labels[e['idx']] if e['idx'] < len(labels) else f\"Edge {e['idx']}\"\n",
" print(f\"{lbl:<25} {e['P_bar']:>10.3f} {e['H_kJkg']:>12.2f}\")\n",
"\n",
" # Compute COP\n",
" if len(edges) >= 4:\n",
" h_comp_in = edges[3]['H_kJkg'] # evap outlet = comp inlet\n",
" h_comp_out = edges[0]['H_kJkg'] # comp outlet\n",
" h_exv_in = edges[1]['H_kJkg'] # cond outlet = exv inlet\n",
" w_comp = h_comp_out - h_comp_in\n",
" q_evap = h_comp_in - h_exv_in\n",
" cop = q_evap / w_comp if w_comp > 0 else 0\n",
" print()\n",
" print(f\"{'W compresseur':<25} {w_comp:>10.2f} kJ/kg\")\n",
" print(f\"{'Q évaporateur':<25} {q_evap:>10.2f} kJ/kg\")\n",
" print(f\"{'COP (froid)':<25} {cop:>10.2f}\")\n",
"\n",
" plot_cycle(edges[:4], labels)\n",
"\n",
" else:\n",
" print('❌ DIVERGENCE')\n",
" print(output[-2000:])\n",
"\n",
" except subprocess.TimeoutExpired:\n",
" print('⏱ Timeout')\n",
" finally:\n",
" os.unlink(tmp_path)\n",
"\n",
"\n",
"def plot_cycle(edges, labels):\n",
" if len(edges) < 4:\n",
" return\n",
" h = [e['H_kJkg'] for e in edges]\n",
" p = [e['P_bar'] for e in edges]\n",
"\n",
" # Close the cycle\n",
" h_cycle = h + [h[0]]\n",
" p_cycle = p + [p[0]]\n",
"\n",
" fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
"\n",
" # ── P-h diagram ──────────────────────────────────────────────────────────\n",
" ax = axes[0]\n",
" colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12']\n",
" ax.plot(h_cycle, p_cycle, 'k-', linewidth=2, zorder=2)\n",
" for i, (hi, pi) in enumerate(zip(h[:4], p[:4])):\n",
" ax.scatter([hi], [pi], color=colors[i], s=100, zorder=3)\n",
" ax.annotate(f\"{i+1}. {labels[i]}\\n({pi:.1f} bar, {hi:.0f} kJ/kg)\",\n",
" (hi, pi), textcoords='offset points', xytext=(8, 5), fontsize=8)\n",
" ax.set_xlabel('Enthalpie h (kJ/kg)', fontsize=11)\n",
" ax.set_ylabel('Pression P (bar)', fontsize=11)\n",
" ax.set_title('Diagramme P-h du cycle', fontsize=12)\n",
" ax.grid(True, alpha=0.3)\n",
"\n",
" # ── Bar chart: enthalpies per state ──────────────────────────────────────\n",
" ax2 = axes[1]\n",
" short_labels = ['1\\nSortie\\ncompresseur', '2\\nSortie\\ncondenseur',\n",
" '3\\nSortie\\nEXV', '4\\nSortie\\névaporateur']\n",
" bars = ax2.bar(short_labels, h[:4], color=colors, edgecolor='black', linewidth=0.7)\n",
" for bar, val in zip(bars, h[:4]):\n",
" ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 2,\n",
" f'{val:.1f}', ha='center', va='bottom', fontsize=9)\n",
" ax2.set_ylabel('Enthalpie (kJ/kg)', fontsize=11)\n",
" ax2.set_title('Enthalpies aux points du cycle', fontsize=12)\n",
" ax2.grid(True, axis='y', alpha=0.3)\n",
"\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
"\n",
"btn_run.on_click(run_simulation)\n",
"print('🎯 Cliquez ▶ Simuler dans la cellule précédente')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ae140c88-c298-4fe6-a13b-53d61640ddcf",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.12.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}