Model, optimize, and analyze complex energy systems with a powerful Python framework designed for flexibility and performance.
@@ -25,36 +22,44 @@ hide:
## :material-map-marker-path: Quick Navigation
-
-
- 🚀 Getting Started
- New to FlixOpt? Start here with installation and your first model
-
-
-
- 💡 Examples Gallery
- Explore real-world examples from simple to complex systems
-
-
-
- 📚 API Reference
- Detailed documentation of all classes, methods, and parameters
-
-
-
- 📖 Recipes
- Common patterns and best practices for modeling energy systems
-
-
-
- ∫ Mathematical Notation
- Understand the mathematical formulations behind the framework
-
-
-
- 🛣️ Roadmap
- See what's coming next and contribute to the future of FlixOpt
-
+
+
+- :rocket: **[Getting Started](home/installation.md)**
+
+ ---
+
+ New to FlixOpt? Start here with installation and your first model
+
+- :bulb: **[Examples Gallery](notebooks/)**
+
+ ---
+
+ Explore real-world examples from simple to complex systems
+
+- :books: **[API Reference](api-reference/)**
+
+ ---
+
+ Detailed documentation of all classes, methods and parameters
+
+- :book: **[Recipes](user-guide/recipes/)**
+
+ ---
+
+ Common patterns and best practices for modeling energy systems
+
+- :material-math-integral: **[Mathematical Notation](user-guide/mathematical-notation/)**
+
+ ---
+
+ Understand the mathematical formulations behind the framework
+
+- :material-road: **[Roadmap](roadmap.md)**
+
+ ---
+
+ See what's coming next and contribute to the future of FlixOpt
+
## 🏗️ Framework Architecture
@@ -77,43 +82,31 @@ hide:
## :material-account-group: Community & Support
-
-
-
-
-:fontawesome-brands-github:{ .feature-icon }
+
-### GitHub
+- :fontawesome-brands-github: **GitHub**
-Report issues, request features, and contribute to the codebase
+ ---
-[Visit Repository →](https://github.com/flixOpt/flixopt){target="_blank" rel="noopener noreferrer"}
+ Report issues, request features, and contribute to the codebase
-
-
-
+ [Visit Repository →](https://github.com/flixOpt/flixopt){target="_blank" rel="noopener noreferrer"}
-:material-forum:{ .feature-icon }
+- :material-forum: **Discussions**
-### Discussions
+ ---
-Ask questions and share your projects with the community
+ Ask questions and share your projects with the community
-[Join Discussion →](https://github.com/flixOpt/flixopt/discussions){target="_blank" rel="noopener noreferrer"}
-
-
+ [Join Discussion →](https://github.com/flixOpt/flixopt/discussions){target="_blank" rel="noopener noreferrer"}
-
+- :material-book-open-page-variant: **Contributing**
-:material-book-open-page-variant:{ .feature-icon }
+ ---
-### Contributing
+ Help improve FlixOpt by contributing code, docs, or examples
-Help improve FlixOpt by contributing code, docs, or examples
-
-[Learn How →](contribute/){target="_blank" rel="noopener noreferrer"}
-
-
+ [Learn How →](contribute/){target="_blank" rel="noopener noreferrer"}
@@ -132,7 +125,7 @@ Help improve FlixOpt by contributing code, docs, or examples
Ready to optimize your energy system?
- ▶️ Start Building
+ ▶️ Start Building
diff --git a/docs/javascripts/plotly-instant.js b/docs/javascripts/plotly-instant.js
new file mode 100644
index 000000000..c6dd2766c
--- /dev/null
+++ b/docs/javascripts/plotly-instant.js
@@ -0,0 +1,30 @@
+// Re-initialize Plotly charts on MkDocs Material instant navigation
+document.addEventListener('DOMContentLoaded', function() {
+ initPlotlyCharts();
+});
+
+// Hook into Material's instant navigation
+if (typeof document$ !== 'undefined') {
+ document$.subscribe(function() {
+ initPlotlyCharts();
+ });
+}
+
+function initPlotlyCharts() {
+ const charts = document.querySelectorAll('div.mkdocs-plotly');
+ charts.forEach(function(chart) {
+ // Skip if already initialized (has children)
+ if (chart.children.length > 0) return;
+
+ try {
+ const plotData = JSON.parse(chart.textContent);
+ chart.textContent = '';
+ const data = plotData.data || [];
+ const layout = plotData.layout || {};
+ const config = plotData.config || {responsive: true};
+ Plotly.newPlot(chart, data, layout, config);
+ } catch (e) {
+ console.error('Failed to initialize Plotly chart:', e);
+ }
+ });
+}
diff --git a/docs/notebooks/01-quickstart.ipynb b/docs/notebooks/01-quickstart.ipynb
new file mode 100644
index 000000000..ba4becd0c
--- /dev/null
+++ b/docs/notebooks/01-quickstart.ipynb
@@ -0,0 +1,279 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": "# Quickstart\n\nHeat a small workshop with a gas boiler - the minimal working example.\n\nThis notebook introduces the **core concepts** of flixopt:\n\n- **FlowSystem**: The container for your energy system model\n- **Bus**: Balance nodes where energy flows meet\n- **Effect**: Quantities to track and optimize (costs, emissions)\n- **Components**: Equipment like boilers, sources, and sinks\n- **Flow**: Connections between components and buses"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "import xarray as xr\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "## Define the Time Horizon\n",
+ "\n",
+ "Every optimization needs a time horizon. Here we model a simple 4-hour period:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "timesteps = pd.date_range('2024-01-15 08:00', periods=4, freq='h')\n",
+ "print(f'Optimizing from {timesteps[0]} to {timesteps[-1]}')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "## Define the Heat Demand\n",
+ "\n",
+ "The workshop has varying heat demand throughout the morning:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Heat demand in kW for each hour - using xarray\n",
+ "heat_demand = xr.DataArray(\n",
+ " [30, 50, 45, 25],\n",
+ " dims=['time'],\n",
+ " coords={'time': timesteps},\n",
+ " name='Heat Demand [kW]',\n",
+ ")\n",
+ "\n",
+ "# Visualize the demand with plotly\n",
+ "fig = px.bar(x=heat_demand.time.values, y=heat_demand.values, labels={'x': 'Time', 'y': 'Heat Demand [kW]'})\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7",
+ "metadata": {},
+ "source": [
+ "## Build the Energy System Model\n",
+ "\n",
+ "Now we create the FlowSystem and add all components:\n",
+ "\n",
+ "```\n",
+ " Gas Supply ──► [Gas Bus] ──► Boiler ──► [Heat Bus] ──► Workshop\n",
+ " € η=90% Demand\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Create the FlowSystem container\n",
+ "flow_system = fx.FlowSystem(timesteps)\n",
+ "\n",
+ "flow_system.add_elements(\n",
+ " # === Buses: Balance nodes for energy carriers ===\n",
+ " fx.Bus('Gas'), # Natural gas network connection\n",
+ " fx.Bus('Heat'), # Heat distribution within workshop\n",
+ " # === Effect: What we want to minimize ===\n",
+ " fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n",
+ " # === Gas Supply: Unlimited gas at 0.08 €/kWh ===\n",
+ " fx.Source(\n",
+ " 'GasGrid',\n",
+ " outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.08)],\n",
+ " ),\n",
+ " # === Boiler: Converts gas to heat at 90% efficiency ===\n",
+ " fx.linear_converters.Boiler(\n",
+ " 'Boiler',\n",
+ " thermal_efficiency=0.9,\n",
+ " thermal_flow=fx.Flow('Heat', bus='Heat', size=100), # 100 kW capacity\n",
+ " fuel_flow=fx.Flow('Gas', bus='Gas'),\n",
+ " ),\n",
+ " # === Workshop: Heat demand that must be met ===\n",
+ " fx.Sink(\n",
+ " 'Workshop',\n",
+ " inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand.values)],\n",
+ " ),\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "## Run the Optimization\n",
+ "\n",
+ "Now we solve the model using the HiGHS solver (open-source, included with flixopt):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.optimize(fx.solvers.HighsSolver());"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11",
+ "metadata": {},
+ "source": [
+ "## Analyze Results\n",
+ "\n",
+ "### Heat Balance\n",
+ "\n",
+ "The `statistics.plot.balance()` method shows how each bus is balanced:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "12",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.balance('Heat')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "13",
+ "metadata": {},
+ "source": [
+ "### Total Costs\n",
+ "\n",
+ "Access the optimized objective value:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "14",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "total_costs = flow_system.solution['costs'].item()\n",
+ "total_heat = float(heat_demand.sum())\n",
+ "gas_consumed = total_heat / 0.9 # Account for boiler efficiency\n",
+ "\n",
+ "print(f'Total heat demand: {total_heat:.1f} kWh')\n",
+ "print(f'Gas consumed: {gas_consumed:.1f} kWh')\n",
+ "print(f'Total costs: {total_costs:.2f} €')\n",
+ "print(f'Average cost: {total_costs / total_heat:.3f} €/kWh_heat')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15",
+ "metadata": {},
+ "source": [
+ "### Flow Rates Over Time\n",
+ "\n",
+ "Visualize all flow rates using the built-in plotting accessor:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "16",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Plot all flow rates\n",
+ "flow_system.statistics.plot.flows()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17",
+ "metadata": {},
+ "source": [
+ "### Energy Flow Sankey\n",
+ "\n",
+ "A Sankey diagram visualizes the total energy flows through the system:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.sankey.flows()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19",
+ "metadata": {},
+ "source": [
+ "## Summary\n",
+ "\n",
+ "In this quickstart, you learned the **basic workflow**:\n",
+ "\n",
+ "1. **Create** a `FlowSystem` with timesteps\n",
+ "2. **Add** buses, effects, and components\n",
+ "3. **Optimize** with `flow_system.optimize(solver)`\n",
+ "4. **Analyze** results via `flow_system.statistics`\n",
+ "\n",
+ "### Next Steps\n",
+ "\n",
+ "- **[02-heat-system](02-heat-system.ipynb)**: Add thermal storage to shift loads\n",
+ "- **[03-investment-optimization](03-investment-optimization.ipynb)**: Optimize equipment sizing"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/02-heat-system.ipynb b/docs/notebooks/02-heat-system.ipynb
new file mode 100644
index 000000000..f1392c72f
--- /dev/null
+++ b/docs/notebooks/02-heat-system.ipynb
@@ -0,0 +1,361 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": "# Heat System\n\nDistrict heating with thermal storage and time-varying prices.\n\nThis notebook introduces:\n\n- **Storage**: Thermal buffer tanks with charging/discharging\n- **Time series data**: Using real demand profiles\n- **Multiple components**: Combining boiler, storage, and loads\n- **Result visualization**: Heatmaps, balance plots, and charge states"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "import xarray as xr\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "## Define Time Horizon and Demand\n",
+ "\n",
+ "We model one week with hourly resolution. The office has typical weekday patterns:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# One week, hourly resolution\n",
+ "timesteps = pd.date_range('2024-01-15', periods=168, freq='h')\n",
+ "\n",
+ "# Create realistic office heat demand pattern\n",
+ "hours = np.arange(168)\n",
+ "hour_of_day = hours % 24\n",
+ "day_of_week = (hours // 24) % 7\n",
+ "\n",
+ "# Base demand pattern (kW)\n",
+ "base_demand = np.where(\n",
+ " (hour_of_day >= 7) & (hour_of_day <= 18), # Office hours\n",
+ " 80, # Daytime\n",
+ " 30, # Night setback\n",
+ ")\n",
+ "\n",
+ "# Reduce on weekends (days 5, 6)\n",
+ "weekend_factor = np.where(day_of_week >= 5, 0.5, 1.0)\n",
+ "heat_demand = base_demand * weekend_factor\n",
+ "\n",
+ "# Add some random variation\n",
+ "np.random.seed(42)\n",
+ "heat_demand = heat_demand + np.random.normal(0, 5, len(heat_demand))\n",
+ "heat_demand = np.clip(heat_demand, 20, 100)\n",
+ "\n",
+ "print(f'Time range: {timesteps[0]} to {timesteps[-1]}')\n",
+ "print(f'Peak demand: {heat_demand.max():.1f} kW')\n",
+ "print(f'Total demand: {heat_demand.sum():.0f} kWh')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Visualize the demand pattern with plotly\n",
+ "demand_series = xr.DataArray(heat_demand, dims=['time'], coords={'time': timesteps}, name='Heat Demand [kW]')\n",
+ "fig = px.line(\n",
+ " x=demand_series.time.values,\n",
+ " y=demand_series.values,\n",
+ " title='Office Heat Demand Profile',\n",
+ " labels={'x': 'Time', 'y': 'kW'},\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6",
+ "metadata": {},
+ "source": [
+ "## Define Gas Prices\n",
+ "\n",
+ "Gas prices vary with time-of-use tariffs:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Time-of-use gas prices (€/kWh)\n",
+ "gas_price = np.where(\n",
+ " (hour_of_day >= 6) & (hour_of_day <= 22),\n",
+ " 0.08, # Peak: 6am-10pm\n",
+ " 0.05, # Off-peak: 10pm-6am\n",
+ ")\n",
+ "\n",
+ "fig = px.line(x=timesteps, y=gas_price, title='Gas Price [€/kWh]', labels={'x': 'Time', 'y': '€/kWh'})\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8",
+ "metadata": {},
+ "source": [
+ "## Build the Energy System\n",
+ "\n",
+ "The system includes:\n",
+ "- Gas boiler (150 kW thermal capacity)\n",
+ "- Thermal storage tank (500 kWh capacity)\n",
+ "- Office building heat demand\n",
+ "\n",
+ "```\n",
+ "Gas Grid ──► [Gas] ──► Boiler ──► [Heat] ◄──► Storage\n",
+ " │\n",
+ " ▼\n",
+ " Office\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9",
+ "metadata": {},
+ "outputs": [],
+ "source": "flow_system = fx.FlowSystem(timesteps)\nflow_system.add_carriers(\n fx.Carrier('gas', '#3498db', 'kW'),\n fx.Carrier('heat', '#e74c3c', 'kW'),\n)\nflow_system.add_elements(\n # === Buses ===\n fx.Bus('Gas', carrier='gas'),\n fx.Bus('Heat', carrier='heat'),\n # === Effect ===\n fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),\n # === Gas Supply with time-varying price ===\n fx.Source(\n 'GasGrid',\n outputs=[fx.Flow('Gas', bus='Gas', size=500, effects_per_flow_hour=gas_price)],\n ),\n # === Gas Boiler: 150 kW, 92% efficiency ===\n fx.linear_converters.Boiler(\n 'Boiler',\n thermal_efficiency=0.92,\n thermal_flow=fx.Flow('Heat', bus='Heat', size=150),\n fuel_flow=fx.Flow('Gas', bus='Gas'),\n ),\n # === Thermal Storage: 500 kWh tank ===\n fx.Storage(\n 'ThermalStorage',\n capacity_in_flow_hours=500, # 500 kWh capacity\n initial_charge_state=250, # Start half-full\n minimal_final_charge_state=200, # End with at least 200 kWh\n eta_charge=0.98, # 98% charging efficiency\n eta_discharge=0.98, # 98% discharging efficiency\n relative_loss_per_hour=0.005, # 0.5% heat loss per hour\n charging=fx.Flow('Charge', bus='Heat', size=100), # Max 100 kW charging\n discharging=fx.Flow('Discharge', bus='Heat', size=100), # Max 100 kW discharging\n ),\n # === Office Heat Demand ===\n fx.Sink(\n 'Office',\n inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand)],\n ),\n)"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "10",
+ "metadata": {},
+ "source": [
+ "## Run Optimization"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "11",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.optimize(fx.solvers.HighsSolver(mip_gap=0.01));"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12",
+ "metadata": {},
+ "source": [
+ "## Analyze Results\n",
+ "\n",
+ "### Heat Balance\n",
+ "\n",
+ "See how the boiler and storage work together to meet demand:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "13",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.balance('Heat')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "14",
+ "metadata": {},
+ "source": [
+ "### Storage Charge State\n",
+ "\n",
+ "Track how the storage level varies over time:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "15",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.balance('ThermalStorage')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "16",
+ "metadata": {},
+ "source": [
+ "### Heatmap Visualization\n",
+ "\n",
+ "Heatmaps show patterns across hours and days:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "17",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.heatmap('Boiler(Heat)')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.heatmap('ThermalStorage')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19",
+ "metadata": {},
+ "source": [
+ "### Cost Analysis"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "20",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "total_costs = flow_system.solution['costs'].item()\n",
+ "total_heat = heat_demand.sum()\n",
+ "\n",
+ "print(f'Total operating costs: {total_costs:.2f} €')\n",
+ "print(f'Total heat delivered: {total_heat:.0f} kWh')\n",
+ "print(f'Average cost: {total_costs / total_heat * 100:.2f} ct/kWh')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "21",
+ "metadata": {},
+ "source": [
+ "### Flow Rates and Charge States\n",
+ "\n",
+ "Visualize all flow rates and storage charge states:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "22",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Plot all flow rates\n",
+ "flow_system.statistics.plot.flows()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "23",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Plot storage charge states\n",
+ "flow_system.statistics.plot.storage('ThermalStorage')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "24",
+ "metadata": {},
+ "source": [
+ "### Energy Flow Sankey\n",
+ "\n",
+ "A Sankey diagram visualizes the total energy flows through the system:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "25",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.sankey.flows()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "26",
+ "metadata": {},
+ "source": [
+ "## Key Insights\n",
+ "\n",
+ "The optimization reveals how storage enables **load shifting**:\n",
+ "\n",
+ "1. **Charge during off-peak**: When gas is cheap (night), the boiler runs at higher output to charge the storage\n",
+ "2. **Discharge during peak**: During expensive periods, storage supplements the boiler\n",
+ "3. **Weekend patterns**: Lower demand allows more storage cycling\n",
+ "\n",
+ "## Summary\n",
+ "\n",
+ "You learned how to:\n",
+ "\n",
+ "- Add **Storage** components with efficiency and losses\n",
+ "- Use **time-varying prices** in effects\n",
+ "- Visualize results with **heatmaps** and **balance plots**\n",
+ "- Access raw data via **statistics.flow_rates** and **statistics.charge_states**\n",
+ "\n",
+ "### Next Steps\n",
+ "\n",
+ "- **[03-investment-optimization](03-investment-optimization.ipynb)**: Optimize storage size\n",
+ "- **[04-operational-constraints](04-operational-constraints.ipynb)**: Add startup costs and minimum run times"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/03-investment-optimization.ipynb b/docs/notebooks/03-investment-optimization.ipynb
new file mode 100644
index 000000000..ff62fe037
--- /dev/null
+++ b/docs/notebooks/03-investment-optimization.ipynb
@@ -0,0 +1,404 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": "# Sizing\n\nSize a solar heating system - let the optimizer decide equipment sizes.\n\nThis notebook introduces:\n\n- **InvestParameters**: Define investment decisions with size bounds and costs\n- **Investment costs**: Fixed costs and size-dependent costs\n- **Optimal sizing**: Let the optimizer find the best equipment sizes\n- **Trade-off analysis**: Balance investment vs. operating costs"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "import xarray as xr\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "## System Description\n",
+ "\n",
+ "The swimming pool heating system:\n",
+ "\n",
+ "- **Solar collectors**: Convert solar radiation to heat (size to be optimized)\n",
+ "- **Gas boiler**: Backup heating when solar is insufficient (existing, 200 kW)\n",
+ "- **Buffer tank**: Store excess solar heat (size to be optimized)\n",
+ "- **Pool**: Constant heat demand of 150 kW during operating hours\n",
+ "\n",
+ "```\n",
+ " ☀️ Solar ──► [Heat] ◄── Boiler ◄── [Gas]\n",
+ " │\n",
+ " ▼\n",
+ " Buffer Tank\n",
+ " │\n",
+ " ▼\n",
+ " Pool 🏊\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4",
+ "metadata": {},
+ "source": [
+ "## Define Time Horizon and Profiles\n",
+ "\n",
+ "We model one representative summer week:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# One week in summer, hourly\n",
+ "timesteps = pd.date_range('2024-07-15', periods=168, freq='h')\n",
+ "hours = np.arange(168)\n",
+ "hour_of_day = hours % 24\n",
+ "\n",
+ "# Solar radiation profile (kW/m² equivalent, simplified)\n",
+ "# Peak around noon, zero at night\n",
+ "solar_profile = np.maximum(0, np.sin((hour_of_day - 6) * np.pi / 12)) * 0.8\n",
+ "solar_profile = np.where((hour_of_day >= 6) & (hour_of_day <= 20), solar_profile, 0)\n",
+ "\n",
+ "# Add some cloud variation\n",
+ "np.random.seed(42)\n",
+ "cloud_factor = np.random.uniform(0.6, 1.0, len(timesteps))\n",
+ "solar_profile = solar_profile * cloud_factor\n",
+ "\n",
+ "# Pool operates 8am-10pm, constant demand when open\n",
+ "pool_demand = np.where((hour_of_day >= 8) & (hour_of_day <= 22), 150, 50) # kW\n",
+ "\n",
+ "print(f'Peak solar: {solar_profile.max():.2f} kW/kW_installed')\n",
+ "print(f'Pool demand: {pool_demand.max():.0f} kW (open), {pool_demand.min():.0f} kW (closed)')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Visualize profiles with plotly - using xarray and faceting\n",
+ "profiles = xr.Dataset(\n",
+ " {\n",
+ " 'Solar Profile [kW/kW]': xr.DataArray(solar_profile, dims=['time'], coords={'time': timesteps}),\n",
+ " 'Pool Demand [kW]': xr.DataArray(pool_demand, dims=['time'], coords={'time': timesteps}),\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "# Convert to long format for faceting\n",
+ "df = profiles.to_dataframe().reset_index().melt(id_vars='time', var_name='variable', value_name='value')\n",
+ "fig = px.line(df, x='time', y='value', facet_col='variable', height=300)\n",
+ "fig.update_yaxes(matches=None, showticklabels=True)\n",
+ "fig.for_each_annotation(lambda a: a.update(text=a.text.split('=')[-1]))\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7",
+ "metadata": {},
+ "source": [
+ "## Define Costs\n",
+ "\n",
+ "Investment costs are **annualized** (€/year) to compare with operating costs:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Cost parameters\n",
+ "GAS_PRICE = 0.12 # €/kWh - high gas price makes solar attractive\n",
+ "\n",
+ "# Solar collectors: 400 €/kW installed, 20-year lifetime → ~25 €/kW/year annualized\n",
+ "# (simplified, real calculation would include interest rate)\n",
+ "SOLAR_COST_PER_KW = 20 # €/kW/year\n",
+ "\n",
+ "# Buffer tank: 50 €/kWh capacity, 30-year lifetime → ~2 €/kWh/year\n",
+ "TANK_COST_PER_KWH = 1.5 # €/kWh/year\n",
+ "\n",
+ "# Scale factor: We model 1 week, but costs are annual\n",
+ "# So we scale investment costs to weekly equivalent\n",
+ "WEEKS_PER_YEAR = 52\n",
+ "SOLAR_COST_WEEKLY = SOLAR_COST_PER_KW / WEEKS_PER_YEAR\n",
+ "TANK_COST_WEEKLY = TANK_COST_PER_KWH / WEEKS_PER_YEAR\n",
+ "\n",
+ "print(f'Solar cost: {SOLAR_COST_WEEKLY:.3f} €/kW/week')\n",
+ "print(f'Tank cost: {TANK_COST_WEEKLY:.4f} €/kWh/week')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "## Build the System with Investment Options\n",
+ "\n",
+ "Use `InvestParameters` to define which sizes should be optimized:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10",
+ "metadata": {},
+ "outputs": [],
+ "source": "flow_system = fx.FlowSystem(timesteps)\nflow_system.add_carriers(\n fx.Carrier('gas', '#3498db', 'kW'),\n fx.Carrier('heat', '#e74c3c', 'kW'),\n)\nflow_system.add_elements(\n # === Buses ===\n fx.Bus('Heat', carrier='heat'),\n fx.Bus('Gas', carrier='gas'),\n # === Effects ===\n fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n # === Gas Supply ===\n fx.Source(\n 'GasGrid',\n outputs=[fx.Flow('Gas', bus='Gas', size=500, effects_per_flow_hour=GAS_PRICE)],\n ),\n # === Gas Boiler (existing, fixed size) ===\n fx.linear_converters.Boiler(\n 'GasBoiler',\n thermal_efficiency=0.92,\n thermal_flow=fx.Flow('Heat', bus='Heat', size=200), # 200 kW existing\n fuel_flow=fx.Flow('Gas', bus='Gas'),\n ),\n # === Solar Collectors (size to be optimized) ===\n fx.Source(\n 'SolarCollectors',\n outputs=[\n fx.Flow(\n 'Heat',\n bus='Heat',\n # Investment optimization: find optimal size between 0-500 kW\n size=fx.InvestParameters(\n minimum_size=0,\n maximum_size=500,\n effects_of_investment_per_size={'costs': SOLAR_COST_WEEKLY},\n ),\n # Solar output depends on radiation profile\n fixed_relative_profile=solar_profile,\n )\n ],\n ),\n # === Buffer Tank (size to be optimized) ===\n fx.Storage(\n 'BufferTank',\n # Investment optimization: find optimal capacity between 0-2000 kWh\n capacity_in_flow_hours=fx.InvestParameters(\n minimum_size=0,\n maximum_size=2000,\n effects_of_investment_per_size={'costs': TANK_COST_WEEKLY},\n ),\n initial_charge_state=0,\n eta_charge=0.95,\n eta_discharge=0.95,\n relative_loss_per_hour=0.01, # 1% loss per hour\n charging=fx.Flow('Charge', bus='Heat', size=200),\n discharging=fx.Flow('Discharge', bus='Heat', size=200),\n ),\n # === Pool Heat Demand ===\n fx.Sink(\n 'Pool',\n inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=pool_demand)],\n ),\n)"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11",
+ "metadata": {},
+ "source": [
+ "## Run Optimization"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "12",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.optimize(fx.solvers.HighsSolver(mip_gap=0.01));"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "13",
+ "metadata": {},
+ "source": [
+ "## Analyze Investment Decisions\n",
+ "\n",
+ "### Optimal Sizes"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "14",
+ "metadata": {},
+ "outputs": [],
+ "source": "solar_size = flow_system.statistics.sizes['SolarCollectors(Heat)'].item()\ntank_size = flow_system.statistics.sizes['BufferTank'].item()\n\nprint('=== Optimal Investment Decisions ===')\nprint(f'Solar collectors: {solar_size:.1f} kW')\nprint(f'Buffer tank: {tank_size:.1f} kWh')\nprint(f'Tank-to-solar ratio: {tank_size / solar_size:.1f} kWh/kW' if solar_size > 0 else 'N/A')"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15",
+ "metadata": {},
+ "source": [
+ "### Visualize Sizes"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "16",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.sizes()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17",
+ "metadata": {},
+ "source": [
+ "### Cost Breakdown"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "total_costs = flow_system.solution['costs'].item()\n",
+ "\n",
+ "# Calculate cost components\n",
+ "solar_invest = solar_size * SOLAR_COST_WEEKLY\n",
+ "tank_invest = tank_size * TANK_COST_WEEKLY\n",
+ "gas_costs = total_costs - solar_invest - tank_invest\n",
+ "\n",
+ "print('=== Weekly Cost Breakdown ===')\n",
+ "print(f'Solar investment: {solar_invest:.2f} € ({solar_invest / total_costs * 100:.1f}%)')\n",
+ "print(f'Tank investment: {tank_invest:.2f} € ({tank_invest / total_costs * 100:.1f}%)')\n",
+ "print(f'Gas operating: {gas_costs:.2f} € ({gas_costs / total_costs * 100:.1f}%)')\n",
+ "print('─────────────────────────────')\n",
+ "print(f'Total: {total_costs:.2f} €')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19",
+ "metadata": {},
+ "source": [
+ "### System Operation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "20",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.balance('Heat')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "21",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.heatmap('SolarCollectors(Heat)')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "22",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.balance('BufferTank')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23",
+ "metadata": {},
+ "source": [
+ "## Compare: What if No Solar?\n",
+ "\n",
+ "Let's see how much the solar system saves:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Gas-only scenario\n",
+ "total_demand = pool_demand.sum()\n",
+ "gas_only_cost = total_demand / 0.92 * GAS_PRICE # All heat from gas boiler\n",
+ "\n",
+ "savings = gas_only_cost - total_costs\n",
+ "savings_pct = savings / gas_only_cost * 100\n",
+ "\n",
+ "print('=== Comparison with Gas-Only ===')\n",
+ "print(f'Gas-only cost: {gas_only_cost:.2f} €/week')\n",
+ "print(f'With solar: {total_costs:.2f} €/week')\n",
+ "print(f'Savings: {savings:.2f} €/week ({savings_pct:.1f}%)')\n",
+ "print(f'Annual savings: {savings * 52:.0f} €/year')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25",
+ "metadata": {},
+ "source": [
+ "### Energy Flow Sankey\n",
+ "\n",
+ "A Sankey diagram visualizes the total energy flows through the system:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "26",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.sankey.flows()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "27",
+ "metadata": {},
+ "source": [
+ "## Key Concepts\n",
+ "\n",
+ "### InvestParameters Options\n",
+ "\n",
+ "```python\n",
+ "fx.InvestParameters(\n",
+ " minimum_size=0, # Lower bound (can be 0 for optional)\n",
+ " maximum_size=500, # Upper bound\n",
+ " fixed_size=100, # Or: fixed size (binary decision)\n",
+ " mandatory=True, # Force investment to happen\n",
+ " effects_of_investment={'costs': 1000}, # Fixed cost if invested\n",
+ " effects_of_investment_per_size={'costs': 25}, # Cost per unit size\n",
+ ")\n",
+ "```\n",
+ "\n",
+ "### Where to Use InvestParameters\n",
+ "\n",
+ "- **Flow.size**: Optimize converter/source/sink capacity\n",
+ "- **Storage.capacity_in_flow_hours**: Optimize storage capacity\n",
+ "\n",
+ "## Summary\n",
+ "\n",
+ "You learned how to:\n",
+ "\n",
+ "- Define **investment decisions** with `InvestParameters`\n",
+ "- Set **size bounds** (minimum/maximum)\n",
+ "- Add **investment costs** (per-size and fixed)\n",
+ "- Access **optimal sizes** via `statistics.sizes`\n",
+ "- Visualize sizes with `statistics.plot.sizes()`\n",
+ "\n",
+ "### Next Steps\n",
+ "\n",
+ "- **[04-operational-constraints](04-operational-constraints.ipynb)**: Add startup costs and minimum run times\n",
+ "- **[05-multi-carrier-system](05-multi-carrier-system.ipynb)**: Model combined heat and power"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/04-operational-constraints.ipynb b/docs/notebooks/04-operational-constraints.ipynb
new file mode 100644
index 000000000..9090b172b
--- /dev/null
+++ b/docs/notebooks/04-operational-constraints.ipynb
@@ -0,0 +1,443 @@
+{
+ "cells": [
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "# Constraints\n",
+ "\n",
+ "Industrial boiler with startup costs, minimum uptime, and load constraints.\n",
+ "\n",
+ "This notebook introduces:\n",
+ "\n",
+ "- **StatusParameters**: Model on/off decisions with constraints\n",
+ "- **Startup costs**: Penalties for turning equipment on\n",
+ "- **Minimum uptime/downtime**: Prevent rapid cycling\n",
+ "- **Minimum load**: Equipment can't run below a certain output"
+ ],
+ "id": "217ee38bd32426e5"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "## Setup",
+ "id": "73f6d18d567c6329"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "import xarray as xr\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ],
+ "id": "e8a50bb05c1400f2"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "## System Description\n",
+ "\n",
+ "The factory has:\n",
+ "\n",
+ "- **Industrial boiler**: 500 kW capacity, startup cost of 50€, minimum 4h uptime\n",
+ "- **Small backup boiler**: 100 kW, no startup constraints (always available)\n",
+ "- **Steam demand**: Varies with production schedule (high during shifts, low overnight)\n",
+ "\n",
+ "The main boiler is more efficient but has operational constraints. The backup is less efficient but flexible."
+ ],
+ "id": "54d9decc2ccf8235"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "## Define Time Horizon and Demand",
+ "id": "65694ad43e7a1f42"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": [
+ "# 3 days, hourly resolution\n",
+ "timesteps = pd.date_range('2024-03-11', periods=72, freq='h')\n",
+ "hours = np.arange(72)\n",
+ "hour_of_day = hours % 24\n",
+ "\n",
+ "# Factory operates in shifts:\n",
+ "# - Day shift (6am-2pm): 400 kW\n",
+ "# - Evening shift (2pm-10pm): 350 kW\n",
+ "# - Night (10pm-6am): 80 kW (maintenance heating only)\n",
+ "\n",
+ "steam_demand = np.select(\n",
+ " [\n",
+ " (hour_of_day >= 6) & (hour_of_day < 14), # Day shift\n",
+ " (hour_of_day >= 14) & (hour_of_day < 22), # Evening shift\n",
+ " ],\n",
+ " [400, 350],\n",
+ " default=80, # Night\n",
+ ")\n",
+ "\n",
+ "# Add some variation\n",
+ "np.random.seed(123)\n",
+ "steam_demand = steam_demand + np.random.normal(0, 20, len(steam_demand))\n",
+ "steam_demand = np.clip(steam_demand, 50, 450).astype(float)\n",
+ "\n",
+ "print(f'Peak demand: {steam_demand.max():.0f} kW')\n",
+ "print(f'Min demand: {steam_demand.min():.0f} kW')"
+ ],
+ "id": "8c606ee48c294628"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": "px.line(x=timesteps, y=steam_demand, title='Factory Steam Demand', labels={'x': 'Time', 'y': 'kW'})",
+ "id": "fd4f46fa717b1572"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "## Build System with Operational Constraints",
+ "id": "2d823131e625dcfa"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": [
+ "flow_system = fx.FlowSystem(timesteps)\n",
+ "\n",
+ "# Define and register custom carriers\n",
+ "flow_system.add_carriers(\n",
+ " fx.Carrier('gas', '#3498db', 'kW'),\n",
+ " fx.Carrier('steam', '#87CEEB', 'kW_th', 'Process steam'),\n",
+ ")\n",
+ "\n",
+ "flow_system.add_elements(\n",
+ " # === Buses ===\n",
+ " fx.Bus('Gas', carrier='gas'),\n",
+ " fx.Bus('Steam', carrier='steam'),\n",
+ " # === Effect ===\n",
+ " fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),\n",
+ " # === Gas Supply ===\n",
+ " fx.Source(\n",
+ " 'GasGrid',\n",
+ " outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)],\n",
+ " ),\n",
+ " # === Main Industrial Boiler (with operational constraints) ===\n",
+ " fx.linear_converters.Boiler(\n",
+ " 'MainBoiler',\n",
+ " thermal_efficiency=0.94, # High efficiency\n",
+ " # StatusParameters define on/off behavior\n",
+ " status_parameters=fx.StatusParameters(\n",
+ " effects_per_startup={'costs': 50}, # 50€ startup cost\n",
+ " min_uptime=4, # Must run at least 4 hours once started\n",
+ " min_downtime=2, # Must stay off at least 2 hours\n",
+ " ),\n",
+ " thermal_flow=fx.Flow(\n",
+ " 'Steam',\n",
+ " bus='Steam',\n",
+ " size=500,\n",
+ " relative_minimum=0.3, # Minimum load: 30% = 150 kW\n",
+ " ),\n",
+ " fuel_flow=fx.Flow('Gas', bus='Gas', size=600), # Size required for status_parameters\n",
+ " ),\n",
+ " # === Backup Boiler (flexible, but less efficient) ===\n",
+ " fx.linear_converters.Boiler(\n",
+ " 'BackupBoiler',\n",
+ " thermal_efficiency=0.85, # Lower efficiency\n",
+ " # No status parameters = can turn on/off freely\n",
+ " thermal_flow=fx.Flow('Steam', bus='Steam', size=150),\n",
+ " fuel_flow=fx.Flow('Gas', bus='Gas'),\n",
+ " ),\n",
+ " # === Factory Steam Demand ===\n",
+ " fx.Sink(\n",
+ " 'Factory',\n",
+ " inputs=[fx.Flow('Steam', bus='Steam', size=1, fixed_relative_profile=steam_demand)],\n",
+ " ),\n",
+ ")"
+ ],
+ "id": "736dfa9a935f6c7e"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "## Run Optimization",
+ "id": "70ae8aaa82997d51"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": "flow_system.optimize(fx.solvers.HighsSolver(mip_gap=0.01));",
+ "id": "76f27e3afe64f8c5"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "## Analyze Results\n",
+ "\n",
+ "### Steam Balance\n",
+ "\n",
+ "See how the two boilers share the load:"
+ ],
+ "id": "c42e2778fd0a8ca"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": "flow_system.statistics.plot.balance('Steam')",
+ "id": "9da80bc8faca05cd"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Main Boiler Operation\n",
+ "\n",
+ "Notice how the main boiler:\n",
+ "- Runs continuously during production (respecting min uptime)\n",
+ "- Stays above minimum load (30%)\n",
+ "- Shuts down during low-demand periods"
+ ],
+ "id": "c885d25675d71371"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": "flow_system.statistics.plot.heatmap('MainBoiler(Steam)')",
+ "id": "5a549b8b60f32745"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### On/Off Status\n",
+ "\n",
+ "Track the boiler's operational status:"
+ ],
+ "id": "66816d462d2f2654"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": [
+ "# Merge solution DataArrays directly - xarray aligns coordinates automatically\n",
+ "status_ds = xr.Dataset(\n",
+ " {\n",
+ " 'Status': flow_system.solution['MainBoiler|status'],\n",
+ " 'Steam Production [kW]': flow_system.solution['MainBoiler(Steam)|flow_rate'],\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "df = status_ds.to_dataframe().reset_index().melt(id_vars='time', var_name='variable', value_name='value')\n",
+ "fig = px.line(df, x='time', y='value', facet_col='variable', height=300, title='Main Boiler Operation')\n",
+ "fig.update_yaxes(matches=None, showticklabels=True)\n",
+ "fig.for_each_annotation(lambda a: a.update(text=a.text.split('=')[-1]))\n",
+ "fig"
+ ],
+ "id": "41801a37f07aa265"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "### Startup Count and Costs",
+ "id": "7ca893f03606362"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": [
+ "total_startups = int(flow_system.solution['MainBoiler|startup'].sum().item())\n",
+ "total_costs = flow_system.solution['costs'].item()\n",
+ "startup_costs = total_startups * 50\n",
+ "gas_costs = total_costs - startup_costs\n",
+ "\n",
+ "print('=== Cost Breakdown ===')\n",
+ "print(f'Number of startups: {total_startups}')\n",
+ "print(f'Startup costs: {startup_costs:.0f} €')\n",
+ "print(f'Gas costs: {gas_costs:.2f} €')\n",
+ "print(f'Total costs: {total_costs:.2f} €')"
+ ],
+ "id": "a95273c9775e1fd9"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Duration Curves\n",
+ "\n",
+ "See how often each boiler operates at different load levels:"
+ ],
+ "id": "e29cf8ae428387bd"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": "flow_system.statistics.plot.duration_curve('MainBoiler(Steam)')",
+ "id": "14e906ea8912de10"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": "flow_system.statistics.plot.duration_curve('BackupBoiler(Steam)')",
+ "id": "15d6068612a73f84"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "## Compare: Without Operational Constraints\n",
+ "\n",
+ "What if the main boiler had no startup costs or minimum uptime?"
+ ],
+ "id": "8354cd68733d5086"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": [
+ "# Build unconstrained system\n",
+ "fs_unconstrained = fx.FlowSystem(timesteps)\n",
+ "fs_unconstrained.add_carriers(\n",
+ " fx.Carrier('gas', '#3498db', 'kW'),\n",
+ " fx.Carrier('steam', '#87CEEB', 'kW_th', 'Process steam'),\n",
+ ")\n",
+ "\n",
+ "fs_unconstrained.add_elements(\n",
+ " fx.Bus('Gas', carrier='gas'),\n",
+ " fx.Bus('Steam', carrier='steam'),\n",
+ " fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),\n",
+ " fx.Source('GasGrid', outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour=0.06)]),\n",
+ " # Main boiler WITHOUT status parameters\n",
+ " fx.linear_converters.Boiler(\n",
+ " 'MainBoiler',\n",
+ " thermal_efficiency=0.94,\n",
+ " thermal_flow=fx.Flow('Steam', bus='Steam', size=500),\n",
+ " fuel_flow=fx.Flow('Gas', bus='Gas'),\n",
+ " ),\n",
+ " fx.linear_converters.Boiler(\n",
+ " 'BackupBoiler',\n",
+ " thermal_efficiency=0.85,\n",
+ " thermal_flow=fx.Flow('Steam', bus='Steam', size=150),\n",
+ " fuel_flow=fx.Flow('Gas', bus='Gas'),\n",
+ " ),\n",
+ " fx.Sink('Factory', inputs=[fx.Flow('Steam', bus='Steam', size=1, fixed_relative_profile=steam_demand)]),\n",
+ ")\n",
+ "\n",
+ "fs_unconstrained.optimize(fx.solvers.HighsSolver())\n",
+ "unconstrained_costs = fs_unconstrained.solution['costs'].item()\n",
+ "\n",
+ "print('=== Comparison ===')\n",
+ "print(f'With constraints: {total_costs:.2f} €')\n",
+ "print(f'Without constraints: {unconstrained_costs:.2f} €')\n",
+ "print(\n",
+ " f'Constraint cost: {total_costs - unconstrained_costs:.2f} € ({(total_costs - unconstrained_costs) / unconstrained_costs * 100:.1f}%)'\n",
+ ")"
+ ],
+ "id": "8769dbda34dd4ccf"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Energy Flow Sankey\n",
+ "\n",
+ "A Sankey diagram visualizes the total energy flows through the system:"
+ ],
+ "id": "64ddc254af867367"
+ },
+ {
+ "metadata": {},
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": null,
+ "source": "flow_system.statistics.plot.sankey.flows()",
+ "id": "f2742f4b0a7c5323"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "## Key Concepts\n",
+ "\n",
+ "### StatusParameters Options\n",
+ "\n",
+ "```python\n",
+ "fx.StatusParameters(\n",
+ " # Startup/shutdown costs\n",
+ " effects_per_startup={'costs': 50}, # Cost per startup event\n",
+ " effects_per_shutdown={'costs': 10}, # Cost per shutdown event\n",
+ " \n",
+ " # Time constraints\n",
+ " min_uptime=4, # Minimum hours running once started\n",
+ " min_downtime=2, # Minimum hours off once stopped\n",
+ " \n",
+ " # Startup limits\n",
+ " max_startups=10, # Maximum startups per period\n",
+ ")\n",
+ "```\n",
+ "\n",
+ "### Minimum Load\n",
+ "\n",
+ "Set via `Flow.relative_minimum`:\n",
+ "```python\n",
+ "fx.Flow('Steam', bus='Steam', size=500, relative_minimum=0.3) # Min 30% load\n",
+ "```\n",
+ "\n",
+ "### When Status is Active\n",
+ "\n",
+ "- When `StatusParameters` is set, a binary on/off variable is created\n",
+ "- Flow is zero when status=0, within bounds when status=1\n",
+ "- Without `StatusParameters`, flow can vary continuously from 0 to max\n",
+ "\n",
+ "## Summary\n",
+ "\n",
+ "You learned how to:\n",
+ "\n",
+ "- Add **startup costs** with `effects_per_startup`\n",
+ "- Set **minimum run times** with `min_uptime` and `min_downtime`\n",
+ "- Define **minimum load** with `relative_minimum`\n",
+ "- Access **status variables** from the solution\n",
+ "- Use **duration curves** to analyze operation patterns\n",
+ "\n",
+ "### Next Steps\n",
+ "\n",
+ "- **[05-multi-carrier-system](05-multi-carrier-system.ipynb)**: Model CHP with electricity and heat\n",
+ "- **[06a-time-varying-parameters](06a-time-varying-parameters.ipynb)**: Variable efficiency based on external conditions"
+ ],
+ "id": "2f9951587227304f"
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/05-multi-carrier-system.ipynb b/docs/notebooks/05-multi-carrier-system.ipynb
new file mode 100644
index 000000000..76de7e69a
--- /dev/null
+++ b/docs/notebooks/05-multi-carrier-system.ipynb
@@ -0,0 +1,370 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": "# Multi-Carrier\n\nHospital with CHP producing both electricity and heat.\n\nThis notebook introduces:\n\n- **Multiple energy carriers**: Electricity, heat, and gas in one system\n- **CHP (Cogeneration)**: Equipment producing multiple outputs\n- **Electricity market**: Buying and selling to the grid\n- **Carrier colors**: Visual distinction between energy types"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "import xarray as xr\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "## System Description\n",
+ "\n",
+ "The hospital energy system:\n",
+ "\n",
+ "```\n",
+ " Grid Buy ──►\n",
+ " [Electricity] ──► Hospital Elec. Load\n",
+ " Grid Sell ◄── ▲\n",
+ " │\n",
+ " Gas Grid ──► [Gas] ──► CHP ──────┘\n",
+ " │ │\n",
+ " │ ▼\n",
+ " │ [Heat] ──► Hospital Heat Load\n",
+ " │ ▲\n",
+ " └──► Boiler\n",
+ "```\n",
+ "\n",
+ "**Equipment:**\n",
+ "- **CHP**: 200 kW electrical, ~250 kW thermal (η_el=40%, η_th=50%)\n",
+ "- **Gas Boiler**: 400 kW thermal backup\n",
+ "- **Grid**: Buy electricity at variable prices, sell at lower prices"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4",
+ "metadata": {},
+ "source": [
+ "## Define Time Horizon and Demand Profiles"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# One week, hourly\n",
+ "timesteps = pd.date_range('2024-02-05', periods=168, freq='h')\n",
+ "hours = np.arange(168)\n",
+ "hour_of_day = hours % 24\n",
+ "\n",
+ "# Hospital electricity demand (kW)\n",
+ "# Base load + daily pattern (higher during day for equipment, lighting)\n",
+ "elec_base = 150 # 24/7 critical systems\n",
+ "elec_daily = 100 * np.sin((hour_of_day - 6) * np.pi / 12) # Peak at noon\n",
+ "elec_daily = np.maximum(0, elec_daily)\n",
+ "electricity_demand = elec_base + elec_daily\n",
+ "\n",
+ "# Hospital heat demand (kW)\n",
+ "# Higher in morning, drops during day, increases for hot water in evening\n",
+ "heat_pattern = np.select(\n",
+ " [\n",
+ " (hour_of_day >= 5) & (hour_of_day < 9), # Morning warmup\n",
+ " (hour_of_day >= 9) & (hour_of_day < 17), # Daytime\n",
+ " (hour_of_day >= 17) & (hour_of_day < 22), # Evening\n",
+ " ],\n",
+ " [350, 250, 300],\n",
+ " default=200, # Night\n",
+ ")\n",
+ "heat_demand = heat_pattern.astype(float)\n",
+ "\n",
+ "# Add random variation\n",
+ "np.random.seed(456)\n",
+ "electricity_demand += np.random.normal(0, 15, len(timesteps))\n",
+ "heat_demand += np.random.normal(0, 20, len(timesteps))\n",
+ "electricity_demand = np.clip(electricity_demand, 100, 300)\n",
+ "heat_demand = np.clip(heat_demand, 150, 400)\n",
+ "\n",
+ "print(f'Electricity: {electricity_demand.min():.0f} - {electricity_demand.max():.0f} kW')\n",
+ "print(f'Heat: {heat_demand.min():.0f} - {heat_demand.max():.0f} kW')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Electricity prices (€/kWh)\n",
+ "# Time-of-use: expensive during day, cheaper at night\n",
+ "elec_buy_price = np.where(\n",
+ " (hour_of_day >= 7) & (hour_of_day <= 21),\n",
+ " 0.35, # Peak - high electricity prices make CHP attractive\n",
+ " 0.20, # Off-peak\n",
+ ")\n",
+ "\n",
+ "# Feed-in tariff (sell price) - allows selling excess CHP electricity\n",
+ "elec_sell_price = 0.12 # Fixed feed-in rate\n",
+ "\n",
+ "# Gas price - relatively low, favoring gas-based generation\n",
+ "gas_price = 0.05 # €/kWh"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Visualize demands and prices with plotly - using xarray and faceting\n",
+ "profiles = xr.Dataset(\n",
+ " {\n",
+ " 'Electricity Demand [kW]': xr.DataArray(electricity_demand, dims=['time'], coords={'time': timesteps}),\n",
+ " 'Heat Demand [kW]': xr.DataArray(heat_demand, dims=['time'], coords={'time': timesteps}),\n",
+ " 'Elec. Buy Price [€/kWh]': xr.DataArray(elec_buy_price, dims=['time'], coords={'time': timesteps}),\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "df = profiles.to_dataframe().reset_index().melt(id_vars='time', var_name='variable', value_name='value')\n",
+ "fig = px.line(df, x='time', y='value', facet_col='variable', height=300)\n",
+ "fig.update_yaxes(matches=None, showticklabels=True)\n",
+ "fig.for_each_annotation(lambda a: a.update(text=a.text.split('=')[-1]))\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8",
+ "metadata": {},
+ "source": [
+ "## Build the Multi-Carrier System"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9",
+ "metadata": {},
+ "outputs": [],
+ "source": "flow_system = fx.FlowSystem(timesteps)\nflow_system.add_carriers(\n fx.Carrier('gas', '#3498db', 'kW'),\n fx.Carrier('electricity', '#f1c40f', 'kW'),\n fx.Carrier('heat', '#e74c3c', 'kW'),\n)\nflow_system.add_elements(\n # === Buses with carriers for visual distinction ===\n fx.Bus('Electricity', carrier='electricity'),\n fx.Bus('Heat', carrier='heat'),\n fx.Bus('Gas', carrier='gas'),\n # === Effects ===\n fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n fx.Effect('CO2', 'kg', 'CO2 Emissions'), # Track emissions too\n # === Gas Supply ===\n fx.Source(\n 'GasGrid',\n outputs=[\n fx.Flow(\n 'Gas',\n bus='Gas',\n size=1000,\n effects_per_flow_hour={'costs': gas_price, 'CO2': 0.2}, # Gas: 0.2 kg CO2/kWh\n )\n ],\n ),\n # === Electricity Grid (buy) ===\n fx.Source(\n 'GridBuy',\n outputs=[\n fx.Flow(\n 'Electricity',\n bus='Electricity',\n size=500,\n effects_per_flow_hour={'costs': elec_buy_price, 'CO2': 0.4}, # Grid: 0.4 kg CO2/kWh\n )\n ],\n ),\n # === Electricity Grid (sell) - negative cost = revenue ===\n fx.Sink(\n 'GridSell',\n inputs=[\n fx.Flow(\n 'Electricity',\n bus='Electricity',\n size=200,\n effects_per_flow_hour={'costs': -elec_sell_price}, # Negative = income\n )\n ],\n ),\n # === CHP Unit (Combined Heat and Power) ===\n fx.linear_converters.CHP(\n 'CHP',\n electrical_efficiency=0.40, # 40% to electricity\n thermal_efficiency=0.50, # 50% to heat (total: 90%)\n status_parameters=fx.StatusParameters(\n effects_per_startup={'costs': 30},\n min_uptime=3,\n ),\n electrical_flow=fx.Flow('P_el', bus='Electricity', size=200),\n thermal_flow=fx.Flow('Q_th', bus='Heat', size=250),\n fuel_flow=fx.Flow(\n 'Q_fuel',\n bus='Gas',\n size=500,\n relative_minimum=0.4, # Min 40% load\n ),\n ),\n # === Gas Boiler (heat only) ===\n fx.linear_converters.Boiler(\n 'Boiler',\n thermal_efficiency=0.92,\n thermal_flow=fx.Flow('Q_th', bus='Heat', size=400),\n fuel_flow=fx.Flow('Q_fuel', bus='Gas'),\n ),\n # === Hospital Loads ===\n fx.Sink(\n 'HospitalElec',\n inputs=[fx.Flow('Load', bus='Electricity', size=1, fixed_relative_profile=electricity_demand)],\n ),\n fx.Sink(\n 'HospitalHeat',\n inputs=[fx.Flow('Load', bus='Heat', size=1, fixed_relative_profile=heat_demand)],\n ),\n)"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "10",
+ "metadata": {},
+ "source": [
+ "## Run Optimization"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "11",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.optimize(fx.solvers.HighsSolver(mip_gap=0.01));"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12",
+ "metadata": {},
+ "source": [
+ "## Analyze Results\n",
+ "\n",
+ "### Electricity Balance"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "13",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.balance('Electricity')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "14",
+ "metadata": {},
+ "source": [
+ "### Heat Balance"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "15",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.balance('Heat')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "16",
+ "metadata": {},
+ "source": [
+ "### Gas Balance"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "17",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.balance('Gas')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "18",
+ "metadata": {},
+ "source": [
+ "### CHP Operation Pattern"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "19",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.heatmap('CHP(P_el)')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "20",
+ "metadata": {},
+ "source": [
+ "### Cost and Emissions Summary"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "21",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "total_costs = flow_system.solution['costs'].item()\n",
+ "total_co2 = flow_system.solution['CO2'].item()\n",
+ "\n",
+ "# Energy flows\n",
+ "flow_rates = flow_system.statistics.flow_rates\n",
+ "grid_buy = flow_rates['GridBuy(Electricity)'].sum().item()\n",
+ "grid_sell = flow_rates['GridSell(Electricity)'].sum().item()\n",
+ "chp_elec = flow_rates['CHP(P_el)'].sum().item()\n",
+ "chp_heat = flow_rates['CHP(Q_th)'].sum().item()\n",
+ "boiler_heat = flow_rates['Boiler(Q_th)'].sum().item()\n",
+ "\n",
+ "total_elec = electricity_demand.sum()\n",
+ "total_heat = heat_demand.sum()\n",
+ "\n",
+ "print('=== Energy Summary ===')\n",
+ "print(f'Total electricity demand: {total_elec:.0f} kWh')\n",
+ "print(f' - From CHP: {chp_elec:.0f} kWh ({chp_elec / total_elec * 100:.1f}%)')\n",
+ "print(f' - From Grid: {grid_buy:.0f} kWh ({grid_buy / total_elec * 100:.1f}%)')\n",
+ "print(f' - Sold to Grid: {grid_sell:.0f} kWh')\n",
+ "print()\n",
+ "print(f'Total heat demand: {total_heat:.0f} kWh')\n",
+ "print(f' - From CHP: {chp_heat:.0f} kWh ({chp_heat / total_heat * 100:.1f}%)')\n",
+ "print(f' - From Boiler: {boiler_heat:.0f} kWh ({boiler_heat / total_heat * 100:.1f}%)')\n",
+ "print()\n",
+ "print('=== Costs & Emissions ===')\n",
+ "print(f'Total costs: {total_costs:.2f} €')\n",
+ "print(f'Total CO2: {total_co2:.0f} kg')\n",
+ "print(f'Specific costs: {total_costs / (total_elec + total_heat) * 100:.2f} ct/kWh')\n",
+ "print(f'Specific CO2: {total_co2 / (total_elec + total_heat) * 1000:.1f} g/kWh')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "22",
+ "metadata": {},
+ "source": [
+ "### Compare: What if No CHP?\n",
+ "\n",
+ "How much does the CHP save compared to buying all electricity?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "23",
+ "metadata": {},
+ "outputs": [],
+ "source": "# Build system without CHP\nfs_no_chp = fx.FlowSystem(timesteps)\nfs_no_chp.add_carriers(\n fx.Carrier('gas', '#3498db', 'kW'),\n fx.Carrier('electricity', '#f1c40f', 'kW'),\n fx.Carrier('heat', '#e74c3c', 'kW'),\n)\nfs_no_chp.add_elements(\n fx.Bus('Electricity', carrier='electricity'),\n fx.Bus('Heat', carrier='heat'),\n fx.Bus('Gas', carrier='gas'),\n fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n fx.Effect('CO2', 'kg', 'CO2 Emissions'),\n fx.Source(\n 'GasGrid',\n outputs=[fx.Flow('Gas', bus='Gas', size=1000, effects_per_flow_hour={'costs': gas_price, 'CO2': 0.2})],\n ),\n fx.Source(\n 'GridBuy',\n outputs=[\n fx.Flow(\n 'Electricity', bus='Electricity', size=500, effects_per_flow_hour={'costs': elec_buy_price, 'CO2': 0.4}\n )\n ],\n ),\n # Only boiler for heat\n fx.linear_converters.Boiler(\n 'Boiler',\n thermal_efficiency=0.92,\n thermal_flow=fx.Flow('Q_th', bus='Heat', size=500),\n fuel_flow=fx.Flow('Q_fuel', bus='Gas'),\n ),\n fx.Sink(\n 'HospitalElec', inputs=[fx.Flow('Load', bus='Electricity', size=1, fixed_relative_profile=electricity_demand)]\n ),\n fx.Sink('HospitalHeat', inputs=[fx.Flow('Load', bus='Heat', size=1, fixed_relative_profile=heat_demand)]),\n)\n\nfs_no_chp.optimize(fx.solvers.HighsSolver())\n\nno_chp_costs = fs_no_chp.solution['costs'].item()\nno_chp_co2 = fs_no_chp.solution['CO2'].item()\n\nprint('=== CHP Benefit Analysis ===')\nprint(f'Without CHP: {no_chp_costs:.2f} € / {no_chp_co2:.0f} kg CO2')\nprint(f'With CHP: {total_costs:.2f} € / {total_co2:.0f} kg CO2')\nprint(f'Cost savings: {no_chp_costs - total_costs:.2f} € ({(no_chp_costs - total_costs) / no_chp_costs * 100:.1f}%)')\nprint(f'CO2 reduction: {no_chp_co2 - total_co2:.0f} kg ({(no_chp_co2 - total_co2) / no_chp_co2 * 100:.1f}%)')"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "24",
+ "metadata": {},
+ "source": [
+ "### Energy Flow Sankey\n",
+ "\n",
+ "A Sankey diagram visualizes the total energy flows through the multi-carrier system:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "25",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.sankey.flows()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "26",
+ "metadata": {},
+ "source": "## Key Concepts\n\n### Multi-Carrier Systems\n\n- Multiple buses for different energy carriers (electricity, heat, gas)\n- Components can connect to multiple buses (CHP produces both electricity and heat)\n- Carriers enable automatic coloring in visualizations\n\n### CHP Modeling\n\n```python\nfx.linear_converters.CHP(\n 'CHP',\n electrical_efficiency=0.40, # Fuel → Electricity\n thermal_efficiency=0.50, # Fuel → Heat\n # Total efficiency = 0.40 + 0.50 = 0.90 (90%)\n electrical_flow=fx.Flow('P_el', bus='Electricity', size=200),\n thermal_flow=fx.Flow('Q_th', bus='Heat', size=250),\n fuel_flow=fx.Flow('Q_fuel', bus='Gas', size=500),\n)\n```\n\n### Electricity Markets\n\n- **Buy**: Source with positive cost\n- **Sell**: Sink with negative cost (= revenue)\n- Different prices for buy vs. sell (spread)\n\n### Tracking Multiple Effects\n\n```python\nfx.Effect('costs', '€', 'Total Costs', is_objective=True) # Minimize this\nfx.Effect('CO2', 'kg', 'CO2 Emissions') # Just track, don't optimize\n```\n\n## Summary\n\nYou learned how to:\n\n- Model **multiple energy carriers** (electricity, heat, gas)\n- Use **CHP** for combined heat and power production\n- Model **electricity markets** with buy/sell prices\n- Track **multiple effects** (costs and emissions)\n- Analyze **multi-carrier balances**\n\n### Next Steps\n\n- **[06a-time-varying-parameters](06a-time-varying-parameters.ipynb)**: Variable efficiency based on conditions\n- **[07-scenarios-and-periods](07-scenarios-and-periods.ipynb)**: Plan under uncertainty"
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/06a-time-varying-parameters.ipynb b/docs/notebooks/06a-time-varying-parameters.ipynb
new file mode 100644
index 000000000..9856aa095
--- /dev/null
+++ b/docs/notebooks/06a-time-varying-parameters.ipynb
@@ -0,0 +1,339 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": [
+ "# Time-Varying Parameters\n",
+ "\n",
+ "Model equipment with efficiency that changes based on external conditions.\n",
+ "\n",
+ "This notebook covers:\n",
+ "\n",
+ "- **Time-varying conversion factors**: Efficiency depends on external conditions\n",
+ "- **Temperature-dependent COP**: Heat pump performance varies with weather\n",
+ "- **Practical application**: Using arrays in conversion factor definitions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "2",
+ "metadata": {},
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "import xarray as xr\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ],
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "## The Problem: Variable Heat Pump Efficiency\n",
+ "\n",
+ "A heat pump's COP (Coefficient of Performance) depends on the temperature difference between source and sink:\n",
+ "\n",
+ "- **Mild weather** (10°C outside): COP ≈ 4.5 (1 kWh electricity → 4.5 kWh heat)\n",
+ "- **Cold weather** (-5°C outside): COP ≈ 2.5 (1 kWh electricity → 2.5 kWh heat)\n",
+ "\n",
+ "This time-varying relationship can be modeled directly using arrays in the conversion factors.\n",
+ "\n",
+ "### When to Use This Approach\n",
+ "\n",
+ "Use time-varying conversion factors when:\n",
+ "- Efficiency depends on **external conditions** (temperature, solar irradiance, humidity)\n",
+ "- The relationship is **independent of the load level**\n",
+ "- You have **measured or forecast data** for the efficiency profile"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4",
+ "metadata": {},
+ "source": [
+ "## Define Time Series Data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "# One winter week\n",
+ "timesteps = pd.date_range('2024-01-22', periods=168, freq='h')\n",
+ "hours = np.arange(168)\n",
+ "hour_of_day = hours % 24\n",
+ "\n",
+ "# Outdoor temperature: daily cycle with cold nights\n",
+ "temp_base = 2 # Average temp in °C\n",
+ "temp_amplitude = 5 # Daily variation\n",
+ "outdoor_temp = temp_base + temp_amplitude * np.sin((hour_of_day - 6) * np.pi / 12)\n",
+ "\n",
+ "# Add day-to-day variation for realism\n",
+ "np.random.seed(789)\n",
+ "daily_offset = np.repeat(np.random.uniform(-3, 3, 7), 24)\n",
+ "outdoor_temp = outdoor_temp + daily_offset"
+ ],
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "code",
+ "id": "6",
+ "metadata": {},
+ "source": [
+ "# Heat demand: inversely related to outdoor temp (higher demand when colder)\n",
+ "heat_demand = 200 - 8 * outdoor_temp\n",
+ "heat_demand = np.clip(heat_demand, 100, 300)"
+ ],
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "code",
+ "id": "7",
+ "metadata": {},
+ "source": [
+ "# Visualize input profiles\n",
+ "profiles = xr.Dataset(\n",
+ " {\n",
+ " 'Outdoor Temp [°C]': xr.DataArray(outdoor_temp, dims=['time'], coords={'time': timesteps}),\n",
+ " 'Heat Demand [kW]': xr.DataArray(heat_demand, dims=['time'], coords={'time': timesteps}),\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "df = profiles.to_dataframe().reset_index().melt(id_vars='time', var_name='variable', value_name='value')\n",
+ "fig = px.line(df, x='time', y='value', facet_col='variable', height=300)\n",
+ "fig.update_yaxes(matches=None, showticklabels=True)\n",
+ "fig.for_each_annotation(lambda a: a.update(text=a.text.split('=')[-1]))\n",
+ "fig"
+ ],
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8",
+ "metadata": {},
+ "source": [
+ "## Calculate Time-Varying COP\n",
+ "\n",
+ "The COP depends on outdoor temperature. We use a simplified Carnot-based formula:\n",
+ "\n",
+ "$$\\text{COP}_{\\text{real}} \\approx 0.45 \\times \\text{COP}_{\\text{Carnot}} = 0.45 \\times \\frac{T_{\\text{supply}}}{T_{\\text{supply}} - T_{\\text{source}}}$$\n",
+ "\n",
+ "where temperatures are in Kelvin."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "# COP calculation\n",
+ "T_supply = 45 + 273.15 # Supply temperature 45°C in Kelvin\n",
+ "T_source = outdoor_temp + 273.15 # Outdoor temp in Kelvin\n",
+ "\n",
+ "carnot_cop = T_supply / (T_supply - T_source)\n",
+ "real_cop = 0.45 * carnot_cop\n",
+ "real_cop = np.clip(real_cop, 2.0, 5.0) # Physical limits"
+ ],
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "code",
+ "id": "10",
+ "metadata": {},
+ "source": [
+ "# Visualize COP vs temperature relationship\n",
+ "px.scatter(\n",
+ " x=outdoor_temp,\n",
+ " y=real_cop,\n",
+ " title='Heat Pump COP vs Outdoor Temperature',\n",
+ " labels={'x': 'Outdoor Temperature [°C]', 'y': 'COP'},\n",
+ " opacity=0.5,\n",
+ ")"
+ ],
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11",
+ "metadata": {},
+ "source": [
+ "## Build the Model\n",
+ "\n",
+ "The key is passing the COP array directly to `conversion_factors`. The equation becomes:\n",
+ "\n",
+ "$$\\text{Elec} \\times \\text{COP}(t) = \\text{Heat} \\times 1$$\n",
+ "\n",
+ "where `COP(t)` varies at each timestep."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "12",
+ "metadata": {},
+ "source": "flow_system = fx.FlowSystem(timesteps)\nflow_system.add_carriers(\n fx.Carrier('electricity', '#f1c40f', 'kW'),\n fx.Carrier('heat', '#e74c3c', 'kW'),\n)\nflow_system.add_elements(\n # Buses\n fx.Bus('Electricity', carrier='electricity'),\n fx.Bus('Heat', carrier='heat'),\n # Effect for cost tracking\n fx.Effect('costs', '€', 'Operating Costs', is_standard=True, is_objective=True),\n # Grid electricity source\n fx.Source('Grid', outputs=[fx.Flow('Elec', bus='Electricity', size=500, effects_per_flow_hour=0.30)]),\n # Heat pump with TIME-VARYING COP\n fx.LinearConverter(\n 'HeatPump',\n inputs=[fx.Flow('Elec', bus='Electricity', size=150)],\n outputs=[fx.Flow('Heat', bus='Heat', size=500)],\n conversion_factors=[{'Elec': real_cop, 'Heat': 1}], # <-- Array for time-varying COP\n ),\n # Heat demand\n fx.Sink('Building', inputs=[fx.Flow('Heat', bus='Heat', size=1, fixed_relative_profile=heat_demand)]),\n)\n\nflow_system.optimize(fx.solvers.HighsSolver())",
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "id": "13",
+ "metadata": {},
+ "source": [
+ "## Analyze Results"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "14",
+ "metadata": {},
+ "source": [
+ "flow_system.statistics.plot.balance('Heat')"
+ ],
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "code",
+ "id": "15",
+ "metadata": {},
+ "source": [
+ "flow_system.statistics.plot.balance('Electricity')"
+ ],
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "code",
+ "id": "16",
+ "metadata": {},
+ "source": [
+ "# Compare electricity consumption vs heat output using xarray for alignment\n",
+ "# Create dataset with solution and input data - xarray auto-aligns by time coordinate\n",
+ "comparison = xr.Dataset(\n",
+ " {\n",
+ " 'elec_consumption': flow_system.solution['HeatPump(Elec)|flow_rate'],\n",
+ " 'heat_output': flow_system.solution['HeatPump(Heat)|flow_rate'],\n",
+ " 'outdoor_temp': xr.DataArray(outdoor_temp, dims=['time'], coords={'time': timesteps}),\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "# Calculate effective COP at each timestep\n",
+ "comparison['effective_cop'] = xr.where(\n",
+ " comparison['elec_consumption'] > 0.1, comparison['heat_output'] / comparison['elec_consumption'], np.nan\n",
+ ")\n",
+ "\n",
+ "px.scatter(\n",
+ " x=comparison['outdoor_temp'].values,\n",
+ " y=comparison['effective_cop'].values,\n",
+ " title='Actual Operating COP vs Outdoor Temperature',\n",
+ " labels={'x': 'Outdoor Temperature [°C]', 'y': 'Operating COP'},\n",
+ ")"
+ ],
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17",
+ "metadata": {},
+ "source": [
+ "## Key Concepts\n",
+ "\n",
+ "### Conversion Factor Syntax\n",
+ "\n",
+ "The `conversion_factors` parameter accepts a list of dictionaries where values can be:\n",
+ "- **Scalars**: Constant efficiency (e.g., `{'Fuel': 1, 'Heat': 0.9}`)\n",
+ "- **Arrays**: Time-varying efficiency (e.g., `{'Elec': cop_array, 'Heat': 1}`)\n",
+ "- **TimeSeriesData**: For more complex data with metadata\n",
+ "\n",
+ "```python\n",
+ "fx.LinearConverter(\n",
+ " 'HeatPump',\n",
+ " inputs=[fx.Flow('Elec', bus='Electricity', size=150)],\n",
+ " outputs=[fx.Flow('Heat', bus='Heat', size=500)],\n",
+ " conversion_factors=[{'Elec': cop_array, 'Heat': 1}], # Time-varying\n",
+ ")\n",
+ "```\n",
+ "\n",
+ "### Physical Interpretation\n",
+ "\n",
+ "The conversion equation at each timestep:\n",
+ "$$\\text{Input}_1 \\times \\text{factor}_1(t) + \\text{Input}_2 \\times \\text{factor}_2(t) + ... = 0$$\n",
+ "\n",
+ "For a heat pump: `Elec * COP(t) - Heat * 1 = 0` → `Heat = Elec * COP(t)`\n",
+ "\n",
+ "### Common Use Cases\n",
+ "\n",
+ "| Equipment | Varying Parameter | External Driver |\n",
+ "|-----------|-------------------|------------------|\n",
+ "| Heat pump | COP | Outdoor temperature |\n",
+ "| Solar PV | Capacity factor | Solar irradiance |\n",
+ "| Cooling tower | Efficiency | Wet bulb temperature |\n",
+ "| Gas turbine | Heat rate | Ambient temperature |"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "18",
+ "metadata": {},
+ "source": [
+ "## Summary\n",
+ "\n",
+ "You learned how to:\n",
+ "\n",
+ "- Model **time-varying efficiency** using arrays in conversion factors\n",
+ "- Calculate **temperature-dependent COP** for heat pumps\n",
+ "- Analyze the **resulting operation** with varying efficiency\n",
+ "\n",
+ "### When to Use This vs Other Approaches\n",
+ "\n",
+ "| Approach | Use When | Example |\n",
+ "|----------|----------|--------|\n",
+ "| **Time-varying factors** (this notebook) | Efficiency varies with external conditions | Heat pump COP vs temperature |\n",
+ "| **PiecewiseConversion** | Efficiency varies with load level | Gas engine efficiency curve |\n",
+ "| **PiecewiseEffects** | Costs vary non-linearly with size | Economies of scale |\n",
+ "\n",
+ "### Next Steps\n",
+ "\n",
+ "- **[06b-piecewise-conversion](06b-piecewise-conversion.ipynb)**: Load-dependent efficiency curves\n",
+ "- **[06c-piecewise-effects](06c-piecewise-effects.ipynb)**: Non-linear cost functions"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.10.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/06b-piecewise-conversion.ipynb b/docs/notebooks/06b-piecewise-conversion.ipynb
new file mode 100644
index 000000000..6493a843c
--- /dev/null
+++ b/docs/notebooks/06b-piecewise-conversion.ipynb
@@ -0,0 +1,4371 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": [
+ "# Piecewise Conversion\n",
+ "\n",
+ "Model equipment with **load-dependent efficiency** using piecewise linear approximation.\n",
+ "\n",
+ "**User Story:** A gas engine's efficiency varies with load - lower at part-load, optimal at mid-load. We want to capture this non-linear behavior in our optimization."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "1",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T13:46:20.634505Z",
+ "start_time": "2025-12-13T13:46:16.763911Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "flixopt.config.CONFIG"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2",
+ "metadata": {},
+ "source": [
+ "## The Problem\n",
+ "\n",
+ "Real equipment efficiency varies with operating point:\n",
+ "\n",
+ "| Load Level | Electrical Efficiency | Reason |\n",
+ "|------------|----------------------|--------|\n",
+ "| 25-50% (part load) | 32-38% | Throttling losses |\n",
+ "| 50-75% (mid load) | 38-42% | Near design point |\n",
+ "| 75-100% (full load) | 42-40% | Thermal limits |\n",
+ "\n",
+ "A constant efficiency assumption misses this behavior."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "## Define the Efficiency Curve\n",
+ "\n",
+ "Each `Piece` defines corresponding fuel input and electricity output ranges:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "4",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T13:46:20.692018Z",
+ "start_time": "2025-12-13T13:46:20.688366Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "piecewise_efficiency = fx.PiecewiseConversion(\n",
+ " {\n",
+ " 'Fuel': fx.Piecewise(\n",
+ " [\n",
+ " fx.Piece(start=78, end=132), # Part load\n",
+ " fx.Piece(start=132, end=179), # Mid load\n",
+ " fx.Piece(start=179, end=250), # Full load\n",
+ " ]\n",
+ " ),\n",
+ " 'Elec': fx.Piecewise(\n",
+ " [\n",
+ " fx.Piece(start=25, end=50), # 32% -> 38% efficiency\n",
+ " fx.Piece(start=50, end=75), # 38% -> 42% efficiency\n",
+ " fx.Piece(start=75, end=100), # 42% -> 40% efficiency\n",
+ " ]\n",
+ " ),\n",
+ " }\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "## Build and Solve"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "6",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T13:46:21.272711Z",
+ "start_time": "2025-12-13T13:46:20.704350Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Running HiGHS 1.12.0 (git hash: 755a8e0): Copyright (c) 2025 HiGHS under MIT licence terms\n",
+ "MIP linopy-problem-0haogvbp has 298 rows; 394 cols; 1070 nonzeros; 72 integer variables (72 binary)\n",
+ "Coefficient ranges:\n",
+ " Matrix [5e-02, 2e+02]\n",
+ " Cost [1e+00, 1e+00]\n",
+ " Bound [1e+00, 3e+02]\n",
+ " RHS [1e+00, 1e+00]\n",
+ "Presolving model\n",
+ "168 rows, 240 cols, 672 nonzeros 0s\n",
+ "119 rows, 214 cols, 428 nonzeros 0s\n",
+ "97 rows, 60 cols, 115 nonzeros 0s\n",
+ "6 rows, 10 cols, 20 nonzeros 0s\n",
+ "Presolve reductions: rows 6(-292); columns 10(-384); nonzeros 20(-1050) \n",
+ "\n",
+ "Solving MIP model with:\n",
+ " 6 rows\n",
+ " 10 cols (2 binary, 0 integer, 0 implied int., 8 continuous, 0 domain fixed)\n",
+ " 20 nonzeros\n",
+ "\n",
+ "Src: B => Branching; C => Central rounding; F => Feasibility pump; H => Heuristic;\n",
+ " I => Shifting; J => Feasibility jump; L => Sub-MIP; P => Empty MIP; R => Randomized rounding;\n",
+ " S => Solve LP; T => Evaluate node; U => Unbounded; X => User solution; Y => HiGHS solution;\n",
+ " Z => ZI Round; l => Trivial lower; p => Trivial point; u => Trivial upper; z => Trivial zero\n",
+ "\n",
+ " Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work \n",
+ "Src Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time\n",
+ "\n",
+ " J 0 0 0 100.00% -inf 182.9596783 Large 0 0 0 0 0.0s\n",
+ " 1 0 1 100.00% 182.9596783 182.9596783 0.00% 0 0 0 0 0.0s\n",
+ "\n",
+ "Solving report\n",
+ " Model linopy-problem-0haogvbp\n",
+ " Status Optimal\n",
+ " Primal bound 182.959678343\n",
+ " Dual bound 182.959678343\n",
+ " Gap 0% (tolerance: 1%)\n",
+ " P-D integral 0\n",
+ " Solution status feasible\n",
+ " 182.959678343 (objective)\n",
+ " 0 (bound viol.)\n",
+ " 0 (int. viol.)\n",
+ " 0 (row viol.)\n",
+ " Timing 0.01\n",
+ " Max sub-MIP depth 0\n",
+ " Nodes 1\n",
+ " Repair LPs 0\n",
+ " LP iterations 0\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "FlowSystem\n",
+ "==========\n",
+ "Timesteps: 24 (Hour) [2024-01-22 to 2024-01-22]\n",
+ "Periods: None\n",
+ "Scenarios: None\n",
+ "Status: ✓\n",
+ "\n",
+ "Components (3 items)\n",
+ "--------------------\n",
+ " * GasEngine\n",
+ " * GasGrid\n",
+ " * Load\n",
+ "\n",
+ "Buses (2 items)\n",
+ "---------------\n",
+ " * Electricity\n",
+ " * Gas\n",
+ "\n",
+ "Effects (2 items)\n",
+ "-----------------\n",
+ " * costs\n",
+ " * Penalty\n",
+ "\n",
+ "Flows (4 items)\n",
+ "---------------\n",
+ " * GasEngine(Elec)\n",
+ " * GasEngine(Fuel)\n",
+ " * GasGrid(Gas)\n",
+ " * Load(Elec)"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "timesteps = pd.date_range('2024-01-22', periods=24, freq='h')\n",
+ "\n",
+ "# Demand varies through the day (30-90 kW, within piecewise range 25-100)\n",
+ "elec_demand = 60 + 30 * np.sin(np.arange(24) * np.pi / 12)\n",
+ "\n",
+ "fs = fx.FlowSystem(timesteps)\n",
+ "fs.add_elements(\n",
+ " fx.Bus('Gas'),\n",
+ " fx.Bus('Electricity'),\n",
+ " fx.Effect('costs', '€', is_standard=True, is_objective=True),\n",
+ " fx.Source('GasGrid', outputs=[fx.Flow('Gas', bus='Gas', size=300, effects_per_flow_hour=0.05)]),\n",
+ " fx.LinearConverter(\n",
+ " 'GasEngine',\n",
+ " inputs=[fx.Flow('Fuel', bus='Gas')],\n",
+ " outputs=[fx.Flow('Elec', bus='Electricity')],\n",
+ " piecewise_conversion=piecewise_efficiency,\n",
+ " ),\n",
+ " fx.Sink('Load', inputs=[fx.Flow('Elec', bus='Electricity', size=1, fixed_relative_profile=elec_demand)]),\n",
+ ")\n",
+ "\n",
+ "fs.optimize(fx.solvers.HighsSolver())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7",
+ "metadata": {},
+ "source": [
+ "## Visualize the Efficiency Curve"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "8",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T13:46:21.384359Z",
+ "start_time": "2025-12-13T13:46:21.288290Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " \n",
+ " \n",
+ " "
+ ]
+ },
+ "jetTransient": {
+ "display_id": null
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ]
+ },
+ "jetTransient": {
+ "display_id": null
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fs.components['GasEngine'].piecewise_conversion.plot(x_flow='Fuel')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "## Results"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "10",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T13:46:22.068940Z",
+ "start_time": "2025-12-13T13:46:21.920317Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ "PlotResult(data=
Size: 600B\n",
+ "Dimensions: (time: 25)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 200B 2024-01-22 ... 2024-01-23\n",
+ "Data variables:\n",
+ " GasEngine(Elec) (time) float64 200B -60.0 -67.76 -75.0 ... -45.0 -52.24 nan\n",
+ " Load(Elec) (time) float64 200B 60.0 67.76 75.0 ... 45.0 52.24 nan, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=GasEngine(Elec)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'GasEngine(Elec)',\n",
+ " 'marker': {'color': '#EF553B', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'GasEngine(Elec)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-22T00:00:00.000000000', '2024-01-22T01:00:00.000000000',\n",
+ " '2024-01-22T02:00:00.000000000', '2024-01-22T03:00:00.000000000',\n",
+ " '2024-01-22T04:00:00.000000000', '2024-01-22T05:00:00.000000000',\n",
+ " '2024-01-22T06:00:00.000000000', '2024-01-22T07:00:00.000000000',\n",
+ " '2024-01-22T08:00:00.000000000', '2024-01-22T09:00:00.000000000',\n",
+ " '2024-01-22T10:00:00.000000000', '2024-01-22T11:00:00.000000000',\n",
+ " '2024-01-22T12:00:00.000000000', '2024-01-22T13:00:00.000000000',\n",
+ " '2024-01-22T14:00:00.000000000', '2024-01-22T15:00:00.000000000',\n",
+ " '2024-01-22T16:00:00.000000000', '2024-01-22T17:00:00.000000000',\n",
+ " '2024-01-22T18:00:00.000000000', '2024-01-22T19:00:00.000000000',\n",
+ " '2024-01-22T20:00:00.000000000', '2024-01-22T21:00:00.000000000',\n",
+ " '2024-01-22T22:00:00.000000000', '2024-01-22T23:00:00.000000000',\n",
+ " '2024-01-23T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAATsDFOq+87vBQwAAAAAAAwF' ... '///39GwHOKoYYiHkrAAAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=Load(Elec)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Load(Elec)',\n",
+ " 'marker': {'color': '#00CC96', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'Load(Elec)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-22T00:00:00.000000000', '2024-01-22T01:00:00.000000000',\n",
+ " '2024-01-22T02:00:00.000000000', '2024-01-22T03:00:00.000000000',\n",
+ " '2024-01-22T04:00:00.000000000', '2024-01-22T05:00:00.000000000',\n",
+ " '2024-01-22T06:00:00.000000000', '2024-01-22T07:00:00.000000000',\n",
+ " '2024-01-22T08:00:00.000000000', '2024-01-22T09:00:00.000000000',\n",
+ " '2024-01-22T10:00:00.000000000', '2024-01-22T11:00:00.000000000',\n",
+ " '2024-01-22T12:00:00.000000000', '2024-01-22T13:00:00.000000000',\n",
+ " '2024-01-22T14:00:00.000000000', '2024-01-22T15:00:00.000000000',\n",
+ " '2024-01-22T16:00:00.000000000', '2024-01-22T17:00:00.000000000',\n",
+ " '2024-01-22T18:00:00.000000000', '2024-01-22T19:00:00.000000000',\n",
+ " '2024-01-22T20:00:00.000000000', '2024-01-22T21:00:00.000000000',\n",
+ " '2024-01-22T22:00:00.000000000', '2024-01-22T23:00:00.000000000',\n",
+ " '2024-01-23T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAATkDFOq+87vBQQAAAAAAAwF' ... '///39GQHOKoYYiHkpAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'bargap': 0,\n",
+ " 'bargroupgap': 0,\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Electricity (flow_rate)'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'time'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "fs.statistics.plot.balance('Electricity')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "11",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T13:46:22.102836Z",
+ "start_time": "2025-12-13T13:46:22.085158Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Efficiency range: 33.8% - 41.9%\n",
+ "Total cost: 182.96 €\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Verify efficiency varies with load\n",
+ "fuel = fs.solution['GasEngine(Fuel)|flow_rate']\n",
+ "elec = fs.solution['GasEngine(Elec)|flow_rate']\n",
+ "efficiency = elec / fuel\n",
+ "\n",
+ "print(f'Efficiency range: {float(efficiency.min()):.1%} - {float(efficiency.max()):.1%}')\n",
+ "print(f'Total cost: {fs.solution[\"costs\"].item():.2f} €')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12",
+ "metadata": {},
+ "source": [
+ "## Key Points\n",
+ "\n",
+ "**Syntax:**\n",
+ "```python\n",
+ "fx.PiecewiseConversion({\n",
+ " 'Input': fx.Piecewise([fx.Piece(start=a, end=b), ...]),\n",
+ " 'Output': fx.Piecewise([fx.Piece(start=x, end=y), ...]),\n",
+ "})\n",
+ "```\n",
+ "\n",
+ "**Rules:**\n",
+ "- All flows must have the **same number of segments**\n",
+ "- Segments typically **connect** (end of N = start of N+1)\n",
+ "- Efficiency = output / input at each point\n",
+ "\n",
+ "**Time-varying:** Pass arrays instead of scalars to model changing limits (e.g., temperature derating).\n",
+ "\n",
+ "**Next:** See [06c-piecewise-effects](06c-piecewise-effects.ipynb) for non-linear investment costs."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.12.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/06c-piecewise-effects.ipynb b/docs/notebooks/06c-piecewise-effects.ipynb
new file mode 100644
index 000000000..8f44b9cf2
--- /dev/null
+++ b/docs/notebooks/06c-piecewise-effects.ipynb
@@ -0,0 +1,4544 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": [
+ "# Piecewise Effects\n",
+ "\n",
+ "Model **non-linear investment costs** with economies of scale and discrete size tiers.\n",
+ "\n",
+ "This notebook demonstrates:\n",
+ "- **PiecewiseEffects**: Non-linear cost functions for investments\n",
+ "- **Gaps between pieces**: Representing discrete size tiers (unavailable sizes)\n",
+ "- How the optimizer selects from available size options"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "1",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T09:37:05.842524Z",
+ "start_time": "2025-12-13T09:37:01.302972Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "flixopt.config.CONFIG"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2",
+ "metadata": {},
+ "source": [
+ "## The Problem: Discrete Size Tiers\n",
+ "\n",
+ "Real equipment often comes in **discrete sizes** with gaps between options:\n",
+ "\n",
+ "| Tier | Size Range | Cost per kWh | Notes |\n",
+ "|------|------------|--------------|-------|\n",
+ "| Small | 50-100 kWh | 0.20 €/kWh | Residential units |\n",
+ "| *Gap* | 100-200 kWh | *unavailable* | No products in this range |\n",
+ "| Medium | 200-400 kWh | 0.12 €/kWh | Commercial units |\n",
+ "| *Gap* | 400-500 kWh | *unavailable* | No products in this range |\n",
+ "| Large | 500-800 kWh | 0.06 €/kWh | Industrial units |\n",
+ "\n",
+ "The gaps represent size ranges where no products are available from manufacturers."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "## Define the Cost Curve with Gaps\n",
+ "\n",
+ "Each piece defines a size tier. Gaps between pieces are **forbidden** zones."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "4",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T09:37:05.891430Z",
+ "start_time": "2025-12-13T09:37:05.883541Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Available size tiers:\n",
+ " Small: 50-100 kWh at 0.20 €/kWh\n",
+ " Medium: 200-400 kWh at 0.12 €/kWh\n",
+ " Large: 500-800 kWh at 0.06 €/kWh\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Piecewise costs with gaps between tiers\n",
+ "# Cost values are CUMULATIVE at each breakpoint\n",
+ "piecewise_costs = fx.PiecewiseEffects(\n",
+ " piecewise_origin=fx.Piecewise(\n",
+ " [\n",
+ " fx.Piece(start=50, end=100), # Small tier: 50-100 kWh\n",
+ " fx.Piece(start=200, end=400), # Medium tier: 200-400 kWh (gap: 100-200)\n",
+ " fx.Piece(start=500, end=800), # Large tier: 500-800 kWh (gap: 400-500)\n",
+ " ]\n",
+ " ),\n",
+ " piecewise_shares={\n",
+ " 'costs': fx.Piecewise(\n",
+ " [\n",
+ " fx.Piece(start=10, end=20), # 50kWh=10€, 100kWh=20€ → 0.20 €/kWh\n",
+ " fx.Piece(start=24, end=48), # 200kWh=24€, 400kWh=48€ → 0.12 €/kWh\n",
+ " fx.Piece(start=30, end=48), # 500kWh=30€, 800kWh=48€ → 0.06 €/kWh\n",
+ " ]\n",
+ " )\n",
+ " },\n",
+ ")\n",
+ "\n",
+ "print('Available size tiers:')\n",
+ "print(' Small: 50-100 kWh at 0.20 €/kWh')\n",
+ "print(' Medium: 200-400 kWh at 0.12 €/kWh')\n",
+ "print(' Large: 500-800 kWh at 0.06 €/kWh')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "8",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T09:37:05.919885Z",
+ "start_time": "2025-12-13T09:37:05.915254Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "timesteps = pd.date_range('2024-01-01', periods=24, freq='h')\n",
+ "\n",
+ "# Electricity price: cheap at night, expensive during day\n",
+ "elec_price = np.array(\n",
+ " [\n",
+ " 0.05,\n",
+ " 0.05,\n",
+ " 0.05,\n",
+ " 0.05,\n",
+ " 0.05,\n",
+ " 0.05, # 00-06: night (cheap)\n",
+ " 0.15,\n",
+ " 0.20,\n",
+ " 0.25,\n",
+ " 0.25,\n",
+ " 0.20,\n",
+ " 0.15, # 06-12: morning\n",
+ " 0.15,\n",
+ " 0.20,\n",
+ " 0.25,\n",
+ " 0.30,\n",
+ " 0.30,\n",
+ " 0.25, # 12-18: afternoon (expensive)\n",
+ " 0.20,\n",
+ " 0.15,\n",
+ " 0.10,\n",
+ " 0.08,\n",
+ " 0.06,\n",
+ " 0.05, # 18-24: evening\n",
+ " ]\n",
+ ")\n",
+ "\n",
+ "demand = np.full(24, 100) # 100 kW constant demand"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "120b3beb025756ef",
+ "metadata": {},
+ "source": [
+ "## Simple Arbitrage Scenario\n",
+ "\n",
+ "A battery arbitrages between cheap night and expensive day electricity."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "## Build and Solve the Model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "10",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T09:37:07.048599Z",
+ "start_time": "2025-12-13T09:37:05.935256Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Running HiGHS 1.12.0 (git hash: 755a8e0): Copyright (c) 2025 HiGHS under MIT licence terms\n",
+ "MIP linopy-problem-wrb0ote0 has 437 rows; 294 cols; 1098 nonzeros; 54 integer variables (54 binary)\n",
+ "Coefficient ranges:\n",
+ " Matrix [1e-05, 8e+02]\n",
+ " Cost [1e+00, 1e+00]\n",
+ " Bound [1e+00, 8e+02]\n",
+ " RHS [1e+00, 1e+00]\n",
+ "Presolving model\n",
+ "253 rows, 159 cols, 588 nonzeros 0s\n",
+ "152 rows, 107 cols, 504 nonzeros 0s\n",
+ "151 rows, 107 cols, 500 nonzeros 0s\n",
+ "Presolve reductions: rows 151(-286); columns 107(-187); nonzeros 500(-598) \n",
+ "\n",
+ "Solving MIP model with:\n",
+ " 151 rows\n",
+ " 107 cols (52 binary, 0 integer, 0 implied int., 55 continuous, 0 domain fixed)\n",
+ " 500 nonzeros\n",
+ "\n",
+ "Src: B => Branching; C => Central rounding; F => Feasibility pump; H => Heuristic;\n",
+ " I => Shifting; J => Feasibility jump; L => Sub-MIP; P => Empty MIP; R => Randomized rounding;\n",
+ " S => Solve LP; T => Evaluate node; U => Unbounded; X => User solution; Y => HiGHS solution;\n",
+ " Z => ZI Round; l => Trivial lower; p => Trivial point; u => Trivial upper; z => Trivial zero\n",
+ "\n",
+ " Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work \n",
+ "Src Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time\n",
+ "\n",
+ " J 0 0 0 0.00% -inf 359 Large 0 0 0 0 0.0s\n",
+ " 0 0 0 0.00% 248.9944598 359 30.64% 0 0 0 62 0.0s\n",
+ " L 0 0 0 0.00% 248.9944598 248.9944598 0.00% 32 11 0 73 0.0s\n",
+ " 1 0 1 100.00% 248.9944598 248.9944598 0.00% 32 11 0 82 0.0s\n",
+ "\n",
+ "Solving report\n",
+ " Model linopy-problem-wrb0ote0\n",
+ " Status Optimal\n",
+ " Primal bound 248.994459834\n",
+ " Dual bound 248.994459834\n",
+ " Gap 0% (tolerance: 1%)\n",
+ " P-D integral 0.00660979209716\n",
+ " Solution status feasible\n",
+ " 248.994459834 (objective)\n",
+ " 0 (bound viol.)\n",
+ " 6.43929354283e-15 (int. viol.)\n",
+ " 0 (row viol.)\n",
+ " Timing 0.03\n",
+ " Max sub-MIP depth 1\n",
+ " Nodes 1\n",
+ " Repair LPs 0\n",
+ " LP iterations 82\n",
+ " 0 (strong br.)\n",
+ " 11 (separation)\n",
+ " 9 (heuristics)\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "FlowSystem\n",
+ "==========\n",
+ "Timesteps: 24 (Hour) [2024-01-01 to 2024-01-01]\n",
+ "Periods: None\n",
+ "Scenarios: None\n",
+ "Status: ✓\n",
+ "\n",
+ "Components (3 items)\n",
+ "--------------------\n",
+ " * Battery\n",
+ " * Demand\n",
+ " * Grid\n",
+ "\n",
+ "Buses (1 item)\n",
+ "--------------\n",
+ " * Elec\n",
+ "\n",
+ "Effects (2 items)\n",
+ "-----------------\n",
+ " * costs\n",
+ " * Penalty\n",
+ "\n",
+ "Flows (4 items)\n",
+ "---------------\n",
+ " * Battery(charge)\n",
+ " * Battery(discharge)\n",
+ " * Demand(Elec)\n",
+ " * Grid(Elec)"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "fs = fx.FlowSystem(timesteps)\n",
+ "\n",
+ "fs.add_elements(\n",
+ " fx.Bus('Elec'),\n",
+ " fx.Effect('costs', '€', is_standard=True, is_objective=True),\n",
+ " # Grid with time-varying price\n",
+ " fx.Source('Grid', outputs=[fx.Flow('Elec', bus='Elec', size=500, effects_per_flow_hour=elec_price)]),\n",
+ " # Battery with PIECEWISE investment cost (discrete tiers)\n",
+ " fx.Storage(\n",
+ " 'Battery',\n",
+ " charging=fx.Flow('charge', bus='Elec', size=fx.InvestParameters(maximum_size=400)),\n",
+ " discharging=fx.Flow('discharge', bus='Elec', size=fx.InvestParameters(maximum_size=400)),\n",
+ " capacity_in_flow_hours=fx.InvestParameters(\n",
+ " piecewise_effects_of_investment=piecewise_costs,\n",
+ " minimum_size=0,\n",
+ " maximum_size=800,\n",
+ " ),\n",
+ " eta_charge=0.95,\n",
+ " eta_discharge=0.95,\n",
+ " initial_charge_state=0,\n",
+ " ),\n",
+ " fx.Sink('Demand', inputs=[fx.Flow('Elec', bus='Elec', size=1, fixed_relative_profile=demand)]),\n",
+ ")\n",
+ "\n",
+ "fs.optimize(fx.solvers.HighsSolver())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "be5dc58de4a3c809",
+ "metadata": {},
+ "source": [
+ "## Visualize the Cost Curve\n",
+ "\n",
+ "The\n",
+ "plot\n",
+ "shows\n",
+ "the\n",
+ "three\n",
+ "discrete\n",
+ "tiers\n",
+ "with gaps between them."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "c734d019ece6c6fe",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T09:37:07.301104Z",
+ "start_time": "2025-12-13T09:37:07.136275Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " \n",
+ " \n",
+ " "
+ ]
+ },
+ "jetTransient": {
+ "display_id": null
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "jetTransient": {
+ "display_id": null
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "piecewise_costs.plot(title='Battery Investment Cost (Discrete Tiers)')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "39b4ec726d6d43c1",
+ "metadata": {},
+ "source": "## Results: Which Tier Was Selected?"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "12",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T09:37:08.189381Z",
+ "start_time": "2025-12-13T09:37:08.142348Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Selected tier: Large (500-800 kWh)\n",
+ "Battery size: 800 kWh\n",
+ "Total cost: 249.0 €\n"
+ ]
+ }
+ ],
+ "source": [
+ "battery_size = fs.solution['Battery|size'].item()\n",
+ "total_cost = fs.solution['costs'].item()\n",
+ "\n",
+ "# Determine which tier was selected\n",
+ "if battery_size < 1:\n",
+ " tier = 'None'\n",
+ "elif battery_size <= 100:\n",
+ " tier = 'Small (50-100 kWh)'\n",
+ "elif battery_size <= 400:\n",
+ " tier = 'Medium (200-400 kWh)'\n",
+ "else:\n",
+ " tier = 'Large (500-800 kWh)'\n",
+ "\n",
+ "print(f'Selected tier: {tier}')\n",
+ "print(f'Battery size: {battery_size:.0f} kWh')\n",
+ "print(f'Total cost: {total_cost:.1f} €')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "13",
+ "metadata": {},
+ "source": [
+ "## Storage Operation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "14",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T09:37:08.407306Z",
+ "start_time": "2025-12-13T09:37:08.263634Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ ""
+ ],
+ "text/plain": [
+ "PlotResult(data= Size: 1kB\n",
+ "Dimensions: (time: 25)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 200B 2024-01-01 ... 2024-01-02\n",
+ "Data variables:\n",
+ " Grid(Elec) (time) float64 200B -100.0 -100.0 -100.0 ... -100.0 nan\n",
+ " Battery(discharge) (time) float64 200B -0.0 -7.267e-14 ... 1.243e-13 nan\n",
+ " Battery(charge) (time) float64 200B 0.0 4.425e-14 ... -1.385e-13 nan\n",
+ " Demand(Elec) (time) float64 200B 100.0 100.0 100.0 ... 100.0 nan, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=Grid(Elec)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Grid(Elec)',\n",
+ " 'marker': {'color': '#636EFA', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'Grid(Elec)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-01T00:00:00.000000000', '2024-01-01T01:00:00.000000000',\n",
+ " '2024-01-01T02:00:00.000000000', '2024-01-01T03:00:00.000000000',\n",
+ " '2024-01-01T04:00:00.000000000', '2024-01-01T05:00:00.000000000',\n",
+ " '2024-01-01T06:00:00.000000000', '2024-01-01T07:00:00.000000000',\n",
+ " '2024-01-01T08:00:00.000000000', '2024-01-01T09:00:00.000000000',\n",
+ " '2024-01-01T10:00:00.000000000', '2024-01-01T11:00:00.000000000',\n",
+ " '2024-01-01T12:00:00.000000000', '2024-01-01T13:00:00.000000000',\n",
+ " '2024-01-01T14:00:00.000000000', '2024-01-01T15:00:00.000000000',\n",
+ " '2024-01-01T16:00:00.000000000', '2024-01-01T17:00:00.000000000',\n",
+ " '2024-01-01T18:00:00.000000000', '2024-01-01T19:00:00.000000000',\n",
+ " '2024-01-01T20:00:00.000000000', '2024-01-01T21:00:00.000000000',\n",
+ " '2024-01-01T22:00:00.000000000', '2024-01-01T23:00:00.000000000',\n",
+ " '2024-01-02T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAWcD+//////9YwPD//////1' ... '////9YwP///////1jAAAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=Battery(discharge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Battery(discharge)',\n",
+ " 'marker': {'color': '#EF553B', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'Battery(discharge)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-01T00:00:00.000000000', '2024-01-01T01:00:00.000000000',\n",
+ " '2024-01-01T02:00:00.000000000', '2024-01-01T03:00:00.000000000',\n",
+ " '2024-01-01T04:00:00.000000000', '2024-01-01T05:00:00.000000000',\n",
+ " '2024-01-01T06:00:00.000000000', '2024-01-01T07:00:00.000000000',\n",
+ " '2024-01-01T08:00:00.000000000', '2024-01-01T09:00:00.000000000',\n",
+ " '2024-01-01T10:00:00.000000000', '2024-01-01T11:00:00.000000000',\n",
+ " '2024-01-01T12:00:00.000000000', '2024-01-01T13:00:00.000000000',\n",
+ " '2024-01-01T14:00:00.000000000', '2024-01-01T15:00:00.000000000',\n",
+ " '2024-01-01T16:00:00.000000000', '2024-01-01T17:00:00.000000000',\n",
+ " '2024-01-01T18:00:00.000000000', '2024-01-01T19:00:00.000000000',\n",
+ " '2024-01-01T20:00:00.000000000', '2024-01-01T21:00:00.000000000',\n",
+ " '2024-01-01T22:00:00.000000000', '2024-01-01T23:00:00.000000000',\n",
+ " '2024-01-02T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAIDs2puCeHQ0vfWsvI9KlT' ... 'zLt3xBPcy3fMu3fEE9AAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=Battery(charge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Battery(charge)',\n",
+ " 'marker': {'color': '#EF553B', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'Battery(charge)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-01T00:00:00.000000000', '2024-01-01T01:00:00.000000000',\n",
+ " '2024-01-01T02:00:00.000000000', '2024-01-01T03:00:00.000000000',\n",
+ " '2024-01-01T04:00:00.000000000', '2024-01-01T05:00:00.000000000',\n",
+ " '2024-01-01T06:00:00.000000000', '2024-01-01T07:00:00.000000000',\n",
+ " '2024-01-01T08:00:00.000000000', '2024-01-01T09:00:00.000000000',\n",
+ " '2024-01-01T10:00:00.000000000', '2024-01-01T11:00:00.000000000',\n",
+ " '2024-01-01T12:00:00.000000000', '2024-01-01T13:00:00.000000000',\n",
+ " '2024-01-01T14:00:00.000000000', '2024-01-01T15:00:00.000000000',\n",
+ " '2024-01-01T16:00:00.000000000', '2024-01-01T17:00:00.000000000',\n",
+ " '2024-01-01T18:00:00.000000000', '2024-01-01T19:00:00.000000000',\n",
+ " '2024-01-01T20:00:00.000000000', '2024-01-01T21:00:00.000000000',\n",
+ " '2024-01-01T22:00:00.000000000', '2024-01-01T23:00:00.000000000',\n",
+ " '2024-01-02T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAADZtTcF8egoPT0r76NS5V' ... 'zLt3xDvcy3fMu3fEO9AAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=Demand(Elec)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Demand(Elec)',\n",
+ " 'marker': {'color': '#00CC96', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'Demand(Elec)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-01T00:00:00.000000000', '2024-01-01T01:00:00.000000000',\n",
+ " '2024-01-01T02:00:00.000000000', '2024-01-01T03:00:00.000000000',\n",
+ " '2024-01-01T04:00:00.000000000', '2024-01-01T05:00:00.000000000',\n",
+ " '2024-01-01T06:00:00.000000000', '2024-01-01T07:00:00.000000000',\n",
+ " '2024-01-01T08:00:00.000000000', '2024-01-01T09:00:00.000000000',\n",
+ " '2024-01-01T10:00:00.000000000', '2024-01-01T11:00:00.000000000',\n",
+ " '2024-01-01T12:00:00.000000000', '2024-01-01T13:00:00.000000000',\n",
+ " '2024-01-01T14:00:00.000000000', '2024-01-01T15:00:00.000000000',\n",
+ " '2024-01-01T16:00:00.000000000', '2024-01-01T17:00:00.000000000',\n",
+ " '2024-01-01T18:00:00.000000000', '2024-01-01T19:00:00.000000000',\n",
+ " '2024-01-01T20:00:00.000000000', '2024-01-01T21:00:00.000000000',\n",
+ " '2024-01-01T22:00:00.000000000', '2024-01-01T23:00:00.000000000',\n",
+ " '2024-01-02T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAWUAAAAAAAABZQAAAAAAAAF' ... 'AAAABZQAAAAAAAAFlAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'bargap': 0,\n",
+ " 'bargroupgap': 0,\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Elec (flow_rate)'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'time'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "fs.statistics.plot.balance('Elec')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15",
+ "metadata": {},
+ "source": [
+ "## Best Practice: PiecewiseEffects with Gaps\n",
+ "\n",
+ "```python\n",
+ "fx.PiecewiseEffects(\n",
+ " piecewise_origin=fx.Piecewise([\n",
+ " fx.Piece(start=50, end=100), # Tier 1\n",
+ " fx.Piece(start=200, end=400), # Tier 2 (gap: 100-200 forbidden)\n",
+ " ]),\n",
+ " piecewise_shares={\n",
+ " 'costs': fx.Piecewise([\n",
+ " fx.Piece(start=10, end=20), # Cumulative cost at tier 1 boundaries\n",
+ " fx.Piece(start=24, end=48), # Cumulative cost at tier 2 boundaries\n",
+ " ])\n",
+ " },\n",
+ ")\n",
+ "```\n",
+ "\n",
+ "**Key points:**\n",
+ "- Gaps between pieces = forbidden size ranges\n",
+ "- Cost values are **cumulative** at each boundary\n",
+ "- Use when equipment comes in discrete tiers"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "16",
+ "metadata": {},
+ "source": "## Previous: Piecewise Conversion\n\nSee **[06b-piecewise-conversion](06b-piecewise-conversion.ipynb)** for modeling minimum load constraints with `PiecewiseConversion` + `StatusParameters`."
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.12.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/07-scenarios-and-periods.ipynb b/docs/notebooks/07-scenarios-and-periods.ipynb
new file mode 100644
index 000000000..27dfdce70
--- /dev/null
+++ b/docs/notebooks/07-scenarios-and-periods.ipynb
@@ -0,0 +1,485 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": "# Scenarios\n\nMulti-year planning with uncertain demand scenarios.\n\nThis notebook introduces:\n\n- **Periods**: Multiple planning years with different conditions\n- **Scenarios**: Uncertain futures (mild vs. harsh winter)\n- **Scenario weights**: Probability-weighted optimization\n- **Multi-dimensional data**: Parameters that vary by time, period, and scenario"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "## The Planning Problem\n",
+ "\n",
+ "We're designing a heating system with:\n",
+ "\n",
+ "- **3 periods** (years): 2024, 2025, 2026 - gas prices expected to rise\n",
+ "- **2 scenarios**: \"Mild Winter\" (60% probability) and \"Harsh Winter\" (40% probability)\n",
+ "- **Investment decision**: Size of CHP unit (made once, works across all futures)\n",
+ "\n",
+ "The optimizer finds the investment that minimizes **expected cost** across all scenarios."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4",
+ "metadata": {},
+ "source": [
+ "## Define Dimensions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Time horizon: one representative winter week\n",
+ "timesteps = pd.date_range('2024-01-15', periods=168, freq='h') # 7 days\n",
+ "\n",
+ "# Planning periods (years)\n",
+ "periods = pd.Index([2024, 2025, 2026], name='period')\n",
+ "\n",
+ "# Scenarios with probabilities\n",
+ "scenarios = pd.Index(['Mild Winter', 'Harsh Winter'], name='scenario')\n",
+ "scenario_weights = np.array([0.6, 0.4]) # 60% mild, 40% harsh\n",
+ "\n",
+ "print(f'Time dimension: {len(timesteps)} hours')\n",
+ "print(f'Periods: {list(periods)}')\n",
+ "print(f'Scenarios: {list(scenarios)}')\n",
+ "print(f'Scenario weights: {dict(zip(scenarios, scenario_weights, strict=False))}')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6",
+ "metadata": {},
+ "source": [
+ "## Create Scenario-Dependent Demand Profiles\n",
+ "\n",
+ "Heat demand differs significantly between mild and harsh winters:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "hours = np.arange(168)\n",
+ "hour_of_day = hours % 24\n",
+ "\n",
+ "# Base daily pattern (kW): higher in morning/evening\n",
+ "daily_pattern = np.select(\n",
+ " [\n",
+ " (hour_of_day >= 6) & (hour_of_day < 9), # Morning peak\n",
+ " (hour_of_day >= 9) & (hour_of_day < 17), # Daytime\n",
+ " (hour_of_day >= 17) & (hour_of_day < 22), # Evening peak\n",
+ " ],\n",
+ " [180, 120, 160],\n",
+ " default=100, # Night\n",
+ ").astype(float)\n",
+ "\n",
+ "# Add random variation\n",
+ "np.random.seed(42)\n",
+ "noise = np.random.normal(0, 10, len(timesteps))\n",
+ "\n",
+ "# Mild winter: lower demand\n",
+ "mild_demand = daily_pattern * 0.8 + noise\n",
+ "mild_demand = np.clip(mild_demand, 60, 200)\n",
+ "\n",
+ "# Harsh winter: higher demand\n",
+ "harsh_demand = daily_pattern * 1.3 + noise * 1.5\n",
+ "harsh_demand = np.clip(harsh_demand, 100, 280)\n",
+ "\n",
+ "# Create DataFrame with scenario columns (flixopt uses column names to match scenarios)\n",
+ "heat_demand = pd.DataFrame(\n",
+ " {\n",
+ " 'Mild Winter': mild_demand,\n",
+ " 'Harsh Winter': harsh_demand,\n",
+ " },\n",
+ " index=timesteps,\n",
+ ")\n",
+ "\n",
+ "print(f'Mild winter demand: {mild_demand.min():.0f} - {mild_demand.max():.0f} kW')\n",
+ "print(f'Harsh winter demand: {harsh_demand.min():.0f} - {harsh_demand.max():.0f} kW')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Visualize demand scenarios with plotly\n",
+ "fig = px.line(\n",
+ " heat_demand.iloc[:48],\n",
+ " title='Heat Demand by Scenario (First 2 Days)',\n",
+ " labels={'index': 'Time', 'value': 'kW', 'variable': 'Scenario'},\n",
+ ")\n",
+ "fig.update_traces(mode='lines')\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "## Create Period-Dependent Prices\n",
+ "\n",
+ "Energy prices change across planning years:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Gas prices by period (€/kWh) - expected to rise\n",
+ "gas_prices = np.array([0.06, 0.08, 0.10]) # 2024, 2025, 2026\n",
+ "\n",
+ "# Electricity sell prices by period (€/kWh) - CHP revenue\n",
+ "elec_prices = np.array([0.28, 0.34, 0.43]) # Rising with gas\n",
+ "\n",
+ "print('Gas prices by period:')\n",
+ "for period, price in zip(periods, gas_prices, strict=False):\n",
+ " print(f' {period}: {price:.2f} €/kWh')\n",
+ "\n",
+ "print('\\nElectricity sell prices by period:')\n",
+ "for period, price in zip(periods, elec_prices, strict=False):\n",
+ " print(f' {period}: {price:.2f} €/kWh')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11",
+ "metadata": {},
+ "source": [
+ "## Build the Flow System\n",
+ "\n",
+ "Initialize with all dimensions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "12",
+ "metadata": {},
+ "outputs": [],
+ "source": "flow_system = fx.FlowSystem(\n timesteps=timesteps,\n periods=periods,\n scenarios=scenarios,\n scenario_weights=scenario_weights,\n)\nflow_system.add_carriers(\n fx.Carrier('gas', '#3498db', 'kW'),\n fx.Carrier('electricity', '#f1c40f', 'kW'),\n fx.Carrier('heat', '#e74c3c', 'kW'),\n)\n\nprint(flow_system)"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "13",
+ "metadata": {},
+ "source": [
+ "## Add Components"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "14",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.add_elements(\n",
+ " # === Buses ===\n",
+ " fx.Bus('Electricity', carrier='electricity'),\n",
+ " fx.Bus('Heat', carrier='heat'),\n",
+ " fx.Bus('Gas', carrier='gas'),\n",
+ " # === Effects ===\n",
+ " fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n",
+ " # === Gas Supply (price varies by period) ===\n",
+ " fx.Source(\n",
+ " 'GasGrid',\n",
+ " outputs=[\n",
+ " fx.Flow(\n",
+ " 'Gas',\n",
+ " bus='Gas',\n",
+ " size=1000,\n",
+ " effects_per_flow_hour=gas_prices, # Array = varies by period\n",
+ " )\n",
+ " ],\n",
+ " ),\n",
+ " # === CHP Unit (investment decision) ===\n",
+ " fx.linear_converters.CHP(\n",
+ " 'CHP',\n",
+ " electrical_efficiency=0.35,\n",
+ " thermal_efficiency=0.50,\n",
+ " electrical_flow=fx.Flow(\n",
+ " 'P_el',\n",
+ " bus='Electricity',\n",
+ " # Investment optimization: find optimal CHP size\n",
+ " size=fx.InvestParameters(\n",
+ " minimum_size=0,\n",
+ " maximum_size=100,\n",
+ " effects_of_investment_per_size={'costs': 50}, # 50 €/kW annualized\n",
+ " ),\n",
+ " relative_minimum=0.3,\n",
+ " ),\n",
+ " thermal_flow=fx.Flow('Q_th', bus='Heat'),\n",
+ " fuel_flow=fx.Flow('Q_fuel', bus='Gas'),\n",
+ " ),\n",
+ " # === Gas Boiler (existing backup) ===\n",
+ " fx.linear_converters.Boiler(\n",
+ " 'Boiler',\n",
+ " thermal_efficiency=0.90,\n",
+ " thermal_flow=fx.Flow('Q_th', bus='Heat', size=500),\n",
+ " fuel_flow=fx.Flow('Q_fuel', bus='Gas'),\n",
+ " ),\n",
+ " # === Electricity Sales (revenue varies by period) ===\n",
+ " fx.Sink(\n",
+ " 'ElecSales',\n",
+ " inputs=[\n",
+ " fx.Flow(\n",
+ " 'P_el',\n",
+ " bus='Electricity',\n",
+ " size=100,\n",
+ " effects_per_flow_hour=-elec_prices, # Negative = revenue\n",
+ " )\n",
+ " ],\n",
+ " ),\n",
+ " # === Heat Demand (varies by scenario) ===\n",
+ " fx.Sink(\n",
+ " 'HeatDemand',\n",
+ " inputs=[\n",
+ " fx.Flow(\n",
+ " 'Q_th',\n",
+ " bus='Heat',\n",
+ " size=1,\n",
+ " fixed_relative_profile=heat_demand, # DataFrame with scenario columns\n",
+ " )\n",
+ " ],\n",
+ " ),\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15",
+ "metadata": {},
+ "source": [
+ "## Run Optimization"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "16",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.optimize(fx.solvers.HighsSolver(mip_gap=0.01));"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17",
+ "metadata": {},
+ "source": [
+ "## Analyze Results\n",
+ "\n",
+ "### Optimal Investment Decision"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "chp_size = flow_system.statistics.sizes['CHP(P_el)']\n",
+ "total_cost = flow_system.solution['costs']\n",
+ "\n",
+ "print('=== Investment Decision ===')\n",
+ "print(f'Optimal CHP size: {chp_size.round(1).to_pandas()} kW electrical')\n",
+ "print(f'Thermal capacity: {(chp_size * 0.50 / 0.35).round(1).to_pandas()} kW')\n",
+ "print(f'\\nExpected total cost: {total_cost.round(2).to_pandas()} €')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19",
+ "metadata": {},
+ "source": [
+ "### Heat Balance by Scenario\n",
+ "\n",
+ "See how the system operates differently in each scenario:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "20",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.balance('Heat')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "21",
+ "metadata": {},
+ "source": [
+ "### CHP Operation Patterns"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "22",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.heatmap('CHP(Q_th)')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23",
+ "metadata": {},
+ "source": [
+ "### Multi-Dimensional Data Access\n",
+ "\n",
+ "Results include all dimensions (time, period, scenario):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# View dimensions\n",
+ "flow_rates = flow_system.statistics.flow_rates\n",
+ "print('Flow rates dimensions:', dict(flow_rates.sizes))\n",
+ "\n",
+ "# Plot flow rates\n",
+ "flow_system.statistics.plot.flows()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "25",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# CHP operation in harsh winter vs mild winter\n",
+ "chp_heat = flow_rates['CHP(Q_th)']\n",
+ "\n",
+ "print('CHP Heat Output Statistics:')\n",
+ "for scenario in scenarios:\n",
+ " scenario_data = chp_heat.sel(scenario=scenario)\n",
+ " print(f'\\n{scenario}:')\n",
+ " for period in periods:\n",
+ " period_data = scenario_data.sel(period=period)\n",
+ " print(f' {period}: avg={period_data.mean().item():.1f} kW, max={period_data.max().item():.1f} kW')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "26",
+ "metadata": {},
+ "source": [
+ "## Sensitivity: What if Only Mild Winter?\n",
+ "\n",
+ "Compare optimal CHP size if we only planned for mild winters:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "27",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Select only the mild winter scenario\n",
+ "fs_mild = flow_system.transform.sel(scenario='Mild Winter')\n",
+ "fs_mild.optimize(fx.solvers.HighsSolver(mip_gap=0.01))\n",
+ "\n",
+ "chp_size_mild = fs_mild.statistics.sizes['CHP(P_el)']\n",
+ "\n",
+ "print('=== Comparison ===')\n",
+ "print(f'CHP size (both scenarios): {chp_size.max(\"scenario\").round(2).values} kW')\n",
+ "print(f'CHP size (mild only): {chp_size_mild.round(2).values} kW')\n",
+ "print(f'\\nPlanning for uncertainty adds {(chp_size - chp_size_mild).round(2).values} kW capacity')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "28",
+ "metadata": {},
+ "source": [
+ "### Energy Flow Sankey\n",
+ "\n",
+ "A Sankey diagram visualizes the total energy flows through the system:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "29",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_system.statistics.plot.sankey.flows()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "30",
+ "metadata": {},
+ "source": "## Key Concepts\n\n### Multi-Dimensional FlowSystem\n\n```python\nflow_system = fx.FlowSystem(\n timesteps=timesteps, # Time dimension\n periods=periods, # Planning periods (years)\n scenarios=scenarios, # Uncertain futures\n scenario_weights=weights, # Probabilities\n)\n```\n\n### Dimension-Varying Parameters\n\n| Data Shape | Meaning |\n|------------|----------|\n| Scalar | Same for all time/period/scenario |\n| Array (n_periods,) | Varies by period |\n| Array (n_scenarios,) | Varies by scenario |\n| DataFrame with columns | Columns match scenario names |\n| Full array (time, period, scenario) | Full specification |\n\n### Scenario Optimization\n\nThe optimizer minimizes **expected cost**:\n$$\\min \\sum_s w_s \\cdot \\text{Cost}_s$$\n\nwhere $w_s$ is the scenario weight (probability).\n\n### Selection Methods\n\n```python\n# Select specific scenario\nfs_mild = flow_system.transform.sel(scenario='Mild Winter')\n\n# Select specific period\nfs_2025 = flow_system.transform.sel(period=2025)\n\n# Select time range\nfs_day1 = flow_system.transform.sel(time=slice('2024-01-15', '2024-01-16'))\n```\n\n## Summary\n\nYou learned how to:\n\n- Define **multiple periods** for multi-year planning\n- Create **scenarios** for uncertain futures\n- Use **scenario weights** for probability-weighted optimization\n- Pass **dimension-varying parameters** (arrays and DataFrames)\n- **Select** specific scenarios or periods for analysis\n\n### Next Steps\n\n- **[08a-Aggregation](08a-aggregation.ipynb)**: Speed up large problems with resampling and clustering\n- **[08b-Rolling Horizon](08b-rolling-horizon.ipynb)**: Decompose large problems into sequential time segments"
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.11.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/08a-aggregation.ipynb b/docs/notebooks/08a-aggregation.ipynb
new file mode 100644
index 000000000..f3d5d1686
--- /dev/null
+++ b/docs/notebooks/08a-aggregation.ipynb
@@ -0,0 +1,499 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": "# Aggregation\n\nSpeed up large problems with time series aggregation techniques.\n\nThis notebook introduces:\n\n- **Resampling**: Reduce time resolution (e.g., hourly → 4-hourly)\n- **Clustering**: Identify typical periods (e.g., 8 representative days)\n- **Two-stage optimization**: Size with reduced data, dispatch at full resolution\n- **Speed vs. accuracy trade-offs**: When to use each technique"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import timeit\n",
+ "\n",
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "import xarray as xr\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": "## Load Time Series Data\n\nWe use real-world district heating data at 15-minute resolution (one month):"
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Load time series data (15-min resolution)\n",
+ "data = pd.read_csv('../../examples/resources/Zeitreihen2020.csv', index_col=0, parse_dates=True).sort_index()\n",
+ "data = data['2020-01-01':'2020-01-31 23:45:00'] # One month\n",
+ "data.index.name = 'time' # Rename index for consistency\n",
+ "\n",
+ "timesteps = data.index\n",
+ "\n",
+ "# Extract profiles\n",
+ "electricity_demand = data['P_Netz/MW'].to_numpy()\n",
+ "heat_demand = data['Q_Netz/MW'].to_numpy()\n",
+ "electricity_price = data['Strompr.€/MWh'].to_numpy()\n",
+ "gas_price = data['Gaspr.€/MWh'].to_numpy()\n",
+ "\n",
+ "print(f'Timesteps: {len(timesteps)} ({len(timesteps) / 96:.0f} days at 15-min resolution)')\n",
+ "print(f'Heat demand: {heat_demand.min():.1f} - {heat_demand.max():.1f} MW')\n",
+ "print(f'Electricity price: {electricity_price.min():.1f} - {electricity_price.max():.1f} €/MWh')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Visualize first week\n",
+ "profiles = xr.Dataset(\n",
+ " {\n",
+ " 'Heat Demand [MW]': xr.DataArray(heat_demand[:672], dims=['time'], coords={'time': timesteps[:672]}),\n",
+ " 'Electricity Price [€/MWh]': xr.DataArray(\n",
+ " electricity_price[:672], dims=['time'], coords={'time': timesteps[:672]}\n",
+ " ),\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "df = profiles.to_dataframe().reset_index().melt(id_vars='time', var_name='variable', value_name='value')\n",
+ "fig = px.line(df, x='time', y='value', facet_col='variable', height=300)\n",
+ "fig.update_yaxes(matches=None, showticklabels=True)\n",
+ "fig.for_each_annotation(lambda a: a.update(text=a.text.split('=')[-1]))\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7",
+ "metadata": {},
+ "source": [
+ "## Build the Base FlowSystem\n",
+ "\n",
+ "A typical district heating system with investment decisions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def build_system(timesteps, heat_demand, electricity_demand, electricity_price, gas_price):\n",
+ " \"\"\"Build a district heating system with CHP, boiler, and storage (with investment options).\"\"\"\n",
+ " fs = fx.FlowSystem(timesteps)\n",
+ "\n",
+ " fs.add_elements(\n",
+ " # Buses\n",
+ " fx.Bus('Electricity'),\n",
+ " fx.Bus('Heat'),\n",
+ " fx.Bus('Gas'),\n",
+ " fx.Bus('Coal'),\n",
+ " # Effects\n",
+ " fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n",
+ " fx.Effect('CO2', 'kg', 'CO2 Emissions'),\n",
+ " # CHP with investment optimization\n",
+ " fx.linear_converters.CHP(\n",
+ " 'CHP',\n",
+ " thermal_efficiency=0.58,\n",
+ " electrical_efficiency=0.22,\n",
+ " electrical_flow=fx.Flow('P_el', bus='Electricity', size=200),\n",
+ " thermal_flow=fx.Flow(\n",
+ " 'Q_th',\n",
+ " bus='Heat',\n",
+ " size=fx.InvestParameters(\n",
+ " minimum_size=100,\n",
+ " maximum_size=300,\n",
+ " effects_of_investment_per_size={'costs': 10},\n",
+ " ),\n",
+ " relative_minimum=0.3,\n",
+ " ),\n",
+ " fuel_flow=fx.Flow('Q_fu', bus='Coal'),\n",
+ " ),\n",
+ " # Gas Boiler with investment optimization\n",
+ " fx.linear_converters.Boiler(\n",
+ " 'Boiler',\n",
+ " thermal_efficiency=0.85,\n",
+ " thermal_flow=fx.Flow(\n",
+ " 'Q_th',\n",
+ " bus='Heat',\n",
+ " size=fx.InvestParameters(\n",
+ " minimum_size=0,\n",
+ " maximum_size=150,\n",
+ " effects_of_investment_per_size={'costs': 5},\n",
+ " ),\n",
+ " relative_minimum=0.1,\n",
+ " ),\n",
+ " fuel_flow=fx.Flow('Q_fu', bus='Gas'),\n",
+ " ),\n",
+ " # Thermal Storage with investment optimization\n",
+ " fx.Storage(\n",
+ " 'Storage',\n",
+ " capacity_in_flow_hours=fx.InvestParameters(\n",
+ " minimum_size=0,\n",
+ " maximum_size=1000,\n",
+ " effects_of_investment_per_size={'costs': 0.5},\n",
+ " ),\n",
+ " initial_charge_state=0,\n",
+ " eta_charge=1,\n",
+ " eta_discharge=1,\n",
+ " relative_loss_per_hour=0.001,\n",
+ " charging=fx.Flow('Charge', size=137, bus='Heat'),\n",
+ " discharging=fx.Flow('Discharge', size=158, bus='Heat'),\n",
+ " ),\n",
+ " # Fuel sources\n",
+ " fx.Source(\n",
+ " 'GasGrid',\n",
+ " outputs=[fx.Flow('Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={'costs': gas_price, 'CO2': 0.3})],\n",
+ " ),\n",
+ " fx.Source(\n",
+ " 'CoalSupply',\n",
+ " outputs=[fx.Flow('Q_Coal', bus='Coal', size=1000, effects_per_flow_hour={'costs': 4.6, 'CO2': 0.3})],\n",
+ " ),\n",
+ " # Electricity grid connection\n",
+ " fx.Source(\n",
+ " 'GridBuy',\n",
+ " outputs=[\n",
+ " fx.Flow(\n",
+ " 'P_el',\n",
+ " bus='Electricity',\n",
+ " size=1000,\n",
+ " effects_per_flow_hour={'costs': electricity_price + 0.5, 'CO2': 0.3},\n",
+ " )\n",
+ " ],\n",
+ " ),\n",
+ " fx.Sink(\n",
+ " 'GridSell',\n",
+ " inputs=[fx.Flow('P_el', bus='Electricity', size=1000, effects_per_flow_hour=-(electricity_price - 0.5))],\n",
+ " ),\n",
+ " # Demands\n",
+ " fx.Sink('HeatDemand', inputs=[fx.Flow('Q_th', bus='Heat', size=1, fixed_relative_profile=heat_demand)]),\n",
+ " fx.Sink(\n",
+ " 'ElecDemand', inputs=[fx.Flow('P_el', bus='Electricity', size=1, fixed_relative_profile=electricity_demand)]\n",
+ " ),\n",
+ " )\n",
+ "\n",
+ " return fs\n",
+ "\n",
+ "\n",
+ "flow_system = build_system(timesteps, heat_demand, electricity_demand, electricity_price, gas_price)\n",
+ "print(f'System: {len(timesteps)} timesteps')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "## Technique 1: Resampling\n",
+ "\n",
+ "Reduce time resolution to speed up optimization:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "solver = fx.solvers.HighsSolver(mip_gap=0.01)\n",
+ "\n",
+ "# Resample from 1h to 4h resolution\n",
+ "fs_resampled = flow_system.transform.resample('4h')\n",
+ "\n",
+ "print(f'Original: {len(flow_system.timesteps)} timesteps')\n",
+ "print(f'Resampled: {len(fs_resampled.timesteps)} timesteps')\n",
+ "print(f'Reduction: {(1 - len(fs_resampled.timesteps) / len(flow_system.timesteps)) * 100:.0f}%')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "11",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Optimize resampled system\n",
+ "start = timeit.default_timer()\n",
+ "fs_resampled.optimize(solver)\n",
+ "time_resampled = timeit.default_timer() - start\n",
+ "\n",
+ "print(f'\\nResampled optimization: {time_resampled:.2f} seconds')\n",
+ "print(f'Cost: {fs_resampled.solution[\"costs\"].item():.2f} €')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12",
+ "metadata": {},
+ "source": [
+ "## Technique 2: Two-Stage Optimization\n",
+ "\n",
+ "1. **Stage 1**: Size components with resampled data (fast)\n",
+ "2. **Stage 2**: Fix sizes and optimize dispatch at full resolution"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "13",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Stage 1: Sizing with resampled data\n",
+ "start = timeit.default_timer()\n",
+ "fs_sizing = flow_system.transform.resample('4h')\n",
+ "fs_sizing.optimize(solver)\n",
+ "time_stage1 = timeit.default_timer() - start\n",
+ "\n",
+ "print('=== Stage 1: Sizing ===')\n",
+ "print(f'Time: {time_stage1:.2f} seconds')\n",
+ "print('\\nOptimized sizes:')\n",
+ "for name, size in fs_sizing.statistics.sizes.items():\n",
+ " print(f' {name}: {float(size.item()):.1f}')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "14",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Stage 2: Dispatch at full resolution with fixed sizes\n",
+ "start = timeit.default_timer()\n",
+ "fs_dispatch = flow_system.transform.fix_sizes(fs_sizing.statistics.sizes)\n",
+ "fs_dispatch.optimize(solver)\n",
+ "time_stage2 = timeit.default_timer() - start\n",
+ "\n",
+ "print('=== Stage 2: Dispatch ===')\n",
+ "print(f'Time: {time_stage2:.2f} seconds')\n",
+ "print(f'Cost: {fs_dispatch.solution[\"costs\"].item():.2f} €')\n",
+ "print(f'\\nTotal two-stage time: {time_stage1 + time_stage2:.2f} seconds')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15",
+ "metadata": {},
+ "source": [
+ "## Technique 3: Full Optimization (Baseline)\n",
+ "\n",
+ "For comparison, solve the full problem:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "16",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "start = timeit.default_timer()\n",
+ "fs_full = flow_system.copy()\n",
+ "fs_full.optimize(solver)\n",
+ "time_full = timeit.default_timer() - start\n",
+ "\n",
+ "print('=== Full Optimization ===')\n",
+ "print(f'Time: {time_full:.2f} seconds')\n",
+ "print(f'Cost: {fs_full.solution[\"costs\"].item():.2f} €')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17",
+ "metadata": {},
+ "source": [
+ "## Compare Results"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Collect results\n",
+ "results = {\n",
+ " 'Full (baseline)': {\n",
+ " 'Time [s]': time_full,\n",
+ " 'Cost [€]': fs_full.solution['costs'].item(),\n",
+ " 'CHP Size [MW]': fs_full.statistics.sizes['CHP(Q_th)'].item(),\n",
+ " 'Boiler Size [MW]': fs_full.statistics.sizes['Boiler(Q_th)'].item(),\n",
+ " 'Storage Size [MWh]': fs_full.statistics.sizes['Storage'].item(),\n",
+ " },\n",
+ " 'Resampled (4h)': {\n",
+ " 'Time [s]': time_resampled,\n",
+ " 'Cost [€]': fs_resampled.solution['costs'].item(),\n",
+ " 'CHP Size [MW]': fs_resampled.statistics.sizes['CHP(Q_th)'].item(),\n",
+ " 'Boiler Size [MW]': fs_resampled.statistics.sizes['Boiler(Q_th)'].item(),\n",
+ " 'Storage Size [MWh]': fs_resampled.statistics.sizes['Storage'].item(),\n",
+ " },\n",
+ " 'Two-Stage': {\n",
+ " 'Time [s]': time_stage1 + time_stage2,\n",
+ " 'Cost [€]': fs_dispatch.solution['costs'].item(),\n",
+ " 'CHP Size [MW]': fs_dispatch.statistics.sizes['CHP(Q_th)'].item(),\n",
+ " 'Boiler Size [MW]': fs_dispatch.statistics.sizes['Boiler(Q_th)'].item(),\n",
+ " 'Storage Size [MWh]': fs_dispatch.statistics.sizes['Storage'].item(),\n",
+ " },\n",
+ "}\n",
+ "\n",
+ "comparison = pd.DataFrame(results).T\n",
+ "\n",
+ "# Add relative metrics\n",
+ "baseline_cost = comparison.loc['Full (baseline)', 'Cost [€]']\n",
+ "baseline_time = comparison.loc['Full (baseline)', 'Time [s]']\n",
+ "comparison['Cost Gap [%]'] = ((comparison['Cost [€]'] - baseline_cost) / baseline_cost * 100).round(2)\n",
+ "comparison['Speedup'] = (baseline_time / comparison['Time [s]']).round(1)\n",
+ "\n",
+ "comparison.style.format(\n",
+ " {\n",
+ " 'Time [s]': '{:.2f}',\n",
+ " 'Cost [€]': '{:,.0f}',\n",
+ " 'CHP Size [MW]': '{:.1f}',\n",
+ " 'Boiler Size [MW]': '{:.1f}',\n",
+ " 'Storage Size [MWh]': '{:.0f}',\n",
+ " 'Cost Gap [%]': '{:.2f}',\n",
+ " 'Speedup': '{:.1f}x',\n",
+ " }\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19",
+ "metadata": {},
+ "source": [
+ "## Visual Comparison: Heat Balance"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "20",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Full optimization heat balance\n",
+ "fs_full.statistics.plot.balance('Heat')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "21",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Two-stage optimization heat balance\n",
+ "fs_dispatch.statistics.plot.balance('Heat')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "22",
+ "metadata": {},
+ "source": [
+ "### Energy Flow Sankey (Full Optimization)\n",
+ "\n",
+ "A Sankey diagram visualizes the total energy flows:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "23",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fs_full.statistics.plot.sankey.flows()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "24",
+ "metadata": {},
+ "source": [
+ "## When to Use Each Technique\n",
+ "\n",
+ "| Technique | Best For | Trade-off |\n",
+ "|-----------|----------|------------|\n",
+ "| **Full optimization** | Final results, small problems | Slowest, most accurate |\n",
+ "| **Resampling** | Quick screening, trend analysis | Fast, loses temporal detail |\n",
+ "| **Two-stage** | Investment decisions, large problems | Good balance of speed and accuracy |\n",
+ "| **Clustering** | Preserves extreme periods | Requires `tsam` package |\n",
+ "\n",
+ "### Resampling Options\n",
+ "\n",
+ "```python\n",
+ "# Different resolutions\n",
+ "fs_2h = flow_system.transform.resample('2h') # 2-hourly\n",
+ "fs_4h = flow_system.transform.resample('4h') # 4-hourly\n",
+ "fs_daily = flow_system.transform.resample('1D') # Daily\n",
+ "\n",
+ "# Different aggregation methods\n",
+ "fs_mean = flow_system.transform.resample('4h', method='mean') # Default\n",
+ "fs_max = flow_system.transform.resample('4h', method='max') # Preserve peaks\n",
+ "```\n",
+ "\n",
+ "### Two-Stage Workflow\n",
+ "\n",
+ "```python\n",
+ "# Stage 1: Sizing\n",
+ "fs_sizing = flow_system.transform.resample('4h')\n",
+ "fs_sizing.optimize(solver)\n",
+ "\n",
+ "# Stage 2: Dispatch\n",
+ "fs_dispatch = flow_system.transform.fix_sizes(fs_sizing.statistics.sizes)\n",
+ "fs_dispatch.optimize(solver)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25",
+ "metadata": {},
+ "source": "## Summary\n\nYou learned how to:\n\n- Use **`transform.resample()`** to reduce time resolution\n- Apply **two-stage optimization** for large investment problems\n- Use **`transform.fix_sizes()`** to lock in investment decisions\n- Compare **speed vs. accuracy** trade-offs\n\n### Key Takeaways\n\n1. **Start fast**: Use resampling for initial exploration\n2. **Iterate**: Refine with two-stage optimization\n3. **Validate**: Run full optimization for final results\n4. **Monitor**: Check cost gaps to ensure acceptable accuracy\n\n### Next Steps\n\n- **[08b-Rolling Horizon](08b-rolling-horizon.ipynb)**: For operational problems without investment decisions, decompose time into sequential segments\n\n### Further Reading\n\n- For clustering with typical periods, see `transform.cluster()` (requires `tsam` package)\n- For time selection, see `transform.sel()` and `transform.isel()`"
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.11.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/08b-rolling-horizon.ipynb b/docs/notebooks/08b-rolling-horizon.ipynb
new file mode 100644
index 000000000..67edf9aa8
--- /dev/null
+++ b/docs/notebooks/08b-rolling-horizon.ipynb
@@ -0,0 +1,4876 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": "# Rolling Horizon\n\nSolve large operational problems by decomposing the time horizon into sequential segments.\n\nThis notebook introduces:\n\n- **Rolling horizon optimization**: Divide time into overlapping segments\n- **State transfer**: Pass storage states and flow history between segments\n- **When to use**: Memory limits, operational planning with limited foresight\n\nWe use a realistic district heating system with CHP, boiler, and storage to demonstrate the approach."
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "2",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:01:44.873555Z",
+ "start_time": "2025-12-13T19:01:40.936227Z"
+ }
+ },
+ "source": [
+ "import timeit\n",
+ "\n",
+ "import pandas as pd\n",
+ "import plotly.express as px\n",
+ "import plotly.graph_objects as go\n",
+ "import xarray as xr\n",
+ "from plotly.subplots import make_subplots\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "flixopt.config.CONFIG"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 1
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": "## Load Time Series Data\n\nWe use real-world district heating data at 15-minute resolution (two weeks):"
+ },
+ {
+ "cell_type": "code",
+ "id": "4",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:01:45.078418Z",
+ "start_time": "2025-12-13T19:01:44.973157Z"
+ }
+ },
+ "source": "# Load time series data (15-min resolution)\ndata = pd.read_csv('../../examples/resources/Zeitreihen2020.csv', index_col=0, parse_dates=True).sort_index()\ndata = data['2020-01-01':'2020-01-14 23:45:00'] # Two weeks\ndata.index.name = 'time' # Rename index for consistency\n\ntimesteps = data.index\n\n# Extract profiles\nelectricity_demand = data['P_Netz/MW'].to_numpy()\nheat_demand = data['Q_Netz/MW'].to_numpy()\nelectricity_price = data['Strompr.€/MWh'].to_numpy()\ngas_price = data['Gaspr.€/MWh'].to_numpy()\n\nprint(f'Timesteps: {len(timesteps)} ({len(timesteps) / 96:.0f} days at 15-min resolution)')\nprint(f'Heat demand: {heat_demand.min():.1f} - {heat_demand.max():.1f} MW')\nprint(f'Electricity price: {electricity_price.min():.1f} - {electricity_price.max():.1f} €/MWh')",
+ "outputs": [],
+ "execution_count": null
+ },
+ {
+ "cell_type": "code",
+ "id": "5",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:01:45.204918Z",
+ "start_time": "2025-12-13T19:01:45.183230Z"
+ }
+ },
+ "source": [
+ "def build_system(timesteps, heat_demand, electricity_demand, electricity_price, gas_price):\n",
+ " \"\"\"Build a district heating system with CHP, boiler, and storage.\"\"\"\n",
+ " fs = fx.FlowSystem(timesteps)\n",
+ "\n",
+ " # Effects\n",
+ "\n",
+ " # Buses\n",
+ " fs.add_elements(\n",
+ " fx.Bus('Electricity'),\n",
+ " fx.Bus('Heat'),\n",
+ " fx.Bus('Gas'),\n",
+ " fx.Bus('Coal'),\n",
+ " fx.Effect('costs', '€', 'Total Costs', is_standard=True, is_objective=True),\n",
+ " fx.Effect('CO2', 'kg', 'CO2 Emissions'),\n",
+ " fx.linear_converters.CHP(\n",
+ " 'CHP',\n",
+ " thermal_efficiency=0.58,\n",
+ " electrical_efficiency=0.22,\n",
+ " status_parameters=fx.StatusParameters(effects_per_startup=24000),\n",
+ " electrical_flow=fx.Flow('P_el', bus='Electricity', size=200),\n",
+ " thermal_flow=fx.Flow('Q_th', bus='Heat', size=200),\n",
+ " fuel_flow=fx.Flow('Q_fu', bus='Coal', size=288, relative_minimum=87 / 288, previous_flow_rate=100),\n",
+ " ),\n",
+ " fx.linear_converters.Boiler(\n",
+ " 'Boiler',\n",
+ " thermal_efficiency=0.85,\n",
+ " thermal_flow=fx.Flow('Q_th', bus='Heat'),\n",
+ " fuel_flow=fx.Flow(\n",
+ " 'Q_fu',\n",
+ " bus='Gas',\n",
+ " size=95,\n",
+ " relative_minimum=12 / 95,\n",
+ " previous_flow_rate=20,\n",
+ " status_parameters=fx.StatusParameters(effects_per_startup=1000),\n",
+ " ),\n",
+ " ),\n",
+ " fx.Storage(\n",
+ " 'Storage',\n",
+ " capacity_in_flow_hours=684,\n",
+ " initial_charge_state=137,\n",
+ " minimal_final_charge_state=137,\n",
+ " maximal_final_charge_state=158,\n",
+ " eta_charge=1,\n",
+ " eta_discharge=1,\n",
+ " relative_loss_per_hour=0.001,\n",
+ " prevent_simultaneous_charge_and_discharge=True,\n",
+ " charging=fx.Flow('Charge', size=137, bus='Heat'),\n",
+ " discharging=fx.Flow('Discharge', size=158, bus='Heat'),\n",
+ " ),\n",
+ " fx.Source(\n",
+ " 'GasGrid',\n",
+ " outputs=[fx.Flow('Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={'costs': gas_price, 'CO2': 0.3})],\n",
+ " ),\n",
+ " fx.Source(\n",
+ " 'CoalSupply',\n",
+ " outputs=[fx.Flow('Q_Coal', bus='Coal', size=1000, effects_per_flow_hour={'costs': 4.6, 'CO2': 0.3})],\n",
+ " ),\n",
+ " fx.Source(\n",
+ " 'GridBuy',\n",
+ " outputs=[\n",
+ " fx.Flow(\n",
+ " 'P_el',\n",
+ " bus='Electricity',\n",
+ " size=1000,\n",
+ " effects_per_flow_hour={'costs': electricity_price + 0.5, 'CO2': 0.3},\n",
+ " )\n",
+ " ],\n",
+ " ),\n",
+ " fx.Sink(\n",
+ " 'GridSell',\n",
+ " inputs=[fx.Flow('P_el', bus='Electricity', size=1000, effects_per_flow_hour=-(electricity_price - 0.5))],\n",
+ " ),\n",
+ " fx.Sink('HeatDemand', inputs=[fx.Flow('Q_th', bus='Heat', size=1, fixed_relative_profile=heat_demand)]),\n",
+ " fx.Sink(\n",
+ " 'ElecDemand', inputs=[fx.Flow('P_el', bus='Electricity', size=1, fixed_relative_profile=electricity_demand)]\n",
+ " ),\n",
+ " )\n",
+ "\n",
+ " return fs\n",
+ "\n",
+ "\n",
+ "flow_system = build_system(timesteps, heat_demand, electricity_demand, electricity_price, gas_price)\n",
+ "print(f'System: {len(timesteps)} timesteps')"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "System: 1344 timesteps\n"
+ ]
+ }
+ ],
+ "execution_count": 3
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6",
+ "metadata": {},
+ "source": [
+ "## Full Optimization (Baseline)\n",
+ "\n",
+ "First, solve the full problem as a baseline:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "7",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:02:56.367270Z",
+ "start_time": "2025-12-13T19:01:45.486690Z"
+ }
+ },
+ "source": [
+ "solver = fx.solvers.HighsSolver()\n",
+ "\n",
+ "start = timeit.default_timer()\n",
+ "fs_full = flow_system.copy()\n",
+ "fs_full.optimize(solver)\n",
+ "time_full = timeit.default_timer() - start\n",
+ "\n",
+ "print(f'Full optimization: {time_full:.2f} seconds')\n",
+ "print(f'Cost: {fs_full.solution[\"costs\"].item():,.0f} €')"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[2m2025-12-13 20:01:45.496\u001b[0m \u001b[33mWARNING \u001b[0m │ FlowSystem is not connected_and_transformed. Connecting and transforming data now.\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Writing constraints.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 74/74 [00:00<00:00, 152.15it/s]\n",
+ "Writing continuous variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 56/56 [00:00<00:00, 378.63it/s]\n",
+ "Writing binary variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 11/11 [00:00<00:00, 335.35it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Running HiGHS 1.12.0 (git hash: 755a8e0): Copyright (c) 2025 HiGHS under MIT licence terms\n",
+ "MIP linopy-problem-lqmu4n6b has 53792 rows; 51102 cols; 168036 nonzeros; 14784 integer variables (14784 binary)\n",
+ "Coefficient ranges:\n",
+ " Matrix [1e-05, 2e+04]\n",
+ " Cost [1e+00, 1e+00]\n",
+ " Bound [1e+00, 1e+03]\n",
+ " RHS [1e-05, 2e+02]\n",
+ "WARNING: Problem has some excessively small row bounds\n",
+ "Presolving model\n",
+ "29568 rows, 24168 cols, 76581 nonzeros 0s\n",
+ "25322 rows, 18981 cols, 72124 nonzeros 0s\n",
+ "24294 rows, 18173 cols, 69378 nonzeros 0s\n",
+ "Presolve reductions: rows 24294(-29498); columns 18173(-32929); nonzeros 69378(-98658) \n",
+ "\n",
+ "Solving MIP model with:\n",
+ " 24294 rows\n",
+ " 18173 cols (13978 binary, 0 integer, 0 implied int., 4195 continuous, 0 domain fixed)\n",
+ " 69378 nonzeros\n",
+ "\n",
+ "Src: B => Branching; C => Central rounding; F => Feasibility pump; H => Heuristic;\n",
+ " I => Shifting; J => Feasibility jump; L => Sub-MIP; P => Empty MIP; R => Randomized rounding;\n",
+ " S => Solve LP; T => Evaluate node; U => Unbounded; X => User solution; Y => HiGHS solution;\n",
+ " Z => ZI Round; l => Trivial lower; p => Trivial point; u => Trivial upper; z => Trivial zero\n",
+ "\n",
+ " Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work \n",
+ "Src Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time\n",
+ "\n",
+ " 0 0 0 0.00% 1030491.76973 inf inf 0 0 0 0 0.5s\n",
+ " 0 0 0 0.00% 1540084.733469 inf inf 0 0 0 9609 1.0s\n",
+ " C 0 0 0 0.00% 1540120.790012 1662404.794591 7.36% 10533 2312 0 13317 3.5s\n",
+ " 0 0 0 0.00% 1540129.709896 1662404.794591 7.36% 10993 2339 0 14788 8.6s\n",
+ " 0 0 0 0.00% 1540135.248328 1662404.794591 7.35% 10379 2761 0 17251 13.8s\n",
+ " 0 0 0 0.00% 1540139.906068 1662404.794591 7.35% 10794 2560 0 18445 19.2s\n",
+ " 0 0 0 0.00% 1540142.257624 1662404.794591 7.35% 10289 2512 0 19682 24.4s\n",
+ " L 0 0 0 0.00% 1540142.371061 1591562.49514 3.23% 10213 2579 0 19922 31.8s\n",
+ " L 0 0 0 0.00% 1540142.371061 1540200.034522 0.00% 10213 2579 0 23652 68.0s\n",
+ " 1 0 1 100.00% 1540142.371061 1540200.034522 0.00% 9659 2579 0 31373 68.0s\n",
+ "\n",
+ "Solving report\n",
+ " Model linopy-problem-lqmu4n6b\n",
+ " Status Optimal\n",
+ " Primal bound 1540200.03452\n",
+ " Dual bound 1540142.37106\n",
+ " Gap 0.00374% (tolerance: 1%)\n",
+ " P-D integral 3.24885572414\n",
+ " Solution status feasible\n",
+ " 1540200.03452 (objective)\n",
+ " 0 (bound viol.)\n",
+ " 6.93576991062e-07 (int. viol.)\n",
+ " 0 (row viol.)\n",
+ " Timing 68.01\n",
+ " Max sub-MIP depth 2\n",
+ " Nodes 1\n",
+ " Repair LPs 0\n",
+ " LP iterations 31373\n",
+ " 0 (strong br.)\n",
+ " 10313 (separation)\n",
+ " 11450 (heuristics)\n",
+ "Full optimization: 70.87 seconds\n",
+ "Cost: 1,540,200 €\n"
+ ]
+ }
+ ],
+ "execution_count": 4
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8",
+ "metadata": {},
+ "source": "## Rolling Horizon Optimization\n\nThe `optimize.rolling_horizon()` method divides the time horizon into segments that are solved sequentially:\n\n```\nFull horizon: |---------- 1344 timesteps (14 days) ----------|\n \nSegment 1: |==== 192 (2 days) ====|-- overlap --|\nSegment 2: |==== 192 (2 days) ====|-- overlap --|\nSegment 3: |==== 192 (2 days) ====|-- overlap --|\n... \n```\n\nKey parameters:\n- **horizon**: Timesteps per segment (excluding overlap)\n- **overlap**: Additional lookahead timesteps (improves storage optimization)\n- **nr_of_previous_values**: Flow history transferred between segments"
+ },
+ {
+ "cell_type": "code",
+ "id": "9",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:03:27.454194Z",
+ "start_time": "2025-12-13T19:02:56.525964Z"
+ }
+ },
+ "source": [
+ "start = timeit.default_timer()\n",
+ "fs_rolling = flow_system.copy()\n",
+ "segments = fs_rolling.optimize.rolling_horizon(\n",
+ " solver,\n",
+ " horizon=192, # 2-day segments (192 timesteps at 15-min resolution)\n",
+ " overlap=96, # 1-day lookahead\n",
+ ")\n",
+ "time_rolling = timeit.default_timer() - start\n",
+ "\n",
+ "print(f'Rolling horizon: {time_rolling:.2f} seconds ({len(segments)} segments)')\n",
+ "print(f'Cost: {fs_rolling.solution[\"costs\"].item():,.0f} €')"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Segment 1/7 (timesteps 0-288): 0%| | 0/7 [00:00, ?segment/s]"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Writing constraints.: 0%|\u001b[38;2;128;191;255m \u001b[0m| 0/74 [00:00, ?it/s]\u001b[A\n",
+ "Writing constraints.: 23%|\u001b[38;2;128;191;255m██▎ \u001b[0m| 17/74 [00:00<00:00, 166.25it/s]\u001b[A\n",
+ "Writing constraints.: 50%|\u001b[38;2;128;191;255m█████ \u001b[0m| 37/74 [00:00<00:00, 181.94it/s]\u001b[A\n",
+ "Writing constraints.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 74/74 [00:00<00:00, 202.91it/s]\u001b[A\n",
+ "\n",
+ "Writing continuous variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 56/56 [00:00<00:00, 1059.72it/s]\n",
+ "\n",
+ "Writing binary variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 11/11 [00:00<00:00, 891.77it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Segment 2/7 (timesteps 192-480): 14%|█▍ | 1/7 [00:04<00:27, 4.51s/segment]"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Writing constraints.: 0%|\u001b[38;2;128;191;255m \u001b[0m| 0/74 [00:00, ?it/s]\u001b[A\n",
+ "Writing constraints.: 7%|\u001b[38;2;128;191;255m▋ \u001b[0m| 5/74 [00:00<00:01, 47.55it/s]\u001b[A\n",
+ "Writing constraints.: 14%|\u001b[38;2;128;191;255m█▎ \u001b[0m| 10/74 [00:00<00:01, 48.71it/s]\u001b[A\n",
+ "Writing constraints.: 26%|\u001b[38;2;128;191;255m██▌ \u001b[0m| 19/74 [00:00<00:00, 60.79it/s]\u001b[A\n",
+ "Writing constraints.: 35%|\u001b[38;2;128;191;255m███▌ \u001b[0m| 26/74 [00:00<00:00, 59.71it/s]\u001b[A\n",
+ "Writing constraints.: 43%|\u001b[38;2;128;191;255m████▎ \u001b[0m| 32/74 [00:00<00:01, 40.03it/s]\u001b[A\n",
+ "Writing constraints.: 50%|\u001b[38;2;128;191;255m█████ \u001b[0m| 37/74 [00:00<00:00, 42.35it/s]\u001b[A\n",
+ "Writing constraints.: 58%|\u001b[38;2;128;191;255m█████▊ \u001b[0m| 43/74 [00:00<00:00, 45.99it/s]\u001b[A\n",
+ "Writing constraints.: 66%|\u001b[38;2;128;191;255m██████▌ \u001b[0m| 49/74 [00:01<00:00, 46.11it/s]\u001b[A\n",
+ "Writing constraints.: 74%|\u001b[38;2;128;191;255m███████▍ \u001b[0m| 55/74 [00:01<00:00, 49.51it/s]\u001b[A\n",
+ "Writing constraints.: 85%|\u001b[38;2;128;191;255m████████▌ \u001b[0m| 63/74 [00:01<00:00, 57.10it/s]\u001b[A\n",
+ "Writing constraints.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 74/74 [00:01<00:00, 50.24it/s]\u001b[A\n",
+ "\n",
+ "Writing continuous variables.: 0%|\u001b[38;2;128;191;255m \u001b[0m| 0/56 [00:00, ?it/s]\u001b[A\n",
+ "Writing continuous variables.: 41%|\u001b[38;2;128;191;255m████ \u001b[0m| 23/56 [00:00<00:00, 217.00it/s]\u001b[A\n",
+ "Writing continuous variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 56/56 [00:00<00:00, 224.88it/s]\u001b[A\n",
+ "\n",
+ "Writing binary variables.: 0%|\u001b[38;2;128;191;255m \u001b[0m| 0/11 [00:00, ?it/s]\u001b[A\n",
+ "Writing binary variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 11/11 [00:00<00:00, 103.98it/s]\u001b[A\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Segment 3/7 (timesteps 384-672): 29%|██▊ | 2/7 [00:13<00:36, 7.29s/segment]"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Writing constraints.: 0%|\u001b[38;2;128;191;255m \u001b[0m| 0/74 [00:00, ?it/s]\u001b[A\n",
+ "Writing constraints.: 8%|\u001b[38;2;128;191;255m▊ \u001b[0m| 6/74 [00:00<00:01, 58.48it/s]\u001b[A\n",
+ "Writing constraints.: 41%|\u001b[38;2;128;191;255m████ \u001b[0m| 30/74 [00:00<00:00, 159.93it/s]\u001b[A\n",
+ "Writing constraints.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 74/74 [00:00<00:00, 185.30it/s]\u001b[A\n",
+ "\n",
+ "Writing continuous variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 56/56 [00:00<00:00, 1017.73it/s]\n",
+ "\n",
+ "Writing binary variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 11/11 [00:00<00:00, 711.01it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Segment 4/7 (timesteps 576-864): 43%|████▎ | 3/7 [00:18<00:24, 6.13s/segment]"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Writing constraints.: 0%|\u001b[38;2;128;191;255m \u001b[0m| 0/74 [00:00, ?it/s]\u001b[A\n",
+ "Writing constraints.: 30%|\u001b[38;2;128;191;255m██▉ \u001b[0m| 22/74 [00:00<00:00, 219.10it/s]\u001b[A\n",
+ "Writing constraints.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 74/74 [00:00<00:00, 251.90it/s]\u001b[A\n",
+ "\n",
+ "Writing continuous variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 56/56 [00:00<00:00, 1221.70it/s]\n",
+ "\n",
+ "Writing binary variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 11/11 [00:00<00:00, 820.41it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Segment 5/7 (timesteps 768-1056): 57%|█████▋ | 4/7 [00:23<00:16, 5.54s/segment]"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Writing constraints.: 0%|\u001b[38;2;128;191;255m \u001b[0m| 0/74 [00:00, ?it/s]\u001b[A\n",
+ "Writing constraints.: 36%|\u001b[38;2;128;191;255m███▋ \u001b[0m| 27/74 [00:00<00:00, 266.29it/s]\u001b[A\n",
+ "Writing constraints.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 74/74 [00:00<00:00, 276.18it/s]\u001b[A\n",
+ "\n",
+ "Writing continuous variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 56/56 [00:00<00:00, 700.66it/s]\n",
+ "\n",
+ "Writing binary variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 11/11 [00:00<00:00, 463.70it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Segment 6/7 (timesteps 960-1248): 71%|███████▏ | 5/7 [00:25<00:08, 4.30s/segment]"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Writing constraints.: 0%|\u001b[38;2;128;191;255m \u001b[0m| 0/74 [00:00, ?it/s]\u001b[A\n",
+ "Writing constraints.: 38%|\u001b[38;2;128;191;255m███▊ \u001b[0m| 28/74 [00:00<00:00, 275.74it/s]\u001b[A\n",
+ "Writing constraints.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 74/74 [00:00<00:00, 277.64it/s]\u001b[A\n",
+ "\n",
+ "Writing continuous variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 56/56 [00:00<00:00, 882.35it/s]\n",
+ "\n",
+ "Writing binary variables.: 100%|\u001b[38;2;128;191;255m██████████\u001b[0m| 11/11 [00:00<00:00, 776.40it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Segment 7/7 (timesteps 1152-1344): 100%|██████████| 7/7 [00:30<00:00, 4.39s/segment]\n",
+ "Rolling horizon: 30.92 seconds (7 segments)\n",
+ "Cost: 1,540,676 €\n"
+ ]
+ }
+ ],
+ "execution_count": 5
+ },
+ {
+ "cell_type": "markdown",
+ "id": "10",
+ "metadata": {},
+ "source": [
+ "## Compare Results"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "11",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:03:27.650112Z",
+ "start_time": "2025-12-13T19:03:27.498367Z"
+ }
+ },
+ "source": [
+ "cost_full = fs_full.solution['costs'].item()\n",
+ "cost_rolling = fs_rolling.solution['costs'].item()\n",
+ "cost_gap = (cost_rolling - cost_full) / cost_full * 100\n",
+ "\n",
+ "results = pd.DataFrame(\n",
+ " {\n",
+ " 'Method': ['Full optimization', 'Rolling horizon'],\n",
+ " 'Time [s]': [time_full, time_rolling],\n",
+ " 'Cost [€]': [cost_full, cost_rolling],\n",
+ " 'Cost Gap [%]': [0.0, cost_gap],\n",
+ " }\n",
+ ").set_index('Method')\n",
+ "\n",
+ "results.style.format({'Time [s]': '{:.2f}', 'Cost [€]': '{:,.0f}', 'Cost Gap [%]': '{:.2f}'})"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ],
+ "text/html": [
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " | | \n",
+ " Time [s] | \n",
+ " Cost [€] | \n",
+ " Cost Gap [%] | \n",
+ "
\n",
+ " \n",
+ " | Method | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Full optimization | \n",
+ " 70.87 | \n",
+ " 1,540,200 | \n",
+ " 0.00 | \n",
+ "
\n",
+ " \n",
+ " | Rolling horizon | \n",
+ " 30.92 | \n",
+ " 1,540,676 | \n",
+ " 0.03 | \n",
+ "
\n",
+ " \n",
+ "
\n"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 6
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12",
+ "metadata": {},
+ "source": [
+ "## Visualize: Heat Balance Comparison"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "13",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:03:28.570509Z",
+ "start_time": "2025-12-13T19:03:27.661Z"
+ }
+ },
+ "source": [
+ "fs_full.statistics.plot.balance('Heat').figure.update_layout(title='Heat Balance (Full)')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " \n",
+ " \n",
+ " "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data",
+ "jetTransient": {
+ "display_id": null
+ }
+ },
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data",
+ "jetTransient": {
+ "display_id": null
+ }
+ }
+ ],
+ "execution_count": 7
+ },
+ {
+ "cell_type": "code",
+ "id": "14",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:03:29.994610Z",
+ "start_time": "2025-12-13T19:03:29.772272Z"
+ }
+ },
+ "source": [
+ "fs_rolling.statistics.plot.balance('Heat').figure.update_layout(title='Heat Balance (Rolling)')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data",
+ "jetTransient": {
+ "display_id": null
+ }
+ }
+ ],
+ "execution_count": 8
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15",
+ "metadata": {},
+ "source": [
+ "## Storage State Continuity\n",
+ "\n",
+ "Rolling horizon transfers storage charge states between segments to ensure continuity:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "16",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:03:30.194568Z",
+ "start_time": "2025-12-13T19:03:30.141045Z"
+ }
+ },
+ "source": [
+ "fig = make_subplots(\n",
+ " rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1, subplot_titles=['Full Optimization', 'Rolling Horizon']\n",
+ ")\n",
+ "\n",
+ "# Full optimization\n",
+ "charge_full = fs_full.solution['Storage|charge_state'].values[:-1] # Drop final value\n",
+ "fig.add_trace(go.Scatter(x=timesteps, y=charge_full, name='Full', line=dict(color='blue')), row=1, col=1)\n",
+ "\n",
+ "# Rolling horizon\n",
+ "charge_rolling = fs_rolling.solution['Storage|charge_state'].values[:-1]\n",
+ "fig.add_trace(go.Scatter(x=timesteps, y=charge_rolling, name='Rolling', line=dict(color='orange')), row=2, col=1)\n",
+ "\n",
+ "fig.update_yaxes(title_text='Charge State [MWh]', row=1, col=1)\n",
+ "fig.update_yaxes(title_text='Charge State [MWh]', row=2, col=1)\n",
+ "fig.update_layout(height=400, showlegend=False)\n",
+ "fig.show()"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data",
+ "jetTransient": {
+ "display_id": null
+ }
+ }
+ ],
+ "execution_count": 9
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17",
+ "metadata": {},
+ "source": [
+ "## Inspect Individual Segments\n",
+ "\n",
+ "The method returns the individual segment FlowSystems, which can be inspected:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "18",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:03:30.246423Z",
+ "start_time": "2025-12-13T19:03:30.228470Z"
+ }
+ },
+ "source": [
+ "print(f'Number of segments: {len(segments)}')\n",
+ "print()\n",
+ "for i, seg in enumerate(segments):\n",
+ " start_time = seg.timesteps[0]\n",
+ " end_time = seg.timesteps[-1]\n",
+ " cost = seg.solution['costs'].item()\n",
+ " print(\n",
+ " f'Segment {i + 1}: {start_time.strftime(\"%Y-%m-%d %H:%M\")} → {end_time.strftime(\"%Y-%m-%d %H:%M\")} | Cost: {cost:,.0f} €'\n",
+ " )"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Number of segments: 7\n",
+ "\n",
+ "Segment 1: 2020-01-01 00:00 → 2020-01-03 23:45 | Cost: 318,658 €\n",
+ "Segment 2: 2020-01-03 00:00 → 2020-01-05 23:45 | Cost: 275,399 €\n",
+ "Segment 3: 2020-01-05 00:00 → 2020-01-07 23:45 | Cost: 335,051 €\n",
+ "Segment 4: 2020-01-07 00:00 → 2020-01-09 23:45 | Cost: 406,345 €\n",
+ "Segment 5: 2020-01-09 00:00 → 2020-01-11 23:45 | Cost: 356,730 €\n",
+ "Segment 6: 2020-01-11 00:00 → 2020-01-13 23:45 | Cost: 275,606 €\n",
+ "Segment 7: 2020-01-13 00:00 → 2020-01-14 23:45 | Cost: 270,066 €\n"
+ ]
+ }
+ ],
+ "execution_count": 10
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19",
+ "metadata": {},
+ "source": "## Visualize Segment Overlaps\n\nUnderstanding how segments overlap is key to tuning rolling horizon. Let's visualize the flow rates from each segment including their overlap regions:"
+ },
+ {
+ "cell_type": "code",
+ "id": "20",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:03:30.528986Z",
+ "start_time": "2025-12-13T19:03:30.272546Z"
+ }
+ },
+ "source": [
+ "# Concatenate all segment solutions into one dataset (including overlaps)\n",
+ "ds = xr.concat([seg.solution for seg in segments], dim=pd.RangeIndex(len(segments), name='segment'), join='outer')\n",
+ "\n",
+ "# Plot CHP thermal flow across all segments - each segment as a separate line\n",
+ "px.line(\n",
+ " ds['Boiler(Q_th)|flow_rate'].to_pandas().T,\n",
+ " labels={'value': 'Boiler Thermal Output [MW]', 'index': 'Timestep'},\n",
+ ")"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data",
+ "jetTransient": {
+ "display_id": null
+ }
+ }
+ ],
+ "execution_count": 11
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T19:03:30.963250Z",
+ "start_time": "2025-12-13T19:03:30.651056Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "px.line(\n",
+ " ds['Storage|charge_state'].to_pandas().T,\n",
+ " labels={'value': 'Storage Charge State [MW]', 'index': 'Timestep'},\n",
+ ")"
+ ],
+ "id": "d7c660381f2190e0",
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data",
+ "jetTransient": {
+ "display_id": null
+ }
+ }
+ ],
+ "execution_count": 12
+ },
+ {
+ "cell_type": "markdown",
+ "id": "21",
+ "metadata": {},
+ "source": [
+ "## When to Use Rolling Horizon\n",
+ "\n",
+ "| Use Case | Recommendation |\n",
+ "|----------|----------------|\n",
+ "| **Memory limits** | Large problems that exceed available memory |\n",
+ "| **Operational planning** | When limited foresight is realistic |\n",
+ "| **Quick approximate solutions** | Faster than full optimization |\n",
+ "| **Investment decisions** | Use full optimization instead |\n",
+ "\n",
+ "### Limitations\n",
+ "\n",
+ "- **No investments**: `InvestParameters` are not supported (raises error)\n",
+ "- **Suboptimal storage**: Limited foresight may miss long-term storage opportunities\n",
+ "- **Global constraints**: `flow_hours_max` etc. cannot be enforced globally"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "22",
+ "metadata": {},
+ "source": [
+ "## API Reference\n",
+ "\n",
+ "```python\n",
+ "segments = flow_system.optimize.rolling_horizon(\n",
+ " solver, # Solver instance\n",
+ " horizon=192, # Timesteps per segment (e.g., 2 days at 15-min resolution)\n",
+ " overlap=48, # Additional lookahead timesteps (e.g., 12 hours)\n",
+ " nr_of_previous_values=1, # Flow history for uptime/downtime tracking\n",
+ ")\n",
+ "\n",
+ "# Combined solution on original FlowSystem\n",
+ "flow_system.solution['costs'].item()\n",
+ "\n",
+ "# Individual segment solutions\n",
+ "for seg in segments:\n",
+ " print(seg.solution['costs'].item())\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23",
+ "metadata": {},
+ "source": "## Summary\n\nYou learned how to:\n\n- Use **`optimize.rolling_horizon()`** to decompose large problems\n- Choose **horizon** and **overlap** parameters\n- Understand the **trade-offs** vs. full optimization\n\n### Key Takeaways\n\n1. **Rolling horizon** is useful for memory-limited or operational planning problems\n2. **Overlap** improves solution quality at the cost of computation time\n3. **Storage states** are automatically transferred between segments\n4. Use **full optimization** for investment decisions\n\n### Related Notebooks\n\n- **[08a-Aggregation](08a-aggregation.ipynb)**: For investment problems, use time series aggregation (resampling, clustering) instead"
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/09-plotting-and-data-access.ipynb b/docs/notebooks/09-plotting-and-data-access.ipynb
new file mode 100644
index 000000000..091d5f1d4
--- /dev/null
+++ b/docs/notebooks/09-plotting-and-data-access.ipynb
@@ -0,0 +1,7775 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": "# Plotting\n\nAccess optimization results and create visualizations.\n\nThis notebook covers:\n\n- Loading saved FlowSystems from NetCDF files\n- Accessing data (flow rates, sizes, effects, charge states)\n- Time series plots (balance, flows, storage)\n- Aggregated plots (sizes, effects, duration curves)\n- Heatmaps with time reshaping\n- Sankey diagrams\n- Topology visualization\n- Color customization and export"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "2",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:06.543191Z",
+ "start_time": "2025-12-13T14:13:00.434024Z"
+ }
+ },
+ "source": [
+ "from pathlib import Path\n",
+ "\n",
+ "import flixopt as fx\n",
+ "\n",
+ "fx.CONFIG.notebook()"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "flixopt.config.CONFIG"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 1
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "## Generate Example Data\n",
+ "\n",
+ "First, run the script that generates three example FlowSystems with solutions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "4",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:14.318678Z",
+ "start_time": "2025-12-13T14:13:06.637107Z"
+ }
+ },
+ "source": [
+ "# Run the generation script (only needed once, or to regenerate)\n",
+ "!python data/generate_example_systems.py"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Creating simple_system...\r\n",
+ " Optimizing...\r\n",
+ " Saving to /Users/felix/PycharmProjects/flixopt_719231/docs/notebooks/data/simple_system.nc4...\r\n",
+ " Done. Objective: 558.83\r\n",
+ "\r\n",
+ "Creating complex_system...\r\n",
+ " Optimizing...\r\n",
+ "HighsMipSolverData::transformNewIntegerFeasibleSolution tmpSolver.run();\r\n",
+ "HighsMipSolverData::transformNewIntegerFeasibleSolution tmpSolver.run();\r\n",
+ " Saving to /Users/felix/PycharmProjects/flixopt_719231/docs/notebooks/data/complex_system.nc4...\r\n",
+ " Done. Objective: 302.36\r\n",
+ "\r\n",
+ "Creating multiperiod_system...\r\n",
+ " Optimizing...\r\n",
+ " Saving to /Users/felix/PycharmProjects/flixopt_719231/docs/notebooks/data/multiperiod_system.nc4...\r\n",
+ " Done. Objective: 19472.48\r\n",
+ "\r\n",
+ "All systems generated successfully!\r\n"
+ ]
+ }
+ ],
+ "execution_count": 2
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "## 1. Loading Saved FlowSystems\n",
+ "\n",
+ "FlowSystems can be saved to and loaded from NetCDF files, preserving the full structure and solution:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "6",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:14.940793Z",
+ "start_time": "2025-12-13T14:13:14.377412Z"
+ }
+ },
+ "source": [
+ "DATA_DIR = Path('data')\n",
+ "\n",
+ "# Load the three example systems\n",
+ "simple = fx.FlowSystem.from_netcdf(DATA_DIR / 'simple_system.nc4')\n",
+ "complex_sys = fx.FlowSystem.from_netcdf(DATA_DIR / 'complex_system.nc4')\n",
+ "multiperiod = fx.FlowSystem.from_netcdf(DATA_DIR / 'multiperiod_system.nc4')\n",
+ "\n",
+ "print('Loaded systems:')\n",
+ "print(f' simple: {len(simple.components)} components, {len(simple.buses)} buses')\n",
+ "print(f' complex_sys: {len(complex_sys.components)} components, {len(complex_sys.buses)} buses')\n",
+ "print(f' multiperiod: {len(multiperiod.components)} components, dims={dict(multiperiod.solution.sizes)}')"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Loaded systems:\n",
+ " simple: 4 components, 2 buses\n",
+ " complex_sys: 9 components, 3 buses\n",
+ " multiperiod: 4 components, dims={'scenario': 2, 'period': 3, 'time': 49}\n"
+ ]
+ }
+ ],
+ "execution_count": 3
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7",
+ "metadata": {},
+ "source": "## 2. Quick Overview: Balance Plot\n\nLet's start with the most common visualization - a balance plot showing energy flows:"
+ },
+ {
+ "cell_type": "code",
+ "id": "8",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:15.234587Z",
+ "start_time": "2025-12-13T14:13:14.950674Z"
+ }
+ },
+ "source": [
+ "# Balance plot for the Heat bus - shows all inflows and outflows\n",
+ "simple.statistics.plot.balance('Heat')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 7kB\n",
+ "Dimensions: (time: 169)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 1kB 2024-01-15 ... 2024-...\n",
+ "Data variables:\n",
+ " Boiler(Heat) (time) float64 1kB -32.48 -29.31 ... -124.5 nan\n",
+ " ThermalStorage(Discharge) (time) float64 1kB -0.0 5.275e-13 ... nan\n",
+ " ThermalStorage(Charge) (time) float64 1kB 0.0 -3.748e-13 ... 100.0 nan\n",
+ " Office(Heat) (time) float64 1kB 32.48 29.31 ... 24.48 nan, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=Boiler(Heat)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Boiler(Heat)',\n",
+ " 'marker': {'color': '#EF553B', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'Boiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('5ZuWpeU9QMD3U8WNBU89wHjXQkqFnk' ... '////8zwPW5+Ef5Hl/AAAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=ThermalStorage(Discharge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage(Discharge)',\n",
+ " 'marker': {'color': '#00CC96', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage(Discharge)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAIAKPvjgg49iPby8nSEx72' ... 'AAAAAgvWP9SoFav2g9AAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=ThermalStorage(Charge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage(Charge)',\n",
+ " 'marker': {'color': '#00CC96', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage(Charge)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAAAUfPDBB19avby8nSEx72' ... 'AAAAAAANj//////1hAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=Office(Heat)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Office(Heat)',\n",
+ " 'marker': {'color': '#AB63FA', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'Office(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('5ZuWpeU9QEDMU8WNBU89QGDXQkqFnk' ... 'AAAAA0QK7n4h/lezhAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'bargap': 0,\n",
+ " 'bargroupgap': 0,\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Heat (flow_rate)'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'time'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 4
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-12T12:06:35.534937Z",
+ "start_time": "2025-12-12T12:06:35.496736Z"
+ }
+ },
+ "source": "### Accessing Plot Data\n\nEvery plot returns a `PlotResult` with both the figure and underlying data. Use `.data.to_dataframe()` to get a pandas DataFrame:"
+ },
+ {
+ "cell_type": "code",
+ "id": "10",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:15.732085Z",
+ "start_time": "2025-12-13T14:13:15.577916Z"
+ }
+ },
+ "source": [
+ "# Get plot result and access the underlying data\n",
+ "result = simple.statistics.plot.balance('Heat', show=False)\n",
+ "\n",
+ "# Convert to DataFrame for easy viewing/export\n",
+ "df = result.data.to_dataframe()\n",
+ "df.head(10)"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " Boiler(Heat) ThermalStorage(Discharge) \\\n",
+ "time \n",
+ "2024-01-15 00:00:00 -32.483571 -0.000000e+00 \n",
+ "2024-01-15 01:00:00 -29.308678 5.275242e-13 \n",
+ "2024-01-15 02:00:00 -33.238443 -7.086767e-13 \n",
+ "2024-01-15 03:00:00 -101.411593 -3.516828e-13 \n",
+ "2024-01-15 04:00:00 -128.829233 -5.613288e-13 \n",
+ "2024-01-15 05:00:00 -128.829315 -7.033655e-13 \n",
+ "2024-01-15 06:00:00 -0.000000 -3.789606e+01 \n",
+ "2024-01-15 07:00:00 -0.000000 -8.383717e+01 \n",
+ "2024-01-15 08:00:00 -0.000000 -7.765263e+01 \n",
+ "2024-01-15 09:00:00 -0.000000 -8.271280e+01 \n",
+ "\n",
+ " ThermalStorage(Charge) Office(Heat) \n",
+ "time \n",
+ "2024-01-15 00:00:00 0.000000e+00 32.483571 \n",
+ "2024-01-15 01:00:00 -3.747575e-13 29.308678 \n",
+ "2024-01-15 02:00:00 8.792069e-13 33.238443 \n",
+ "2024-01-15 03:00:00 6.379644e+01 37.615149 \n",
+ "2024-01-15 04:00:00 1.000000e+02 28.829233 \n",
+ "2024-01-15 05:00:00 1.000000e+02 28.829315 \n",
+ "2024-01-15 06:00:00 1.055048e-12 37.896064 \n",
+ "2024-01-15 07:00:00 7.033655e-13 83.837174 \n",
+ "2024-01-15 08:00:00 -7.673862e-13 77.652628 \n",
+ "2024-01-15 09:00:00 7.033655e-13 82.712800 "
+ ],
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " Boiler(Heat) | \n",
+ " ThermalStorage(Discharge) | \n",
+ " ThermalStorage(Charge) | \n",
+ " Office(Heat) | \n",
+ "
\n",
+ " \n",
+ " | time | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2024-01-15 00:00:00 | \n",
+ " -32.483571 | \n",
+ " -0.000000e+00 | \n",
+ " 0.000000e+00 | \n",
+ " 32.483571 | \n",
+ "
\n",
+ " \n",
+ " | 2024-01-15 01:00:00 | \n",
+ " -29.308678 | \n",
+ " 5.275242e-13 | \n",
+ " -3.747575e-13 | \n",
+ " 29.308678 | \n",
+ "
\n",
+ " \n",
+ " | 2024-01-15 02:00:00 | \n",
+ " -33.238443 | \n",
+ " -7.086767e-13 | \n",
+ " 8.792069e-13 | \n",
+ " 33.238443 | \n",
+ "
\n",
+ " \n",
+ " | 2024-01-15 03:00:00 | \n",
+ " -101.411593 | \n",
+ " -3.516828e-13 | \n",
+ " 6.379644e+01 | \n",
+ " 37.615149 | \n",
+ "
\n",
+ " \n",
+ " | 2024-01-15 04:00:00 | \n",
+ " -128.829233 | \n",
+ " -5.613288e-13 | \n",
+ " 1.000000e+02 | \n",
+ " 28.829233 | \n",
+ "
\n",
+ " \n",
+ " | 2024-01-15 05:00:00 | \n",
+ " -128.829315 | \n",
+ " -7.033655e-13 | \n",
+ " 1.000000e+02 | \n",
+ " 28.829315 | \n",
+ "
\n",
+ " \n",
+ " | 2024-01-15 06:00:00 | \n",
+ " -0.000000 | \n",
+ " -3.789606e+01 | \n",
+ " 1.055048e-12 | \n",
+ " 37.896064 | \n",
+ "
\n",
+ " \n",
+ " | 2024-01-15 07:00:00 | \n",
+ " -0.000000 | \n",
+ " -8.383717e+01 | \n",
+ " 7.033655e-13 | \n",
+ " 83.837174 | \n",
+ "
\n",
+ " \n",
+ " | 2024-01-15 08:00:00 | \n",
+ " -0.000000 | \n",
+ " -7.765263e+01 | \n",
+ " -7.673862e-13 | \n",
+ " 77.652628 | \n",
+ "
\n",
+ " \n",
+ " | 2024-01-15 09:00:00 | \n",
+ " -0.000000 | \n",
+ " -8.271280e+01 | \n",
+ " 7.033655e-13 | \n",
+ " 82.712800 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 5
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-12T12:06:35.617665Z",
+ "start_time": "2025-12-12T12:06:35.585811Z"
+ }
+ },
+ "source": "### Energy Totals\n\nGet total energy by flow using `flow_hours`:"
+ },
+ {
+ "cell_type": "code",
+ "id": "12",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:15.948455Z",
+ "start_time": "2025-12-13T14:13:15.924150Z"
+ }
+ },
+ "source": "import pandas as pd\n\n# Total energy per flow\ntotals = {var: float(simple.statistics.flow_hours[var].sum()) for var in simple.statistics.flow_hours.data_vars}\n\npd.Series(totals, name='Energy [kWh]').to_frame().T",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ " GasGrid(Gas) Boiler(Gas) Boiler(Heat) ThermalStorage(Charge) \\\n",
+ "Energy [kWh] 8936.665406 8936.665406 8221.732173 3457.182735 \n",
+ "\n",
+ " ThermalStorage(Discharge) Office(Heat) \n",
+ "Energy [kWh] 3242.788948 8007.338386 "
+ ],
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " GasGrid(Gas) | \n",
+ " Boiler(Gas) | \n",
+ " Boiler(Heat) | \n",
+ " ThermalStorage(Charge) | \n",
+ " ThermalStorage(Discharge) | \n",
+ " Office(Heat) | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Energy [kWh] | \n",
+ " 8936.665406 | \n",
+ " 8936.665406 | \n",
+ " 8221.732173 | \n",
+ " 3457.182735 | \n",
+ " 3242.788948 | \n",
+ " 8007.338386 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 6
+ },
+ {
+ "cell_type": "markdown",
+ "id": "13",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-12T12:06:35.754890Z",
+ "start_time": "2025-12-12T12:06:35.735084Z"
+ }
+ },
+ "source": "## 3. Time Series Plots"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "14",
+ "metadata": {},
+ "source": "### 3.1 Balance Plot\n\nShows inflows (positive) and outflows (negative) for a bus or component:"
+ },
+ {
+ "cell_type": "code",
+ "id": "15",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:16.412850Z",
+ "start_time": "2025-12-13T14:13:16.305115Z"
+ }
+ },
+ "source": [
+ "# Component balance (all flows of a component)\n",
+ "simple.statistics.plot.balance('ThermalStorage')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 4kB\n",
+ "Dimensions: (time: 169)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 1kB 2024-01-15 ... 2024-...\n",
+ "Data variables:\n",
+ " ThermalStorage(Charge) (time) float64 1kB -0.0 3.748e-13 ... -100.0 nan\n",
+ " ThermalStorage(Discharge) (time) float64 1kB 0.0 -5.275e-13 ... nan, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=ThermalStorage(Charge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage(Charge)',\n",
+ " 'marker': {'color': '#D62728', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage(Charge)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAIAUfPDBB19aPby8nSEx72' ... 'AAAAAAgNj//////1jAAAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=ThermalStorage(Discharge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage(Discharge)',\n",
+ " 'marker': {'color': '#D62728', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage(Discharge)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAAAKPvjgg49ivby8nSEx72' ... 'AAAAAgPWP9SoFav2i9AAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'bargap': 0,\n",
+ " 'bargroupgap': 0,\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'ThermalStorage (flow_rate)'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'time'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 7
+ },
+ {
+ "cell_type": "markdown",
+ "id": "16",
+ "metadata": {},
+ "source": "### 3.2 Carrier Balance\n\nShows all flows of a specific carrier across the entire system:"
+ },
+ {
+ "cell_type": "code",
+ "id": "17",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:16.630015Z",
+ "start_time": "2025-12-13T14:13:16.539450Z"
+ }
+ },
+ "source": [
+ "complex_sys.statistics.plot.carrier_balance('heat')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 4kB\n",
+ "Dimensions: (time: 73)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 584B 2024-06-01 ... 2024-06-04\n",
+ "Data variables:\n",
+ " CHP(Heat) (time) float64 584B 0.0 0.0 0.0 0.0 ... 0.0 0.0 nan\n",
+ " HeatPump(Heat) (time) float64 584B 0.0 0.0 0.0 0.0 ... 0.0 0.0 nan\n",
+ " BackupBoiler(Heat) (time) float64 584B 20.0 26.01 25.43 ... 20.0 nan\n",
+ " HeatStorage(Discharge) (time) float64 584B 0.0 0.0 0.0 ... 0.0 nan\n",
+ " HeatStorage(Charge) (time) float64 584B -0.0 -0.0 -0.0 ... -0.0 nan\n",
+ " HeatDemand(Heat) (time) float64 584B -20.0 -26.01 ... -20.0 nan, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=CHP(Heat)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'CHP(Heat)',\n",
+ " 'marker': {'color': '#AB63FA', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'CHP(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' ... 'AAAAAAAAAAAAAAAAAAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=HeatPump(Heat)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'HeatPump(Heat)',\n",
+ " 'marker': {'color': '#FFA15A', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'HeatPump(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' ... 'AAAAAAAAAAAAAAAAAAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=BackupBoiler(Heat)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'BackupBoiler(Heat)',\n",
+ " 'marker': {'color': '#19D3F3', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'BackupBoiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAANEBcQRe1SgI6QOU9Gisjbz' ... 'Dnhlw6QAAAAAAAADRAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=HeatStorage(Discharge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'HeatStorage(Discharge)',\n",
+ " 'marker': {'color': '#FF6692', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'HeatStorage(Discharge)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' ... 'RO7MQ7PQAAAAAAAAAAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=HeatStorage(Charge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'HeatStorage(Charge)',\n",
+ " 'marker': {'color': '#FF6692', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'HeatStorage(Charge)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAIAAAAAAAAAAgAAAAAAAAA' ... 'RO7MQ+vQAAAAAAAACAAAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=HeatDemand(Heat)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'HeatDemand(Heat)',\n",
+ " 'marker': {'color': '#B6E880', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'HeatDemand(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAANMBcQRe1SgI6wOQ9Gisjbz' ... 'Dnhlw6wAAAAAAAADTAAAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'bargap': 0,\n",
+ " 'bargroupgap': 0,\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Heat Balance (flow_rate)'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'time'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 8
+ },
+ {
+ "cell_type": "code",
+ "id": "18",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:16.765682Z",
+ "start_time": "2025-12-13T14:13:16.660109Z"
+ }
+ },
+ "source": [
+ "complex_sys.statistics.plot.carrier_balance('electricity')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 4kB\n",
+ "Dimensions: (time: 73)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 584B 2024-06-01 ... 2024-06-04\n",
+ "Data variables:\n",
+ " ElectricityImport(El) (time) float64 584B 23.49 20.59 21.13 ... 17.12 nan\n",
+ " CHP(El) (time) float64 584B 0.0 0.0 0.0 0.0 ... 0.0 0.0 nan\n",
+ " ElectricityExport(El) (time) float64 584B -0.0 -0.0 -0.0 ... -0.0 -0.0 nan\n",
+ " HeatPump(El) (time) float64 584B -0.0 -0.0 -0.0 ... -0.0 -0.0 nan\n",
+ " ElDemand(El) (time) float64 584B -23.49 -20.59 ... -17.12 nan, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=ElectricityImport(El)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ElectricityImport(El)',\n",
+ " 'marker': {'color': '#EF553B', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ElectricityImport(El)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('2HsanZJ8N0B/T9mTNpc0QB5Tg3x1IT' ... 'ANSU0wQAE5VciyHTFAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=CHP(El)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'CHP(El)',\n",
+ " 'marker': {'color': '#AB63FA', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'CHP(El)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' ... 'AAAAAAAAAAAAAAAAAAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=ElectricityExport(El)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ElectricityExport(El)',\n",
+ " 'marker': {'color': '#00CC96', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ElectricityExport(El)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAIAAAAAAAAAAgAAAAAAAAA' ... 'AAAAAAgAAAAAAAAACAAAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=HeatPump(El)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'HeatPump(El)',\n",
+ " 'marker': {'color': '#FFA15A', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'HeatPump(El)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAIAAAAAAAAAAgAAAAAAAAA' ... 'AAAAAAgAAAAAAAAACAAAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=ElDemand(El)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ElDemand(El)',\n",
+ " 'marker': {'color': '#FF97FF', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ElDemand(El)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-06-01T00:00:00.000000000', '2024-06-01T01:00:00.000000000',\n",
+ " '2024-06-01T02:00:00.000000000', '2024-06-01T03:00:00.000000000',\n",
+ " '2024-06-01T04:00:00.000000000', '2024-06-01T05:00:00.000000000',\n",
+ " '2024-06-01T06:00:00.000000000', '2024-06-01T07:00:00.000000000',\n",
+ " '2024-06-01T08:00:00.000000000', '2024-06-01T09:00:00.000000000',\n",
+ " '2024-06-01T10:00:00.000000000', '2024-06-01T11:00:00.000000000',\n",
+ " '2024-06-01T12:00:00.000000000', '2024-06-01T13:00:00.000000000',\n",
+ " '2024-06-01T14:00:00.000000000', '2024-06-01T15:00:00.000000000',\n",
+ " '2024-06-01T16:00:00.000000000', '2024-06-01T17:00:00.000000000',\n",
+ " '2024-06-01T18:00:00.000000000', '2024-06-01T19:00:00.000000000',\n",
+ " '2024-06-01T20:00:00.000000000', '2024-06-01T21:00:00.000000000',\n",
+ " '2024-06-01T22:00:00.000000000', '2024-06-01T23:00:00.000000000',\n",
+ " '2024-06-02T00:00:00.000000000', '2024-06-02T01:00:00.000000000',\n",
+ " '2024-06-02T02:00:00.000000000', '2024-06-02T03:00:00.000000000',\n",
+ " '2024-06-02T04:00:00.000000000', '2024-06-02T05:00:00.000000000',\n",
+ " '2024-06-02T06:00:00.000000000', '2024-06-02T07:00:00.000000000',\n",
+ " '2024-06-02T08:00:00.000000000', '2024-06-02T09:00:00.000000000',\n",
+ " '2024-06-02T10:00:00.000000000', '2024-06-02T11:00:00.000000000',\n",
+ " '2024-06-02T12:00:00.000000000', '2024-06-02T13:00:00.000000000',\n",
+ " '2024-06-02T14:00:00.000000000', '2024-06-02T15:00:00.000000000',\n",
+ " '2024-06-02T16:00:00.000000000', '2024-06-02T17:00:00.000000000',\n",
+ " '2024-06-02T18:00:00.000000000', '2024-06-02T19:00:00.000000000',\n",
+ " '2024-06-02T20:00:00.000000000', '2024-06-02T21:00:00.000000000',\n",
+ " '2024-06-02T22:00:00.000000000', '2024-06-02T23:00:00.000000000',\n",
+ " '2024-06-03T00:00:00.000000000', '2024-06-03T01:00:00.000000000',\n",
+ " '2024-06-03T02:00:00.000000000', '2024-06-03T03:00:00.000000000',\n",
+ " '2024-06-03T04:00:00.000000000', '2024-06-03T05:00:00.000000000',\n",
+ " '2024-06-03T06:00:00.000000000', '2024-06-03T07:00:00.000000000',\n",
+ " '2024-06-03T08:00:00.000000000', '2024-06-03T09:00:00.000000000',\n",
+ " '2024-06-03T10:00:00.000000000', '2024-06-03T11:00:00.000000000',\n",
+ " '2024-06-03T12:00:00.000000000', '2024-06-03T13:00:00.000000000',\n",
+ " '2024-06-03T14:00:00.000000000', '2024-06-03T15:00:00.000000000',\n",
+ " '2024-06-03T16:00:00.000000000', '2024-06-03T17:00:00.000000000',\n",
+ " '2024-06-03T18:00:00.000000000', '2024-06-03T19:00:00.000000000',\n",
+ " '2024-06-03T20:00:00.000000000', '2024-06-03T21:00:00.000000000',\n",
+ " '2024-06-03T22:00:00.000000000', '2024-06-03T23:00:00.000000000',\n",
+ " '2024-06-04T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('2HsanZJ8N8B/T9mTNpc0wB5Tg3x1IT' ... 'ANSU0wwAE5VciyHTHAAAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'bargap': 0,\n",
+ " 'bargroupgap': 0,\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Electricity Balance (flow_rate)'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'time'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 9
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-12T12:06:36.266666Z",
+ "start_time": "2025-12-12T12:06:36.198686Z"
+ }
+ },
+ "source": "### 3.3 Flow Rates\n\nPlot multiple flow rates together:"
+ },
+ {
+ "cell_type": "code",
+ "id": "20",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:16.863735Z",
+ "start_time": "2025-12-13T14:13:16.783096Z"
+ }
+ },
+ "source": [
+ "# All flows\n",
+ "simple.statistics.plot.flows()"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 9kB\n",
+ "Dimensions: (time: 169)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 1kB 2024-01-15 ... 2024-...\n",
+ "Data variables:\n",
+ " GasGrid(Gas) (time) float64 1kB 35.31 31.86 ... 135.3 nan\n",
+ " Boiler(Gas) (time) float64 1kB 35.31 31.86 ... 135.3 nan\n",
+ " Boiler(Heat) (time) float64 1kB 32.48 29.31 ... 124.5 nan\n",
+ " ThermalStorage(Charge) (time) float64 1kB 0.0 -3.748e-13 ... 100.0 nan\n",
+ " ThermalStorage(Discharge) (time) float64 1kB 0.0 -5.275e-13 ... nan\n",
+ " Office(Heat) (time) float64 1kB 32.48 29.31 ... 24.48 nan, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=GasGrid(Gas)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'GasGrid(Gas)',\n",
+ " 'line': {'color': '#636EFA', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'GasGrid(Gas)',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scattergl',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('GuEHDXSnQUD261BXdds/QI2yoZ56EE' ... 'SmN701QKxDuYXg6WBAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=Boiler(Gas)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Boiler(Gas)',\n",
+ " 'line': {'color': '#EF553B', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'Boiler(Gas)',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scattergl',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('GuEHDXSnQUD261BXdds/QI2yoZ56EE' ... 'SmN701QKxDuYXg6WBAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=Boiler(Heat)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Boiler(Heat)',\n",
+ " 'line': {'color': '#00CC96', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'Boiler(Heat)',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scattergl',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('5ZuWpeU9QED3U8WNBU89QHjXQkqFnk' ... '////8zQPW5+Ef5Hl9AAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=ThermalStorage(Charge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage(Charge)',\n",
+ " 'line': {'color': '#AB63FA', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'ThermalStorage(Charge)',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scattergl',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAAAUfPDBB19avby8nSEx72' ... 'AAAAAAANj//////1hAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=ThermalStorage(Discharge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage(Discharge)',\n",
+ " 'line': {'color': '#FFA15A', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'ThermalStorage(Discharge)',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scattergl',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAAAKPvjgg49ivby8nSEx72' ... 'AAAAAgPWP9SoFav2i9AAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=Office(Heat)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Office(Heat)',\n",
+ " 'line': {'color': '#19D3F3', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'Office(Heat)',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scattergl',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('5ZuWpeU9QEDMU8WNBU89QGDXQkqFnk' ... 'AAAAA0QK7n4h/lezhAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Flows (flow_rate)'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'time'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 10
+ },
+ {
+ "cell_type": "code",
+ "id": "21",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:16.936035Z",
+ "start_time": "2025-12-13T14:13:16.880022Z"
+ }
+ },
+ "source": [
+ "# Flows filtered by component\n",
+ "simple.statistics.plot.flows(component='Boiler')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 4kB\n",
+ "Dimensions: (time: 169)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 1kB 2024-01-15 ... 2024-01-22\n",
+ "Data variables:\n",
+ " Boiler(Gas) (time) float64 1kB 35.31 31.86 36.13 110.2 ... 21.74 135.3 nan\n",
+ " Boiler(Heat) (time) float64 1kB 32.48 29.31 33.24 101.4 ... 20.0 124.5 nan, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=Boiler(Gas)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Boiler(Gas)',\n",
+ " 'line': {'color': '#636EFA', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'Boiler(Gas)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scatter',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('GuEHDXSnQUD261BXdds/QI2yoZ56EE' ... 'SmN701QKxDuYXg6WBAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=Boiler(Heat)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Boiler(Heat)',\n",
+ " 'line': {'color': '#EF553B', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'Boiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scatter',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('5ZuWpeU9QED3U8WNBU89QHjXQkqFnk' ... '////8zQPW5+Ef5Hl9AAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Flows (flow_rate)'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'time'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 11
+ },
+ {
+ "cell_type": "markdown",
+ "id": "32",
+ "metadata": {},
+ "source": [
+ "### 3.4 Storage Plot\n",
+ "\n",
+ "Combined view of storage charge state and flows:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "33",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.166751Z",
+ "start_time": "2025-12-13T14:13:16.985913Z"
+ }
+ },
+ "source": [
+ "simple.statistics.plot.storage('ThermalStorage')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 5kB\n",
+ "Dimensions: (time: 169)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 1kB 2024-01-15 ... 2024-...\n",
+ "Data variables:\n",
+ " ThermalStorage(Charge) (time) float64 1kB 0.0 -3.748e-13 ... 100.0 nan\n",
+ " ThermalStorage(Discharge) (time) float64 1kB -0.0 5.275e-13 ... nan\n",
+ " charge_state (time) float64 1kB 250.0 248.8 ... 102.5 200.0, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=ThermalStorage(Charge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage(Charge)',\n",
+ " 'marker': {'color': '#D62728', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage(Charge)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAAAUfPDBB19avby8nSEx72' ... 'AAAAAAANj//////1hAAAAAAAAA+H8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=ThermalStorage(Discharge)
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage(Discharge)',\n",
+ " 'marker': {'color': '#D62728', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage(Discharge)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAAAAAIAKPvjgg49iPby8nSEx72' ... 'AAAAAgvWP9SoFav2g9AAAAAAAA+P8='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'time=%{x}
value=%{y}',\n",
+ " 'legendgroup': '',\n",
+ " 'line': {'color': 'black', 'width': 2},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'charge_state',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scatter',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAABAb0AAAAAAABhvQDkzMzMz8G' ... 'LbxcFZQPDkQtTNoFlAAAAAAAAAaUA='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y2'}],\n",
+ " 'layout': {'bargap': 0,\n",
+ " 'bargroupgap': 0,\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'ThermalStorage Operation (flow_rate)'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'time'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}},\n",
+ " 'yaxis2': {'overlaying': 'y', 'showgrid': False, 'side': 'right', 'title': {'text': 'Charge State'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 12
+ },
+ {
+ "cell_type": "markdown",
+ "id": "34",
+ "metadata": {},
+ "source": [
+ "### 3.5 Charge States Plot\n",
+ "\n",
+ "Plot charge state time series directly:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "35",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.297322Z",
+ "start_time": "2025-12-13T14:13:17.214857Z"
+ }
+ },
+ "source": [
+ "simple.statistics.plot.charge_states('ThermalStorage')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 3kB\n",
+ "Dimensions: (time: 169)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 1kB 2024-01-15 ... 2024-01-22\n",
+ "Data variables:\n",
+ " ThermalStorage (time) float64 1kB 250.0 248.8 247.5 ... 103.0 102.5 200.0, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=ThermalStorage
time=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage',\n",
+ " 'line': {'color': '#636EFA', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'ThermalStorage',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scatter',\n",
+ " 'x': array(['2024-01-15T00:00:00.000000000', '2024-01-15T01:00:00.000000000',\n",
+ " '2024-01-15T02:00:00.000000000', '2024-01-15T03:00:00.000000000',\n",
+ " '2024-01-15T04:00:00.000000000', '2024-01-15T05:00:00.000000000',\n",
+ " '2024-01-15T06:00:00.000000000', '2024-01-15T07:00:00.000000000',\n",
+ " '2024-01-15T08:00:00.000000000', '2024-01-15T09:00:00.000000000',\n",
+ " '2024-01-15T10:00:00.000000000', '2024-01-15T11:00:00.000000000',\n",
+ " '2024-01-15T12:00:00.000000000', '2024-01-15T13:00:00.000000000',\n",
+ " '2024-01-15T14:00:00.000000000', '2024-01-15T15:00:00.000000000',\n",
+ " '2024-01-15T16:00:00.000000000', '2024-01-15T17:00:00.000000000',\n",
+ " '2024-01-15T18:00:00.000000000', '2024-01-15T19:00:00.000000000',\n",
+ " '2024-01-15T20:00:00.000000000', '2024-01-15T21:00:00.000000000',\n",
+ " '2024-01-15T22:00:00.000000000', '2024-01-15T23:00:00.000000000',\n",
+ " '2024-01-16T00:00:00.000000000', '2024-01-16T01:00:00.000000000',\n",
+ " '2024-01-16T02:00:00.000000000', '2024-01-16T03:00:00.000000000',\n",
+ " '2024-01-16T04:00:00.000000000', '2024-01-16T05:00:00.000000000',\n",
+ " '2024-01-16T06:00:00.000000000', '2024-01-16T07:00:00.000000000',\n",
+ " '2024-01-16T08:00:00.000000000', '2024-01-16T09:00:00.000000000',\n",
+ " '2024-01-16T10:00:00.000000000', '2024-01-16T11:00:00.000000000',\n",
+ " '2024-01-16T12:00:00.000000000', '2024-01-16T13:00:00.000000000',\n",
+ " '2024-01-16T14:00:00.000000000', '2024-01-16T15:00:00.000000000',\n",
+ " '2024-01-16T16:00:00.000000000', '2024-01-16T17:00:00.000000000',\n",
+ " '2024-01-16T18:00:00.000000000', '2024-01-16T19:00:00.000000000',\n",
+ " '2024-01-16T20:00:00.000000000', '2024-01-16T21:00:00.000000000',\n",
+ " '2024-01-16T22:00:00.000000000', '2024-01-16T23:00:00.000000000',\n",
+ " '2024-01-17T00:00:00.000000000', '2024-01-17T01:00:00.000000000',\n",
+ " '2024-01-17T02:00:00.000000000', '2024-01-17T03:00:00.000000000',\n",
+ " '2024-01-17T04:00:00.000000000', '2024-01-17T05:00:00.000000000',\n",
+ " '2024-01-17T06:00:00.000000000', '2024-01-17T07:00:00.000000000',\n",
+ " '2024-01-17T08:00:00.000000000', '2024-01-17T09:00:00.000000000',\n",
+ " '2024-01-17T10:00:00.000000000', '2024-01-17T11:00:00.000000000',\n",
+ " '2024-01-17T12:00:00.000000000', '2024-01-17T13:00:00.000000000',\n",
+ " '2024-01-17T14:00:00.000000000', '2024-01-17T15:00:00.000000000',\n",
+ " '2024-01-17T16:00:00.000000000', '2024-01-17T17:00:00.000000000',\n",
+ " '2024-01-17T18:00:00.000000000', '2024-01-17T19:00:00.000000000',\n",
+ " '2024-01-17T20:00:00.000000000', '2024-01-17T21:00:00.000000000',\n",
+ " '2024-01-17T22:00:00.000000000', '2024-01-17T23:00:00.000000000',\n",
+ " '2024-01-18T00:00:00.000000000', '2024-01-18T01:00:00.000000000',\n",
+ " '2024-01-18T02:00:00.000000000', '2024-01-18T03:00:00.000000000',\n",
+ " '2024-01-18T04:00:00.000000000', '2024-01-18T05:00:00.000000000',\n",
+ " '2024-01-18T06:00:00.000000000', '2024-01-18T07:00:00.000000000',\n",
+ " '2024-01-18T08:00:00.000000000', '2024-01-18T09:00:00.000000000',\n",
+ " '2024-01-18T10:00:00.000000000', '2024-01-18T11:00:00.000000000',\n",
+ " '2024-01-18T12:00:00.000000000', '2024-01-18T13:00:00.000000000',\n",
+ " '2024-01-18T14:00:00.000000000', '2024-01-18T15:00:00.000000000',\n",
+ " '2024-01-18T16:00:00.000000000', '2024-01-18T17:00:00.000000000',\n",
+ " '2024-01-18T18:00:00.000000000', '2024-01-18T19:00:00.000000000',\n",
+ " '2024-01-18T20:00:00.000000000', '2024-01-18T21:00:00.000000000',\n",
+ " '2024-01-18T22:00:00.000000000', '2024-01-18T23:00:00.000000000',\n",
+ " '2024-01-19T00:00:00.000000000', '2024-01-19T01:00:00.000000000',\n",
+ " '2024-01-19T02:00:00.000000000', '2024-01-19T03:00:00.000000000',\n",
+ " '2024-01-19T04:00:00.000000000', '2024-01-19T05:00:00.000000000',\n",
+ " '2024-01-19T06:00:00.000000000', '2024-01-19T07:00:00.000000000',\n",
+ " '2024-01-19T08:00:00.000000000', '2024-01-19T09:00:00.000000000',\n",
+ " '2024-01-19T10:00:00.000000000', '2024-01-19T11:00:00.000000000',\n",
+ " '2024-01-19T12:00:00.000000000', '2024-01-19T13:00:00.000000000',\n",
+ " '2024-01-19T14:00:00.000000000', '2024-01-19T15:00:00.000000000',\n",
+ " '2024-01-19T16:00:00.000000000', '2024-01-19T17:00:00.000000000',\n",
+ " '2024-01-19T18:00:00.000000000', '2024-01-19T19:00:00.000000000',\n",
+ " '2024-01-19T20:00:00.000000000', '2024-01-19T21:00:00.000000000',\n",
+ " '2024-01-19T22:00:00.000000000', '2024-01-19T23:00:00.000000000',\n",
+ " '2024-01-20T00:00:00.000000000', '2024-01-20T01:00:00.000000000',\n",
+ " '2024-01-20T02:00:00.000000000', '2024-01-20T03:00:00.000000000',\n",
+ " '2024-01-20T04:00:00.000000000', '2024-01-20T05:00:00.000000000',\n",
+ " '2024-01-20T06:00:00.000000000', '2024-01-20T07:00:00.000000000',\n",
+ " '2024-01-20T08:00:00.000000000', '2024-01-20T09:00:00.000000000',\n",
+ " '2024-01-20T10:00:00.000000000', '2024-01-20T11:00:00.000000000',\n",
+ " '2024-01-20T12:00:00.000000000', '2024-01-20T13:00:00.000000000',\n",
+ " '2024-01-20T14:00:00.000000000', '2024-01-20T15:00:00.000000000',\n",
+ " '2024-01-20T16:00:00.000000000', '2024-01-20T17:00:00.000000000',\n",
+ " '2024-01-20T18:00:00.000000000', '2024-01-20T19:00:00.000000000',\n",
+ " '2024-01-20T20:00:00.000000000', '2024-01-20T21:00:00.000000000',\n",
+ " '2024-01-20T22:00:00.000000000', '2024-01-20T23:00:00.000000000',\n",
+ " '2024-01-21T00:00:00.000000000', '2024-01-21T01:00:00.000000000',\n",
+ " '2024-01-21T02:00:00.000000000', '2024-01-21T03:00:00.000000000',\n",
+ " '2024-01-21T04:00:00.000000000', '2024-01-21T05:00:00.000000000',\n",
+ " '2024-01-21T06:00:00.000000000', '2024-01-21T07:00:00.000000000',\n",
+ " '2024-01-21T08:00:00.000000000', '2024-01-21T09:00:00.000000000',\n",
+ " '2024-01-21T10:00:00.000000000', '2024-01-21T11:00:00.000000000',\n",
+ " '2024-01-21T12:00:00.000000000', '2024-01-21T13:00:00.000000000',\n",
+ " '2024-01-21T14:00:00.000000000', '2024-01-21T15:00:00.000000000',\n",
+ " '2024-01-21T16:00:00.000000000', '2024-01-21T17:00:00.000000000',\n",
+ " '2024-01-21T18:00:00.000000000', '2024-01-21T19:00:00.000000000',\n",
+ " '2024-01-21T20:00:00.000000000', '2024-01-21T21:00:00.000000000',\n",
+ " '2024-01-21T22:00:00.000000000', '2024-01-21T23:00:00.000000000',\n",
+ " '2024-01-22T00:00:00.000000000'], dtype='datetime64[ns]'),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('AAAAAABAb0AAAAAAABhvQDkzMzMz8G' ... 'LbxcFZQPDkQtTNoFlAAAAAAAAAaUA='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Storage Charge States'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'time'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'Charge State'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 13
+ },
+ {
+ "cell_type": "markdown",
+ "id": "36",
+ "metadata": {},
+ "source": [
+ "## 4. Aggregated Plots"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "37",
+ "metadata": {},
+ "source": [
+ "### 4.1 Sizes Plot\n",
+ "\n",
+ "Bar chart of component/flow sizes:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "38",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:17:12.906249Z",
+ "start_time": "2025-12-13T14:17:12.823893Z"
+ }
+ },
+ "source": "multiperiod.statistics.plot.sizes()",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 208B\n",
+ "Dimensions: (period: 3, scenario: 2)\n",
+ "Coordinates:\n",
+ " * period (period) int64 24B 2024 2025 2026\n",
+ " * scenario (scenario) scenario=high_demand
period=2024
Size=%{y}',\n",
+ " 'legendgroup': 'Boiler(Heat)',\n",
+ " 'marker': {'color': '#30123b', 'pattern': {'shape': ''}},\n",
+ " 'name': 'Boiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['Boiler(Heat)'], dtype=object),\n",
+ " 'xaxis': 'x4',\n",
+ " 'y': {'bdata': 'PvP9oLpQWkA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y4'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=high_demand
period=2025
Size=%{y}',\n",
+ " 'legendgroup': 'Boiler(Heat)',\n",
+ " 'marker': {'color': '#30123b', 'pattern': {'shape': ''}},\n",
+ " 'name': 'Boiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': False,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['Boiler(Heat)'], dtype=object),\n",
+ " 'xaxis': 'x5',\n",
+ " 'y': {'bdata': 'PvP9oLpQWkA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y5'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=high_demand
period=2026
Size=%{y}',\n",
+ " 'legendgroup': 'Boiler(Heat)',\n",
+ " 'marker': {'color': '#30123b', 'pattern': {'shape': ''}},\n",
+ " 'name': 'Boiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': False,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['Boiler(Heat)'], dtype=object),\n",
+ " 'xaxis': 'x6',\n",
+ " 'y': {'bdata': 'PvP9oLpQWkA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y6'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=low_demand
period=2024
Size=%{y}',\n",
+ " 'legendgroup': 'Boiler(Heat)',\n",
+ " 'marker': {'color': '#30123b', 'pattern': {'shape': ''}},\n",
+ " 'name': 'Boiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': False,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['Boiler(Heat)'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'PvP9oLpQWkA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=low_demand
period=2025
Size=%{y}',\n",
+ " 'legendgroup': 'Boiler(Heat)',\n",
+ " 'marker': {'color': '#30123b', 'pattern': {'shape': ''}},\n",
+ " 'name': 'Boiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': False,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['Boiler(Heat)'], dtype=object),\n",
+ " 'xaxis': 'x2',\n",
+ " 'y': {'bdata': 'PvP9oLpQWkA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y2'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=low_demand
period=2026
Size=%{y}',\n",
+ " 'legendgroup': 'Boiler(Heat)',\n",
+ " 'marker': {'color': '#30123b', 'pattern': {'shape': ''}},\n",
+ " 'name': 'Boiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': False,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['Boiler(Heat)'], dtype=object),\n",
+ " 'xaxis': 'x3',\n",
+ " 'y': {'bdata': 'PvP9oLpQWkA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y3'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=high_demand
period=2024
Size=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage',\n",
+ " 'marker': {'color': '#7a0402', 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['ThermalStorage'], dtype=object),\n",
+ " 'xaxis': 'x4',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y4'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=high_demand
period=2025
Size=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage',\n",
+ " 'marker': {'color': '#7a0402', 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': False,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['ThermalStorage'], dtype=object),\n",
+ " 'xaxis': 'x5',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y5'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=high_demand
period=2026
Size=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage',\n",
+ " 'marker': {'color': '#7a0402', 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': False,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['ThermalStorage'], dtype=object),\n",
+ " 'xaxis': 'x6',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y6'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=low_demand
period=2024
Size=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage',\n",
+ " 'marker': {'color': '#7a0402', 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': False,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['ThermalStorage'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=low_demand
period=2025
Size=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage',\n",
+ " 'marker': {'color': '#7a0402', 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': False,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['ThermalStorage'], dtype=object),\n",
+ " 'xaxis': 'x2',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y2'},\n",
+ " {'hovertemplate': 'Flow=%{x}
scenario=low_demand
period=2026
Size=%{y}',\n",
+ " 'legendgroup': 'ThermalStorage',\n",
+ " 'marker': {'color': '#7a0402', 'pattern': {'shape': ''}},\n",
+ " 'name': 'ThermalStorage',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': False,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['ThermalStorage'], dtype=object),\n",
+ " 'xaxis': 'x3',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y3'}],\n",
+ " 'layout': {'annotations': [{'font': {},\n",
+ " 'showarrow': False,\n",
+ " 'text': 'period=2024',\n",
+ " 'x': 0.15666666666666665,\n",
+ " 'xanchor': 'center',\n",
+ " 'xref': 'paper',\n",
+ " 'y': 1.0,\n",
+ " 'yanchor': 'bottom',\n",
+ " 'yref': 'paper'},\n",
+ " {'font': {},\n",
+ " 'showarrow': False,\n",
+ " 'text': 'period=2025',\n",
+ " 'x': 0.49,\n",
+ " 'xanchor': 'center',\n",
+ " 'xref': 'paper',\n",
+ " 'y': 1.0,\n",
+ " 'yanchor': 'bottom',\n",
+ " 'yref': 'paper'},\n",
+ " {'font': {},\n",
+ " 'showarrow': False,\n",
+ " 'text': 'period=2026',\n",
+ " 'x': 0.8233333333333333,\n",
+ " 'xanchor': 'center',\n",
+ " 'xref': 'paper',\n",
+ " 'y': 1.0,\n",
+ " 'yanchor': 'bottom',\n",
+ " 'yref': 'paper'},\n",
+ " {'font': {},\n",
+ " 'showarrow': False,\n",
+ " 'text': 'scenario=low_demand',\n",
+ " 'textangle': 90,\n",
+ " 'x': 0.98,\n",
+ " 'xanchor': 'left',\n",
+ " 'xref': 'paper',\n",
+ " 'y': 0.2425,\n",
+ " 'yanchor': 'middle',\n",
+ " 'yref': 'paper'},\n",
+ " {'font': {},\n",
+ " 'showarrow': False,\n",
+ " 'text': 'scenario=high_demand',\n",
+ " 'textangle': 90,\n",
+ " 'x': 0.98,\n",
+ " 'xanchor': 'left',\n",
+ " 'xref': 'paper',\n",
+ " 'y': 0.7575000000000001,\n",
+ " 'yanchor': 'middle',\n",
+ " 'yref': 'paper'}],\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'Flow'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Investment Sizes'},\n",
+ " 'xaxis': {'anchor': 'y',\n",
+ " 'categoryarray': [Boiler(Heat), ThermalStorage],\n",
+ " 'categoryorder': 'array',\n",
+ " 'domain': [0.0, 0.3133333333333333],\n",
+ " 'title': {'text': 'Flow'}},\n",
+ " 'xaxis2': {'anchor': 'y2',\n",
+ " 'categoryarray': [Boiler(Heat), ThermalStorage],\n",
+ " 'categoryorder': 'array',\n",
+ " 'domain': [0.3333333333333333, 0.6466666666666666],\n",
+ " 'matches': 'x',\n",
+ " 'title': {'text': 'Flow'}},\n",
+ " 'xaxis3': {'anchor': 'y3',\n",
+ " 'categoryarray': [Boiler(Heat), ThermalStorage],\n",
+ " 'categoryorder': 'array',\n",
+ " 'domain': [0.6666666666666666, 0.98],\n",
+ " 'matches': 'x',\n",
+ " 'title': {'text': 'Flow'}},\n",
+ " 'xaxis4': {'anchor': 'y4', 'domain': [0.0, 0.3133333333333333], 'matches': 'x', 'showticklabels': False},\n",
+ " 'xaxis5': {'anchor': 'y5',\n",
+ " 'domain': [0.3333333333333333, 0.6466666666666666],\n",
+ " 'matches': 'x',\n",
+ " 'showticklabels': False},\n",
+ " 'xaxis6': {'anchor': 'y6', 'domain': [0.6666666666666666, 0.98], 'matches': 'x', 'showticklabels': False},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 0.485], 'title': {'text': 'Size'}},\n",
+ " 'yaxis2': {'anchor': 'x2', 'domain': [0.0, 0.485], 'matches': 'y', 'showticklabels': False},\n",
+ " 'yaxis3': {'anchor': 'x3', 'domain': [0.0, 0.485], 'matches': 'y', 'showticklabels': False},\n",
+ " 'yaxis4': {'anchor': 'x4', 'domain': [0.515, 1.0], 'matches': 'y', 'title': {'text': 'Size'}},\n",
+ " 'yaxis5': {'anchor': 'x5', 'domain': [0.515, 1.0], 'matches': 'y', 'showticklabels': False},\n",
+ " 'yaxis6': {'anchor': 'x6', 'domain': [0.515, 1.0], 'matches': 'y', 'showticklabels': False}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 46,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 46
+ },
+ {
+ "cell_type": "markdown",
+ "id": "39",
+ "metadata": {},
+ "source": [
+ "### 4.2 Effects Plot\n",
+ "\n",
+ "Bar chart of effect totals by component:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "40",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.440231Z",
+ "start_time": "2025-12-13T14:13:17.355184Z"
+ }
+ },
+ "source": [
+ "simple.statistics.plot.effects(effect='costs')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 24B\n",
+ "Dimensions: (effect: 1, component: 1)\n",
+ "Coordinates:\n",
+ " * effect (effect) object 8B 'costs'\n",
+ " * component (component) object 8B 'GasGrid'\n",
+ "Data variables:\n",
+ " total (effect, component) float64 8B 558.8, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'GasGrid',\n",
+ " 'marker': {'color': '#a4fc3b', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'GasGrid',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['GasGrid'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'sDkY5qR2gUA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'bargap': 0,\n",
+ " 'bargroupgap': 0,\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'component'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'costs (total) by component'},\n",
+ " 'xaxis': {'anchor': 'y',\n",
+ " 'categoryarray': [GasGrid],\n",
+ " 'categoryorder': 'array',\n",
+ " 'domain': [0.0, 1.0],\n",
+ " 'title': {'text': 'component'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 15
+ },
+ {
+ "cell_type": "code",
+ "id": "41",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.547032Z",
+ "start_time": "2025-12-13T14:13:17.454197Z"
+ }
+ },
+ "source": [
+ "# Multi-effect system: compare costs and CO2\n",
+ "complex_sys.statistics.plot.effects(effect='costs')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 104B\n",
+ "Dimensions: (effect: 1, component: 6)\n",
+ "Coordinates:\n",
+ " * effect (effect) object 8B 'costs'\n",
+ " * component (component) object 48B 'CHP' ... 'HeatStorage'\n",
+ "Data variables:\n",
+ " total (effect, component) float64 48B 76.0 -297.4 102.9 420.8 0.0 0.0, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'CHP',\n",
+ " 'marker': {'color': '#30123b', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'CHP',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['CHP'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'AAAAAAAAU0A=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ElectricityExport',\n",
+ " 'marker': {'color': '#3c99f9', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ElectricityExport',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['ElectricityExport'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'QuE7D7GWcsA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ElectricityImport',\n",
+ " 'marker': {'color': '#49f683', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ElectricityImport',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['ElectricityImport'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'mB7bhVm8WUA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'GasGrid',\n",
+ " 'marker': {'color': '#dfda36', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'GasGrid',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['GasGrid'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'VVvjiWRNekA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'HeatPump',\n",
+ " 'marker': {'color': '#ee5a12', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'HeatPump',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['HeatPump'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'HeatStorage',\n",
+ " 'marker': {'color': '#7a0402', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'HeatStorage',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['HeatStorage'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'bargap': 0,\n",
+ " 'bargroupgap': 0,\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'component'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'costs (total) by component'},\n",
+ " 'xaxis': {'anchor': 'y',\n",
+ " 'categoryarray': [CHP, ElectricityExport,\n",
+ " ElectricityImport, GasGrid, HeatPump,\n",
+ " HeatStorage],\n",
+ " 'categoryorder': 'array',\n",
+ " 'domain': [0.0, 1.0],\n",
+ " 'title': {'text': 'component'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 16
+ },
+ {
+ "cell_type": "code",
+ "id": "42",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.616154Z",
+ "start_time": "2025-12-13T14:13:17.558702Z"
+ }
+ },
+ "source": [
+ "complex_sys.statistics.plot.effects(effect='CO2')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 104B\n",
+ "Dimensions: (effect: 1, component: 6)\n",
+ "Coordinates:\n",
+ " * effect (effect) object 8B 'CO2'\n",
+ " * component (component) object 48B 'CHP' ... 'HeatStorage'\n",
+ "Data variables:\n",
+ " total (effect, component) float64 48B 0.0 0.0 255.1 1.403e+03 0.0 0.0, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'CHP',\n",
+ " 'marker': {'color': '#30123b', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'CHP',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['CHP'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ElectricityExport',\n",
+ " 'marker': {'color': '#3c99f9', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ElectricityExport',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['ElectricityExport'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'ElectricityImport',\n",
+ " 'marker': {'color': '#49f683', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'ElectricityImport',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['ElectricityImport'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'PuZR52/jb0A=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'GasGrid',\n",
+ " 'marker': {'color': '#dfda36', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'GasGrid',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['GasGrid'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'HMySHSnrlUA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'HeatPump',\n",
+ " 'marker': {'color': '#ee5a12', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'HeatPump',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['HeatPump'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'component=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'HeatStorage',\n",
+ " 'marker': {'color': '#7a0402', 'line': {'width': 0}, 'pattern': {'shape': ''}},\n",
+ " 'name': 'HeatStorage',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'textposition': 'auto',\n",
+ " 'type': 'bar',\n",
+ " 'x': array(['HeatStorage'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': 'AAAAAAAAAAA=', 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'bargap': 0,\n",
+ " 'bargroupgap': 0,\n",
+ " 'barmode': 'relative',\n",
+ " 'legend': {'title': {'text': 'component'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'CO2 (total) by component'},\n",
+ " 'xaxis': {'anchor': 'y',\n",
+ " 'categoryarray': [CHP, ElectricityExport,\n",
+ " ElectricityImport, GasGrid, HeatPump,\n",
+ " HeatStorage],\n",
+ " 'categoryorder': 'array',\n",
+ " 'domain': [0.0, 1.0],\n",
+ " 'title': {'text': 'component'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 17
+ },
+ {
+ "cell_type": "markdown",
+ "id": "43",
+ "metadata": {},
+ "source": [
+ "### 4.3 Duration Curve\n",
+ "\n",
+ "Shows how often each power level is reached:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "44",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.659929Z",
+ "start_time": "2025-12-13T14:13:17.624261Z"
+ }
+ },
+ "source": [
+ "simple.statistics.plot.duration_curve('Boiler(Heat)')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 3kB\n",
+ "Dimensions: (duration: 169)\n",
+ "Coordinates:\n",
+ " * duration (duration) int64 1kB 0 1 2 3 4 5 6 ... 163 164 165 166 167 168\n",
+ "Data variables:\n",
+ " Boiler(Heat) (duration) float64 1kB nan 137.8 134.1 133.1 ... 0.0 0.0 0.0, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=Boiler(Heat)
duration=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'Boiler(Heat)',\n",
+ " 'line': {'color': '#636EFA', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'Boiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scatter',\n",
+ " 'x': {'bdata': ('AAABAAIAAwAEAAUABgAHAAgACQAKAA' ... '4AnwCgAKEAogCjAKQApQCmAKcAqAA='),\n",
+ " 'dtype': 'i2'},\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('/////////39oQtzNVzphQLt+ZyCBw2' ... 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Duration Curve'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'Timesteps'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 18
+ },
+ {
+ "cell_type": "code",
+ "id": "45",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.711351Z",
+ "start_time": "2025-12-13T14:13:17.670270Z"
+ }
+ },
+ "source": [
+ "# Multiple variables\n",
+ "complex_sys.statistics.plot.duration_curve(['CHP(Heat)', 'HeatPump(Heat)', 'BackupBoiler(Heat)'])"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 2kB\n",
+ "Dimensions: (duration: 73)\n",
+ "Coordinates:\n",
+ " * duration (duration) int64 584B 0 1 2 3 4 5 ... 67 68 69 70 71 72\n",
+ "Data variables:\n",
+ " CHP(Heat) (duration) float64 584B nan 80.88 80.62 ... 0.0 0.0 0.0\n",
+ " HeatPump(Heat) (duration) float64 584B nan 0.0 0.0 0.0 ... 0.0 0.0 0.0\n",
+ " BackupBoiler(Heat) (duration) float64 584B nan 63.11 ... -8.993e-15, figure=Figure({\n",
+ " 'data': [{'hovertemplate': 'variable=CHP(Heat)
duration=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'CHP(Heat)',\n",
+ " 'line': {'color': '#636EFA', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'CHP(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scatter',\n",
+ " 'x': {'bdata': ('AAECAwQFBgcICQoLDA0ODxAREhMUFR' ... 'Q1Njc4OTo7PD0+P0BBQkNERUZHSA=='),\n",
+ " 'dtype': 'i1'},\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('/////////39Gwcq9YjhUQOyIZIeOJ1' ... 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=HeatPump(Heat)
duration=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'HeatPump(Heat)',\n",
+ " 'line': {'color': '#EF553B', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'HeatPump(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scatter',\n",
+ " 'x': {'bdata': ('AAECAwQFBgcICQoLDA0ODxAREhMUFR' ... 'Q1Njc4OTo7PD0+P0BBQkNERUZHSA=='),\n",
+ " 'dtype': 'i1'},\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('/////////38AAAAAAAAAAAAAAAAAAA' ... 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'},\n",
+ " {'hovertemplate': 'variable=BackupBoiler(Heat)
duration=%{x}
value=%{y}',\n",
+ " 'legendgroup': 'BackupBoiler(Heat)',\n",
+ " 'line': {'color': '#00CC96', 'dash': 'solid'},\n",
+ " 'marker': {'symbol': 'circle'},\n",
+ " 'mode': 'lines',\n",
+ " 'name': 'BackupBoiler(Heat)',\n",
+ " 'orientation': 'v',\n",
+ " 'showlegend': True,\n",
+ " 'type': 'scatter',\n",
+ " 'x': {'bdata': ('AAECAwQFBgcICQoLDA0ODxAREhMUFR' ... 'Q1Njc4OTo7PD0+P0BBQkNERUZHSA=='),\n",
+ " 'dtype': 'i1'},\n",
+ " 'xaxis': 'x',\n",
+ " 'y': {'bdata': ('/////////38h4dgzOo5PQDMD0m1cz0' ... 'AAAACwvAAAAAAAALi8AAAAAABABL0='),\n",
+ " 'dtype': 'f8'},\n",
+ " 'yaxis': 'y'}],\n",
+ " 'layout': {'legend': {'title': {'text': 'variable'}, 'tracegroupgap': 0},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Duration Curve'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'Timesteps'}},\n",
+ " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'value'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 19
+ },
+ {
+ "cell_type": "markdown",
+ "id": "46",
+ "metadata": {},
+ "source": [
+ "## 5. Heatmaps\n",
+ "\n",
+ "Heatmaps reshape time series into 2D grids (e.g., hour-of-day vs day):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "47",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.799982Z",
+ "start_time": "2025-12-13T14:13:17.729391Z"
+ }
+ },
+ "source": [
+ "# Auto-reshape based on data frequency\n",
+ "simple.statistics.plot.heatmap('Boiler(Heat)')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 2kB\n",
+ "Dimensions: (timeframe: 8, timestep: 24)\n",
+ "Coordinates:\n",
+ " * timeframe (timeframe) object 64B '2024-01-15' '2024-01-16' ... '2024-01-22'\n",
+ " * timestep (timestep) object 192B '00:00' '01:00' ... '22:00' '23:00'\n",
+ "Data variables:\n",
+ " value (timestep, timeframe) float64 2kB 32.48 42.84 47.28 ... 124.5 nan, figure=Figure({\n",
+ " 'data': [{'coloraxis': 'coloraxis',\n",
+ " 'hovertemplate': 'timeframe: %{x}
timestep: %{y}
Boiler(Heat)|flow_rate: %{z}',\n",
+ " 'name': '0',\n",
+ " 'type': 'heatmap',\n",
+ " 'x': array(['2024-01-15', '2024-01-16', '2024-01-17', '2024-01-18', '2024-01-19',\n",
+ " '2024-01-20', '2024-01-21', '2024-01-22'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': array(['00:00', '01:00', '02:00', '03:00', '04:00', '05:00', '06:00', '07:00',\n",
+ " '08:00', '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00',\n",
+ " '16:00', '17:00', '18:00', '19:00', '20:00', '21:00', '22:00', '23:00'],\n",
+ " dtype=object),\n",
+ " 'yaxis': 'y',\n",
+ " 'z': {'bdata': ('5ZuWpeU9QED8nmEA1mtFQOR8bxYopE' ... '//////M0D1ufhH+R5fQAAAAAAAAPh/'),\n",
+ " 'dtype': 'f8',\n",
+ " 'shape': '24, 8'}}],\n",
+ " 'layout': {'coloraxis': {'colorbar': {'title': {'text': 'Boiler(Heat)|flow_rate'}},\n",
+ " 'colorscale': [[0.0, '#30123b'],\n",
+ " [0.07142857142857142, '#4145ab'],\n",
+ " [0.14285714285714285, '#4675ed'],\n",
+ " [0.21428571428571427, '#39a2fc'],\n",
+ " [0.2857142857142857, '#1bcfd4'],\n",
+ " [0.35714285714285715, '#24eca6'],\n",
+ " [0.42857142857142855, '#61fc6c'], [0.5,\n",
+ " '#a4fc3b'], [0.5714285714285714,\n",
+ " '#d1e834'], [0.6428571428571429,\n",
+ " '#f3c63a'], [0.7142857142857143,\n",
+ " '#fe9b2d'], [0.7857142857142857,\n",
+ " '#f36315'], [0.8571428571428571,\n",
+ " '#d93806'], [0.9285714285714286,\n",
+ " '#b11901'], [1.0, '#7a0402']]},\n",
+ " 'margin': {'t': 60},\n",
+ " 'template': '...',\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'timeframe'}},\n",
+ " 'yaxis': {'anchor': 'x', 'autorange': 'reversed', 'domain': [0.0, 1.0], 'title': {'text': 'timestep'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 20
+ },
+ {
+ "cell_type": "code",
+ "id": "48",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.849042Z",
+ "start_time": "2025-12-13T14:13:17.808302Z"
+ }
+ },
+ "source": [
+ "# Storage charge state heatmap\n",
+ "simple.statistics.plot.heatmap('ThermalStorage')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 2kB\n",
+ "Dimensions: (timeframe: 8, timestep: 24)\n",
+ "Coordinates:\n",
+ " * timeframe (timeframe) object 64B '2024-01-15' '2024-01-16' ... '2024-01-22'\n",
+ " * timestep (timestep) object 192B '00:00' '01:00' ... '22:00' '23:00'\n",
+ "Data variables:\n",
+ " value (timestep, timeframe) float64 2kB 250.0 1.379e-14 ... 102.5 nan, figure=Figure({\n",
+ " 'data': [{'coloraxis': 'coloraxis',\n",
+ " 'hovertemplate': ('timeframe: %{x}
timestep: %' ... 'rge_state: %{z}'),\n",
+ " 'name': '0',\n",
+ " 'type': 'heatmap',\n",
+ " 'x': array(['2024-01-15', '2024-01-16', '2024-01-17', '2024-01-18', '2024-01-19',\n",
+ " '2024-01-20', '2024-01-21', '2024-01-22'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': array(['00:00', '01:00', '02:00', '03:00', '04:00', '05:00', '06:00', '07:00',\n",
+ " '08:00', '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00',\n",
+ " '16:00', '17:00', '18:00', '19:00', '20:00', '21:00', '22:00', '23:00'],\n",
+ " dtype=object),\n",
+ " 'yaxis': 'y',\n",
+ " 'z': {'bdata': ('AAAAAABAb0DkBdNVug0PPZGJ+Pa5Lj' ... 'AAAAAAAADw5ELUzaBZQAAAAAAAAPh/'),\n",
+ " 'dtype': 'f8',\n",
+ " 'shape': '24, 8'}}],\n",
+ " 'layout': {'coloraxis': {'colorbar': {'title': {'text': 'ThermalStorage|charge_state'}},\n",
+ " 'colorscale': [[0.0, '#30123b'],\n",
+ " [0.07142857142857142, '#4145ab'],\n",
+ " [0.14285714285714285, '#4675ed'],\n",
+ " [0.21428571428571427, '#39a2fc'],\n",
+ " [0.2857142857142857, '#1bcfd4'],\n",
+ " [0.35714285714285715, '#24eca6'],\n",
+ " [0.42857142857142855, '#61fc6c'], [0.5,\n",
+ " '#a4fc3b'], [0.5714285714285714,\n",
+ " '#d1e834'], [0.6428571428571429,\n",
+ " '#f3c63a'], [0.7142857142857143,\n",
+ " '#fe9b2d'], [0.7857142857142857,\n",
+ " '#f36315'], [0.8571428571428571,\n",
+ " '#d93806'], [0.9285714285714286,\n",
+ " '#b11901'], [1.0, '#7a0402']]},\n",
+ " 'margin': {'t': 60},\n",
+ " 'template': '...',\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'timeframe'}},\n",
+ " 'yaxis': {'anchor': 'x', 'autorange': 'reversed', 'domain': [0.0, 1.0], 'title': {'text': 'timestep'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 21
+ },
+ {
+ "cell_type": "code",
+ "id": "49",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.900833Z",
+ "start_time": "2025-12-13T14:13:17.858196Z"
+ }
+ },
+ "source": [
+ "# Custom colorscale\n",
+ "simple.statistics.plot.heatmap('Office(Heat)', color_continuous_scale='Blues', title='Heat Demand Pattern')"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 2kB\n",
+ "Dimensions: (timeframe: 8, timestep: 24)\n",
+ "Coordinates:\n",
+ " * timeframe (timeframe) object 64B '2024-01-15' '2024-01-16' ... '2024-01-22'\n",
+ " * timestep (timestep) object 192B '00:00' '01:00' ... '22:00' '23:00'\n",
+ "Data variables:\n",
+ " value (timestep, timeframe) float64 2kB 32.48 27.28 31.72 ... 24.48 nan, figure=Figure({\n",
+ " 'data': [{'coloraxis': 'coloraxis',\n",
+ " 'hovertemplate': 'timeframe: %{x}
timestep: %{y}
Office(Heat)|flow_rate: %{z}',\n",
+ " 'name': '0',\n",
+ " 'type': 'heatmap',\n",
+ " 'x': array(['2024-01-15', '2024-01-16', '2024-01-17', '2024-01-18', '2024-01-19',\n",
+ " '2024-01-20', '2024-01-21', '2024-01-22'], dtype=object),\n",
+ " 'xaxis': 'x',\n",
+ " 'y': array(['00:00', '01:00', '02:00', '03:00', '04:00', '05:00', '06:00', '07:00',\n",
+ " '08:00', '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00',\n",
+ " '16:00', '17:00', '18:00', '19:00', '20:00', '21:00', '22:00', '23:00'],\n",
+ " dtype=object),\n",
+ " 'yaxis': 'y',\n",
+ " 'z': {'bdata': ('5ZuWpeU9QEDqSDirMEc7QB8FVNfUtz' ... 'AAAAAANECu5+If5Xs4QAAAAAAAAPh/'),\n",
+ " 'dtype': 'f8',\n",
+ " 'shape': '24, 8'}}],\n",
+ " 'layout': {'coloraxis': {'colorbar': {'title': {'text': 'Office(Heat)|flow_rate'}},\n",
+ " 'colorscale': [[0.0, 'rgb(247,251,255)'], [0.125,\n",
+ " 'rgb(222,235,247)'], [0.25,\n",
+ " 'rgb(198,219,239)'], [0.375,\n",
+ " 'rgb(158,202,225)'], [0.5,\n",
+ " 'rgb(107,174,214)'], [0.625,\n",
+ " 'rgb(66,146,198)'], [0.75,\n",
+ " 'rgb(33,113,181)'], [0.875,\n",
+ " 'rgb(8,81,156)'], [1.0,\n",
+ " 'rgb(8,48,107)']]},\n",
+ " 'template': '...',\n",
+ " 'title': {'text': 'Heat Demand Pattern'},\n",
+ " 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'timeframe'}},\n",
+ " 'yaxis': {'anchor': 'x', 'autorange': 'reversed', 'domain': [0.0, 1.0], 'title': {'text': 'timestep'}}}\n",
+ "}))"
+ ],
+ "text/html": [
+ ""
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 22
+ },
+ {
+ "cell_type": "markdown",
+ "id": "50",
+ "metadata": {},
+ "source": [
+ "## 6. Sankey Diagrams\n",
+ "\n",
+ "Sankey diagrams visualize energy flows through the system."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "51",
+ "metadata": {},
+ "source": [
+ "### 6.1 Flow Sankey\n",
+ "\n",
+ "Total energy flows:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "52",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.930662Z",
+ "start_time": "2025-12-13T14:13:17.908846Z"
+ }
+ },
+ "source": [
+ "simple.statistics.plot.sankey.flows()"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data= Size: 1kB\n",
+ "Dimensions: (link: 6)\n",
+ "Coordinates:\n",
+ " * link (link) int64 48B 0 1 2 3 4 5\n",
+ " source (link) \n",
+ " "
+ ]
+ },
+ "execution_count": 23,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 23
+ },
+ {
+ "cell_type": "code",
+ "id": "53",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-12-13T14:13:17.970954Z",
+ "start_time": "2025-12-13T14:13:17.939809Z"
+ }
+ },
+ "source": [
+ "# Complex system with multiple carriers\n",
+ "complex_sys.statistics.plot.sankey.flows()"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "PlotResult(data=