{ "cells": [ { "cell_type": "markdown", "id": "label-cell", "metadata": {}, "source": [ "(tutorial_humidair_label)=\n", "\n", "# Humid Air Connection\n", "\n", "This tutorial introduces the `HAConnection` class, which models moist (humid)\n", "air streams. It wraps CoolProp's HumidAir backend and exposes physical\n", "quantities for psychrometric calculations. It is an experimental feature for\n", "now and might be reworked in near future!\n", "\n", "| Parameter | Description |\n", "|-----------|-------------|\n", "| `m` | mass flow rate of **dry air** (system variable) |\n", "| `p` | absolute pressure |\n", "| `h` | dry-air-specific enthalpy (system variable) |\n", "| `T` | temperature |\n", "| `w` | humidity ratio - kg water per kg dry air |\n", "| `r` | relative humidity (0 ... 1) |\n", "| `mHA` | mass flow rate of the humid-air mixture (result) |\n", "| `mH2O`| mass flow rate of liquid/solid water that condenses out (result) |\n", "\n", "The fluid vector of an `HAConnection` always contains exactly two components:\n", "`air` and `water` (mass fractions). Setting `w` is a shortcut that derives\n", "the corresponding mass fractions automatically.\n", "\n", "## Physical scenario\n", "\n", "We simulate a simple air-cooling coil found in HVAC systems. Humid air enters\n", "at 15 °C, is cooled to 5 °C (below the dew point), and the heat is absorbed\n", "by an evaporating NH₃ refrigerant. If the outlet temperature is below the\n", "dew point, some moisture condenses and leaves as liquid water (`mH2O > 0`).\n", "\n", "The network contains:\n", "- **Air side** - two `HAConnection` objects around a `MovingBoundaryHeatExchanger`\n", "- **Refrigerant side** - two regular `Connection` objects carrying NH₃\n" ] }, { "cell_type": "code", "execution_count": null, "id": "imports", "metadata": {}, "outputs": [], "source": [ "from tespy.components import MovingBoundaryHeatExchanger, Sink, Source\n", "from tespy.connections import Connection, HAConnection\n", "from tespy.networks import Network" ] }, { "cell_type": "markdown", "id": "network-setup-text", "metadata": {}, "source": [ "## Network and component setup" ] }, { "cell_type": "code", "execution_count": null, "id": "network-setup", "metadata": {}, "outputs": [], "source": [ "nw = Network()\n", "nw.units.set_defaults(\n", " temperature=\"°C\", pressure=\"bar\", pressure_difference=\"bar\", heat=\"kW\"\n", ")\n", "\n", "# air-side components\n", "air_source = Source(\"air source\")\n", "air_sink = Sink(\"air sink\")\n", "\n", "# refrigerant-side components\n", "ref_source = Source(\"refrigerant source\")\n", "ref_sink = Sink(\"refrigerant sink\")\n", "\n", "hex = MovingBoundaryHeatExchanger(\"heat exchanger\")\n", "\n", "# air-side connections use HAConnection\n", "c1 = HAConnection(air_source, \"out1\", hex, \"in1\", label=\"c1\")\n", "c2 = HAConnection(hex, \"out1\", air_sink, \"in1\", label=\"c2\")\n", "\n", "# refrigerant-side connections use regular Connection\n", "a1 = Connection(ref_source, \"out1\", hex, \"in2\", label=\"a1\")\n", "a2 = Connection(hex, \"out2\", ref_sink, \"in1\", label=\"a2\")\n", "\n", "nw.add_conns(c1, c2, a1, a2)" ] }, { "cell_type": "markdown", "id": "case1-text", "metadata": {}, "source": [ "## Case 1: Specifying the humidity ratio `w`\n", "\n", "The most direct way to fix the inlet humidity is to set `w`, the ratio of\n", "water mass to dry-air mass. A value of `w = 0.004` kg/kg corresponds to\n", "roughly 40 % relative humidity at 15 °C. TESPy translates this into mass\n", "fractions internally, so there is no need to also set `fluid` or\n", "`fluid_balance`." ] }, { "cell_type": "code", "execution_count": null, "id": "case1-spec", "metadata": {}, "outputs": [], "source": [ "# air inlet: 15 °C, 1 bar, w = 0.004 kg_water/kg_dry_air\n", "c1.set_attr(p=1, T=15, w=0.004)\n", "# air outlet: cooled to 5 °C\n", "c2.set_attr(T=5)\n", "\n", "# NH3 refrigerant: 2 kg/s, 30 % quality at inlet; 5 K superheat above dew point at -20 °C outlet\n", "a1.set_attr(fluid={\"NH3\": 1}, m=2, x=0.3)\n", "a2.set_attr(td_dew=5, T_dew=-20)\n", "\n", "hex.set_attr(dp1=0, dp2=0)" ] }, { "cell_type": "code", "execution_count": null, "id": "case1-solve", "metadata": {}, "outputs": [], "source": [ "nw.solve(\"design\")\n", "nw.assert_convergence()" ] }, { "cell_type": "code", "execution_count": null, "id": "case1-results", "metadata": {}, "outputs": [], "source": [ "nw.results[\"HAConnection\"][[\"T\", \"w\", \"r\", \"m\", \"mHA\", \"mH2O\"]].style" ] }, { "cell_type": "markdown", "id": "case1-interpretation", "metadata": {}, "source": [ "### Interpreting the results\n", "\n", "- **`m`** – mass flow of *dry air* (the conserved quantity inside a humid-air\n", " loop; it does not change even if moisture condenses)\n", "- **`mHA`** – mass flow of the *humid-air mixture* \n", "- **`mH2O`** – mass flow of liquid water that condenses out\n", "- **`w`** – humidity ratio at each connection. It drops if moisture is removed\n", "- **`r`** – relative humidity" ] }, { "cell_type": "markdown", "id": "case2-text", "metadata": {}, "source": [ "## Case 2: Specifying relative humidity `r` with `fluid_balance`\n", "\n", "When the inlet state is described by relative humidity rather than humidity\n", "ratio, we use the `r` parameter. Because the fluid composition (the ratio of\n", "`air` to `water` mass fractions) is now an unknown that is determined by the\n", "`r` equation, we must tell TESPy to close the fluid balance automatically with\n", "`fluid_balance=True`.\n", "\n", "If no previous result is available `fluid0` should be specified as initial\n", "guess for the solver." ] }, { "cell_type": "code", "execution_count": null, "id": "case2-spec", "metadata": {}, "outputs": [], "source": [ "# remove the w specification and switch to relative humidity\n", "c1.set_attr(\n", " w=None,\n", " r=0.9,\n", " fluid_balance=True,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "case2-solve", "metadata": {}, "outputs": [], "source": [ "nw.solve(\"design\")\n", "nw.assert_convergence()" ] }, { "cell_type": "code", "execution_count": null, "id": "case2-results", "metadata": {}, "outputs": [], "source": [ "nw.results[\"HAConnection\"][[\"T\", \"w\", \"r\", \"m\", \"mHA\", \"mH2O\"]].style" ] }, { "cell_type": "markdown", "id": "case2-interpretation", "metadata": {}, "source": [ "At 90 % relative humidity and 15 °C the humidity ratio is noticeably higher\n", "than in Case 1. When the air is cooled to 5 °C 390 g/s of the water condenses.\n", "\n", "### When to use `fluid_balance`\n", "\n", "| Specification | `fluid_balance` needed? |\n", "|---|---|\n", "| `w` (humidity ratio) | No - composition is fully determined by `w` |\n", "| `r` (relative humidity) | **Yes** - `r` imposes one equation on the two unknown mass fractions; `fluid_balance` provides the second (sum = 1) |\n", "| `fluid={\"air\": ..., \"water\": ...}` directly | No - both fractions are fixed |\n" ] } ], "metadata": { "kernelspec": { "display_name": "tespy (3.14.0)", "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.0" } }, "nbformat": 4, "nbformat_minor": 5 }