Metadata-Version: 2.3
Name: mpctrano
Version: 0.3.0
Summary: Calibration, state estimation and economic model predictive control of the FastModel building models generated by Trano (rumoca + CasADi + IPOPT).
Keywords: model predictive control,building,calibration,state estimation,modelica,casadi,ipopt,trano
Author: Ando Andriamamonjy
Author-email: Ando Andriamamonjy <andoludovic.andriamamonjy@gmail.com>
License: LGPL-3.0-or-later
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Requires-Dist: trano>=0.57.0,<1
Requires-Dist: trano-optimization>=0.2.0,<1
Requires-Dist: rumoca>=0.10.2,<0.11
Requires-Dist: casadi>=3.6.7,<4
Requires-Dist: numpy>=1.26
Requires-Dist: pandas>=2.2.2,<3
Requires-Dist: pydantic>=2.7,<3
Requires-Dist: typer>=0.12,<1
Requires-Dist: ipywidgets>=8.1 ; extra == 'notebook'
Requires-Dist: plotly>=5.22 ; extra == 'notebook'
Requires-Dist: pyarrow>=15 ; extra == 'notebook'
Requires-Dist: plotly>=5.22 ; extra == 'plot'
Requires-Python: >=3.11, <4.0
Project-URL: Repository, https://github.com/andoludo/mpctrano
Provides-Extra: notebook
Provides-Extra: plot
Description-Content-Type: text/markdown

# mpctrano

