{ "cells": [ { "cell_type": "markdown", "id": "735cc571", "metadata": {}, "source": [ "# Composing a model from submodels\n", "\n", "Every PyTES model is a fixed composition of submodels. `print_submodel_info` shows them: for\n", "each slot, the class, the variables it requires and provides, and its assumptions. To try a\n", "new submodel, build a `pytes.ComposedModel` from the submodels you want. The wiring is\n", "checked as for the built-in models, and the result is an ordinary PyBaMM model." ] }, { "cell_type": "code", "execution_count": 1, "id": "ed85d650", "metadata": { "execution": { "iopub.execute_input": "2026-10-23T22:00:53.642264Z", "iopub.status.busy": "2026-10-23T22:00:53.642141Z", "iopub.status.idle": "2026-10-23T22:00:54.546262Z", "shell.execute_reply": "2026-10-23T22:00:54.545215Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "fluid: AdvectedFluid (Heat transfer fluid)\n", " requires:\n", " Heat transfer fluid heat gain per unit length [W.m-1]\n", " Initial uniform temperature [K]\n", " provides:\n", " Heat transfer fluid specific enthalpy [J.kg-1]\n", " Inlet specific enthalpy [J.kg-1]\n", " Outlet specific enthalpy [J.kg-1]\n", " Mass flow rate [kg.s-1]\n", " Heat transfer fluid thermal energy [J]\n", " Heat transfer fluid temperature [K]\n", " Inlet temperature [K]\n", " Outlet temperature [K]\n", " Inlet-outlet temperature difference [K]\n", " Heat transfer fluid velocity [m.s-1]\n", " Volume flow rate [m3.s-1]\n", " Pumped volume [m3]\n", " parameters:\n", " Heat transfer fluid density [kg.m-3]\n", " Heat transfer fluid specific heat capacity [J.kg-1.K-1]\n", " Inlet temperature [K]\n", " Mass flow rate [kg.s-1]\n", " Number of tubes\n", " PCM melting temperature [K]\n", " Reference temperature [K]\n", " Tube length [m]\n", " Tube radius [m]\n", " assumptions:\n", " The fluid is incompressible, with constant density.\n", " Axial conduction and dispersion in the fluid are neglected.\n", " The flow is the same in every flow path.\n", "storage: EnthalpyStorage (Phase-change material storage)\n", " requires:\n", " Phase-change material boundary heat flux [W.m-2]\n", " Initial uniform temperature [K]\n", " provides:\n", " Phase-change material enthalpy [J.m-3]\n", " Phase-change material enthalpy rate of change [W.m-3]\n", " Phase-change material conduction heating rate [W.m-3]\n", " Phase-change material thermal energy [J]\n", " Liquid fraction\n", " Phase\n", " Average liquid fraction\n", " Phase-change material temperature [K]\n", " Phase-change material average temperature [K]\n", " Phase-change material cross-section average temperature [K]\n", " Phase-change material surface temperature [K]\n", " Phase-change material thermal conductivity [W.m-1.K-1]\n", " parameters:\n", " Fin pitch [m]\n", " Fin thickness [m]\n", " Metal conductivity [W.m-1.K-1]\n", " Metal density [kg.m-3]\n", " Metal specific heat capacity [J.kg-1.K-1]\n", " Number of tubes\n", " PCM latent heat [J.kg-1]\n", " PCM liquid phase conductivity [W.m-1.K-1]\n", " PCM liquid phase density [kg.m-3]\n", " PCM liquid phase specific heat capacity [J.kg-1.K-1]\n", " PCM melting range [K]\n", " PCM melting temperature [K]\n", " PCM smoothing width [K]\n", " PCM solid phase conductivity [W.m-1.K-1]\n", " PCM solid phase density [kg.m-3]\n", " PCM solid phase specific heat capacity [J.kg-1.K-1]\n", " Reference temperature [K]\n", " assumptions:\n", " Heat flows along the primary spatial variable only.\n", " Heat flows by conduction only (no convection in the liquid).\n", " The volume change on melting is neglected.\n", "coupling: WallCoupling (Wall coupling)\n", " requires:\n", " Phase-change material conduction heating rate [W.m-3]\n", " Heat transfer fluid specific enthalpy [J.kg-1]\n", " Heat transfer fluid temperature [K]\n", " Phase-change material surface temperature [K]\n", " Mass flow rate [kg.s-1]\n", " provides:\n", " Heat transfer fluid heat gain per unit length [W.m-1]\n", " Phase-change material boundary heat flux [W.m-2]\n", " Heat transfer coefficient [W.m-2.K-1]\n", " Surface Reynolds number\n", " Surface Prandtl number\n", " parameters:\n", " Heat transfer coefficient [W.m-2.K-1]\n", " Heat transfer fluid conductivity [W.m-1.K-1]\n", " Heat transfer fluid specific heat capacity [J.kg-1.K-1]\n", " Heat transfer fluid viscosity [Pa.s]\n", " Number of tubes\n", " Tube radius [m]\n", " assumptions:\n", " (none)\n", "device energy: DeviceEnergy (Device energy)\n", " requires:\n", " Mass flow rate [kg.s-1]\n", " Outlet specific enthalpy [J.kg-1]\n", " Inlet specific enthalpy [J.kg-1]\n", " Phase-change material thermal energy [J]\n", " Heat transfer fluid thermal energy [J]\n", " Phase-change material average temperature [K]\n", " Phase-change material cross-section average temperature [K]\n", " provides:\n", " Power [W]\n", " Discharged energy [J]\n", " Charged energy [J]\n", " Discharged throughput [J]\n", " Charged throughput [J]\n", " Storage thermal energy [J]\n", " Total thermal energy [J]\n", " Heat loss power [W]\n", " Storage average temperature [K]\n", " Storage cross-section average temperature [K]\n", " Initial uniform temperature [K]\n", " parameters:\n", " Initial temperature [K]\n", " assumptions:\n", " There are no heat losses to the surroundings.\n", "geometry parameters:\n", " Storage depth [m]\n", " Tube length [m]\n", " Tube radius [m]\n" ] } ], "source": [ "import numpy as np\n", "import pybamm\n", "\n", "import pytes\n", "\n", "base = pytes.SharpFrontModel()\n", "base.print_submodel_info()" ] }, { "cell_type": "markdown", "id": "27d6f949", "metadata": {}, "source": [ "Here the coupling's surface flux law is replaced by a nonlinear one. The submodels of the\n", "base model are reused, not modified, and `defaults_from=base` takes its geometry, mesh, solver\n", "and parameter values." ] }, { "cell_type": "code", "execution_count": 2, "id": "ca1eae53", "metadata": { "execution": { "iopub.execute_input": "2026-10-23T22:00:54.547913Z", "iopub.status.busy": "2026-10-23T22:00:54.547625Z", "iopub.status.idle": "2026-10-23T22:00:54.557150Z", "shell.execute_reply": "2026-10-23T22:00:54.556020Z" } }, "outputs": [], "source": [ "class CubicFlux(pytes.submodels.SurfaceFluxLaw):\n", " \"\"\"Heat flux c (T_f - T_s)**3, positive into the storage\"\"\"\n", "\n", " def heat_flux(self, fluid, fluid_state, T_s):\n", " return (\n", " pybamm.Parameter(\"Cubic flux coefficient [W.m-2.K-3]\")\n", " * (fluid_state.T - T_s) ** 3\n", " )\n", "\n", "\n", "storage = base.submodels[\"storage\"]\n", "coupling = pytes.submodels.WallCoupling(\n", " base.param,\n", " base.param.fluid,\n", " storage.label,\n", " storage.storage_geometry,\n", " flux_law=CubicFlux(),\n", ")\n", "cubic = pytes.ComposedModel(\n", " {**base.submodels, \"coupling\": coupling},\n", " base.param,\n", " name=\"Cubic flux\",\n", " defaults_from=base,\n", ")\n", "parameter_values = cubic.default_parameter_values\n", "parameter_values[\"Cubic flux coefficient [W.m-2.K-3]\"] = 40" ] }, { "cell_type": "markdown", "id": "d0650cbd", "metadata": {}, "source": [ "Energy is still conserved exactly, whatever the flux law, because the coupling takes the\n", "fluid's heat from the storage's own energy balance." ] }, { "cell_type": "code", "execution_count": 3, "id": "e50dd849", "metadata": { "execution": { "iopub.execute_input": "2026-10-23T22:00:54.559077Z", "iopub.status.busy": "2026-10-23T22:00:54.558856Z", "iopub.status.idle": "2026-10-23T22:00:56.296607Z", "shell.execute_reply": "2026-10-23T22:00:56.295872Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Sharp front model - max conservation error [J]: 1.4901161193847656e-08\n", "Cubic flux - max conservation error [J]: 3.91155481338501e-08\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "7c54f839abc5412ab1965632f6b1dae1", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(FloatSlider(value=0.0, description='t', max=1.0, step=0.01), Output()), _dom_classes=('w…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t = np.linspace(0, 3600, 121)\n", "solutions = [\n", " pybamm.Simulation(base).solve([0, 3600], t_interp=t),\n", " pybamm.Simulation(cubic, parameter_values=parameter_values).solve(\n", " [0, 3600], t_interp=t\n", " ),\n", "]\n", "for solution in solutions:\n", " E = solution[\"Total thermal energy [J]\"].entries\n", " discharged = solution[\"Discharged energy [J]\"].entries\n", " print(\n", " solution.all_models[0].name,\n", " \"- max conservation error [J]:\",\n", " np.max(np.abs(E - E[0] + discharged)),\n", " )\n", "pybamm.dynamic_plot(solutions, [\"Outlet temperature [degC]\", \"Power [kW]\"])" ] } ], "metadata": { "kernelspec": { "display_name": "pytes (3.14.4)", "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.14.4" } }, "nbformat": 4, "nbformat_minor": 5 }