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