**Calibration, state estimation and economic model predictive control (MPC) of the building models generated by
[Trano](https://github.com/andoludo/trano).**

Trano turns a YAML description of a building into Modelica models. Its `FastModel` library writes a model close to
the Buildings library (layered walls, windows with their angular optics, the hydronic circuits with their radiators,
valves, mixing valves, boilers and air-to-water heat pumps, ventilation with heat recovery, PV) that
[rumoca](https://github.com/cognipilot/rumoca) translates into a [CasADi](https://web.casadi.org/) ODE. mpctrano
takes the `FastModelMPC` version of it, whose controls are the inputs of the systems, and:

1. **calibrates** groups of its parameters on monitoring data of the real building (IPOPT least squares),
2. **estimates** the unmeasured states (walls, water) by moving horizon estimation,
3. **controls** the valves, the heat pump or the boiler with an economic MPC that keeps comfort at the lowest cost
   of the energy the systems draw, read from their meters, under a tariff: flat, time-of-use, day-ahead prices,
   demand response events or a capacity charge, which exposes the **flexibility** of the building to the grid,
4. **compares** it with the controllers the YAML describes, simulated on the same building (the closed-loop
   `FastModel`).

All optimisation problems collocate the model on Radau points (`trano_optimization`) and are solved with
[IPOPT](https://coin-or.github.io/Ipopt/).

```
 house.yaml ──trano (FastModelMPC)──▶ building_mpc ──rumoca──▶ CasADi ODE  xdot = f(t, x, u, p)
                                                                    │
   monitoring.csv ──▶ MonitoringData ──▶ Calibrator (IPOPT) ──▶ calibrated parameter groups
                                                                    │
   weather .mos + occupancy of the YAML ──▶ WeatherForecaster ──▶ d(t)
   tariff + comfort band ─────────────────────────────▶ ModelPredictiveController (IPOPT)
   measured air temperatures ──▶ MovingHorizonEstimator ──▶ x̂ ──▶ valves, heat pump, boiler
 house.yaml ──trano (FastModel)─────▶ the controllers of the YAML: the rule-based reference
```

## Installation

```bash
git clone https://github.com/andoludo/mpctrano && cd mpctrano
uv sync                        # trano, trano-optimization, casadi (IPOPT bundled) and rumoca built from source
```

The CasADi export of the FastModel models needs rumoca 0.10.3 (rumoca main up to commit `c74cddf`), which is not on
PyPI yet: `uv sync` builds it from the commit pinned in `pyproject.toml` (a Rust toolchain is needed, about
10 minutes the first time, cached afterwards). With pip, rumoca 0.10.2 from PyPI would install but cannot translate
the FastModel models, so install rumoca first:

```bash
pip install "rumoca @ git+https://github.com/CogniPilot/rumoca@c74cddff6d7669fc2c3603d43529ab62e0964072#subdirectory=crates/rumoca-bind-python"
pip install "mpctrano[plot]"
```

## Quick start

```python
from mpctrano import MonitoringData, Project, compare, scenario_catalogue

project = Project.from_yaml("house.yaml")  # Trano generates the FastModelMPC model
data = MonitoringData.from_csv("monitoring.csv", temperature_unit="C")
result = project.calibrate(data, validation_fraction=0.25)
print(result.factors, result.metrics())  # the parameter groups, RMSE before/after, training and validation

scenario = scenario_catalogue(start="2023-01-16", days=2)["time_of_use"]
runs = {"mpc": project.run(scenario), "rule based": project.run_rule_based(scenario)}
print(compare(runs, baseline="rule based"))  # cost, energy, comfort, peak, shifted energy
```

The same steps from the command line:

```bash
mpctrano generate house.yaml --closed-loop                      # house.mo (+ house.closed_loop.mo)
mpctrano synthetic-data house.yaml --output monitoring.csv     # data of a perturbed copy, to try the package
mpctrano calibrate house.yaml monitoring.csv                   # house.calibration.json
mpctrano run house.yaml --calibrated house.calibration.json --scenario time_of_use --days 2 --rule-based
mpctrano scenarios
```

The weather file of the model (the `.mos` file its weather reader names) is looked up next to the YAML; give
another one with `weather=` (`--weather`). No real building at hand? `Project.true_building()` sets the parameter
groups of the model to random values to play the "real" building and `Project.synthetic_data()` records monitoring
data on it, operated by a thermostat with a random set point; this is what the tests and the tutorials use.

## The model

`FastModel.from_yaml("house.yaml")` reads everything from the Modelica model: the **controls** (the inputs with a
`min` and a `max`: valve positions, mixing valves, heat pump speed or boiler load between 0 and 1, the heat of ideal
emitters), the **disturbances** (the weather channels `weather_inputs.*` and the internal gains per floor area of
each occupancy model), the **comfort temperatures** (`<zone>.TAir`) and the **meters** (states in kWh: `EEle` the
electricity of a heat pump, `EFue` the fuel of a boiler, `EFan`, `EPro`). The model depends on the time of the year
(the solar position is computed inside it) and is stiff (water nodes): it is simulated with CVODES and optimised on
Radau collocation points. `FastModel.from_yaml("house.yaml", library="FastModel")` gives the closed loop of the
same building, with its controllers.

## Monitoring data

A regularly sampled CSV (or `DataFrame`) whose columns are named after the variables of the model:

| Column                                | Unit | Role                                                  |
|---------------------------------------|------|-------------------------------------------------------|
| `time`                                |      | timestamp (first column by default)                   |
| `<zone>.TAir`                         | K/°C | measured air temperature of a zone (at least one)     |
| `valve_001_y.y`, `boiler_001_y.y`, ... | -   | the controls applied                                  |
| `boiler_001.EEle`, `boiler_001.EFue`  | kWh  | meter readings, cumulative (optional)                 |
| `weather_inputs.*`, `<occupancy>_gai.*` |    | disturbances (optional, filled from the weather file) |

## Calibration

`Calibrator` adjusts groups of parameters, each by one factor or one value: the conductances and the capacities of
the envelope, the heat capacity of the air, the infiltration rate and the efficiency of the boilers (from the fuel
meter). It solves the least-squares problem on the Radau collocation of the whole period, the states free and the
dynamics as constraints, regularised towards the Trano values. On the boiler house of the tests, four days of data
recover the true groups within a few percent and bring the air temperature error from 4.5 K to 0.06 K.

## State estimation

`MovingHorizonEstimator` finds the trajectory of the model closest to the measured air temperatures over a sliding
window (trano's moving horizon estimation), with the model errors and the prior of the window weighted: started
2 K off, it recovers the 26 temperature states of the test house within a day.

## MPC, tariffs and flexibility

`ModelPredictiveController` minimises, over a receding horizon,

    cost of the electricity bought and of the fuel (read from the meters) + capacity charge × peak electric power
    + comfort penalties (soft band) + penalty above a demand response power limit + moves of the controls

subject to the collocated FastModel and the bounds of the controls. The problem is built once and warm started.

Tariffs (`mpctrano.tariff`): `FlatTariff`, `TimeOfUseTariff`, `DynamicTariff` (a price series, e.g. day-ahead prices
from a CSV), `DemandResponseEvent` (price multiplier and/or power cap over a period) and a `capacity_charge` on the
peak. Comfort (`mpctrano.comfort`): occupied/unoccupied bands per zone; `ComfortSpec.map("widen", 1.0)` opens the
band to trade comfort for flexibility.

`scenario_catalogue()` ships ready-made scenarios (`flat`, `time_of_use`, `dynamic_price`, `demand_response`,
`peak_shaving`, `wide_comfort_band`, `imperfect_forecast`); `compare(runs, baseline=...)` reports the cost savings,
the energy shifted in time, the share of energy bought below the median price, the comfort violations and the peak
power of each run, all from the meters of the simulated building. On the boiler house of the tests, over a January
day, the MPC costs 12 % less than the controllers of the YAML with almost no discomfort.

## Tutorials

The interactive notebooks live in `tutorials/` (see `tutorials/README.md`; executed on
[andoludo.github.io/mpctrano](https://andoludo.github.io/mpctrano/), each with an *Open in Colab* badge;
`06_your_building` takes the building as a YAML cell). The `tutorials/package/` scripts walk
through the package on a single-zone house heated by an air-to-water heat pump from the Trano test suite
(`uv run python tutorials/package/01_model_from_trano.py`, figures written to `tutorials/output/`):

1. `01_model_from_trano.py` - generate the model, its controls, disturbances and meters, simulate it.
2. `02_monitoring_data_and_calibration.py` - monitoring CSV, validation report, calibration, fit plots.
3. `03_state_estimation.py` - moving horizon estimation against an uncorrected model.
4. `04_mpc_comfort.py` - one optimal plan, the closed loop against the "real" building, the rule-based reference.
5. `05_tariffs_and_flexibility.py` - scenarios, custom tariffs, demand response, comfort band vs cost.
6. `06_full_pipeline.py` - the whole workflow with `Project` in a few lines.

More in `docs/`: [workflow](docs/index.md), [monitoring data](docs/data.md), [calibration](docs/calibration.md),
[state estimation](docs/estimation.md), [MPC and tariffs](docs/mpc.md).

## Development

```bash
uv sync
make linting      # ruff format, ruff check, mypy (strict)
make tests        # pytest (fast tests)
make tests-all    # including the slow scenario and tutorial tests
make notebooks    # execute the notebooks (quick mode)
```

Trano is only read: mpctrano consumes the `FastModel` library and `trano_optimization` and never modifies them.

## License

LGPL-3.0-or-later, like Trano.
