Entropyk/bindings/python/control_example.ipynb
Sepehr fa480ed303 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.
2026-02-21 23:21:34 +01:00

202 lines
6.2 KiB
Plaintext

{
"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
}