Metadata-Version: 2.4
Name: building_eload
Version: 0.10.0
Summary: District-level energy hourly simulation of buildings
Author-email: Yoann Chiche <yoann.chiche@minesparis.psl.eu>, Yassine Abdelouadoud <yassine.abdelouadoud@gmail.com>, Anna Cocchi <anna.cocchi@minesparis.psl.eu>
Maintainer-email: Yoann Chiche <yoann.chiche@minesparis.psl.eu>, Yassine Abdelouadoud <yassine.abdelouadoud@gmail.com>
License: The MIT License (MIT)
        =====================
        
        - Copyright © `2025` `Yoann Chiche`
        - Copyright © `2025` `Seddik Yassine Abdelouadoud`
        - Copyright © `2025` `Anna Cocchi`
        
        Permission is hereby granted, free of charge, to any person
        obtaining a copy of this software and associated documentation
        files (the “Software”), to deal in the Software without
        restriction, including without limitation the rights to use,
        copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the
        Software is furnished to do so, subject to the following
        conditions:
        
        The above copyright notice and this permission notice shall be
        included in all copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
        EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
        OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
        NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
        HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
        WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
        FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
        OTHER DEALINGS IN THE SOFTWARE.
        
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: <4.0.0,>=3.10
Description-Content-Type: text/markdown
License-File: LICENCE.md
Requires-Dist: numpy
Requires-Dist: pandas
Requires-Dist: polars
Requires-Dist: matplotlib
Requires-Dist: tqdm
Requires-Dist: colorlog
Requires-Dist: buildingdata[era5]>=0.2.0
Requires-Dist: heatpumpmodel>=0.1.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: ipykernel; extra == "dev"
Dynamic: license-file

# Building_eload

**Version:** `0.10.0`

![Python Version](https://img.shields.io/badge/python-3.10%2B-blue)
![PyPI](https://img.shields.io/pypi/v/building_eload)
![Pipeline](https://git.persee.minesparis.psl.eu/planeterr/building_eload/badges/main/pipeline.svg)

District-level building energy simulation at hourly resolution.

This model was first presented in the research article : <https://doi.org/10.1016/j.enbuild.2026.117409>

`building_eload` is developed as a **library**, released on PyPI. The Snakemake
workflow reproducing the paper's results lives in a separate repository
(`building_eload_paper`), pinned to the published `0.4.x` releases of this
package.

## Overview

The model is a two-stage pipeline, split across two packages since **0.9.0**:

1. **Static simulation** — annual building-level energy estimates and district
   calibration. This stage now lives in the separate
   [`buildingcalibration`](https://pypi.org/project/buildingcalibration/)
   package (`buildingcalibration.calibration`), together with the validation
   (`.validation`), the representative-district clustering (`.clustering`) and
   their figures (`.plots`).
2. **Dynamic simulation** — hourly electricity load profiles from those static
   outputs. **This package.**

`building_eload` is therefore a pure dynamic-simulation library: the hourly
end-use models (`models/`), the simulation orchestrator
(`core/dynamic_simulation`) and the load-curve plots. It does not import
`buildingcalibration`, and `buildingcalibration` does not import it — the
handover is either a set of parquet files on disk or an in-memory object
matching `core.dynamic_simulation.StaticResultsLike` (see
`DynamicSimulation.from_static_results` below).

Main dependencies:

- `buildingdata` (reference datasets: ELMAS non-residential load curves,
  occupant activity diaries, ERA5-derived climate)
- `heatpumpmodel` (the opt-in heat-pump performance core)

## Installation

```bash
git clone https://git.persee.minesparis.psl.eu/planeterr/building_eload.git
cd building_eload
pip install -e .
```

The static-calibration half of the pipeline is a separate install:

```bash
pip install buildingcalibration
```

## Data: the Caller Owns the Layout

**Since 0.10.0 this package has no path registry at all.** The `data_path`
dict and `plot_path` constant that used to live in `building_eload/__init__.py`
— eight `<checkout>/data/...` paths resolved at *import* time — are gone
(improvement plan, task G8), which completes for the dynamic core the doctrine
`buildingcalibration` was extracted under in 0.9.0:

1. **External open data is never a path.** It comes from a `buildingdata`
   getter, which owns download, cache, vintage and schema. Nothing needs a
   local `data/` tree out of the box.
2. **Pipeline intermediates are frames first, explicit paths second, and have
   no default.** A missing one raises a `ValueError` naming the parameter,
   instead of silently reading from someone else's checkout.

What that means per input:

| Input | How you supply it |
|---|---|
| Occupant activity diaries | nothing — defaults to `buildingdata.get_occupant_diaries()`. Or pass `DynamicParameters(activity_file=...)`: a parquet path, or an already-loaded `polars.DataFrame` (cheaper across many districts). |
| ELMAS non-residential load curves | nothing — `buildingdata.get_elmas(table=...)`, called by `NonResidentialModel`. |
| Climate (EPW) | the static results name the file; `DynamicParameters(climate_path=...)` is the root a bare name resolves against, and an absolute path bypasses it. Fetch one with `buildingdata.get_era5_climate(lat, lon, year)` and pass its path — the dynamic stage carries no district coordinates, so it cannot fetch one for you. |
| Static-stage results (this stage's inputs) | `DynamicSimulation.from_static_results(results, ...)` (in memory), or `DynamicParameters(input_path=...)` + `DynamicSimulation.from_files(...)` (parquet). |
| Dynamic results (this stage's outputs) | `DynamicParameters(output_path=...)`, required by `save_results()` only — a run that keeps its results in memory needs no path at all. `output_folder` is an optional run sub-directory of it. |
| Figures | `save_plot(..., save_path=...)`; unset, figures go to `<cwd>/plot`. |

The paper workflow's conventional tree (nothing in the library knows about it;
it is `data/` in the repo root, untracked and multi-GB):

```
data/
├── climate/                                # ERA5-derived weather files (EPW)
└── simulation/
    ├── static_simulation/<year>/           # -> DynamicParameters(input_path=...)
    └── dynamic_simulation/<year>/          # -> DynamicParameters(output_path=...)
```

The hermetic test suite (`pytest -m "not integration"`) never touches it; the
integration tests do, and they declare that layout themselves in
`building_eload/tests/conftest.py` (`BUILDING_ELOAD_DATA` overrides the root)
— in the caller, where it belongs.

## Current Core API

### Dynamic simulation (`building_eload.core.dynamic_simulation`)

Main classes:

- `DynamicParameters`
- `DynamicSimulation`

Primary methods used by users:

- `DynamicSimulation.from_files(district_id, parameters)`
- `DynamicSimulation.from_static_results(static_results, parameters)`
- `DynamicSimulation.run()`
- `DynamicSimulation.save_results()`
- `DynamicSimulation.plot_results(...)`

### The static-stage seam (`StaticResultsLike`)

`from_static_results` accepts **any object** carrying the seven attributes the
dynamic stage reads — `district_id`, `climate_year`, `climate_path`,
`climate_file`, `residential_buildings`, `dwellings`,
`non_residential_buildings`. That contract is published as a
`typing.Protocol`:

```python
from buildingcalibration.calibration import StaticSimulation, StaticParameters
from building_eload.core.dynamic_simulation import (
    DynamicParameters, DynamicSimulation, StaticResultsLike,
)

static_results = StaticSimulation("262320000", StaticParameters(...)).run()
assert isinstance(static_results, StaticResultsLike)  # runtime-checkable

sim = DynamicSimulation.from_static_results(static_results, DynamicParameters(year=2023))
```

Neither package imports the other; the `isinstance` check is the whole
coupling. Passing files instead (`from_files`) needs no adapter at all.

### Static calibration, validation, clustering

Moved to `buildingcalibration` in 0.9.0 — same public names, new import paths:
`buildingcalibration.calibration` (`StaticParameters`, `StaticSimulation`,
`StaticResults`, `BuildingModelResults`, `StaticProcessor`, `BuildingLoader`,
the representative-district selectors), `buildingcalibration.validation`
(`Validation`), `buildingcalibration.clustering` (`cluster_districts`,
`screen_unreliable_iris*`) and `buildingcalibration.plots`.

## Minimal Usage

Tutorials for the dynamic simulation are available in `/doc/tutorials`; the
static/validation ones moved with their domain (see
`doc/tutorials/moved_to_buildingcalibration.md`).

### 1) Dynamic simulation

```python
from building_eload.core.dynamic_simulation import DynamicParameters, DynamicSimulation


district_id = "262320000"
params = DynamicParameters(
    year=2023,
    run_non_residential=True,
    run_again=True,
)

sim = DynamicSimulation.from_files(district_id=district_id, parameters=params)
sim.run()
sim.save_results()
```

### 2) Dynamic simulation with the heat-pump model (opt-in)

The conversion of hourly heating *demand* into heating *electricity* happens in
a single place, `building_eload.models.heating_system`. Leaving
`DynamicSimulation.heating_system` at `None` keeps the published
constant-efficiency (static-COP) behaviour; assigning a converter swaps the
model without touching anything else:

```python
from building_eload.models import heat_pump as hp
from building_eload.models.heat_pump_system import HeatPumpHeatingSystem

# Defaults: air-to-water, medium-temperature radiators with weather
# compensation, inverter, monovalent with an electric-resistance backup --
# the reference configuration of Rogeau et al. (2024). Applied to the
# buildings whose `heating_system` is "electric heat pump"; every other
# building keeps the published conversion.
sim.heating_system = HeatPumpHeatingSystem(
    hp.HeatPumpConfig(
        system=hp.System.A_W,
        mode=hp.Mode.M,
        emitter=hp.Emitter.FH,        # floor heating; the biggest SCOP lever
        technology=hp.Technology.INVERTER,
    )
)
sim.run()
```

The hourly results then carry `heat_pump_electricity_need` and
`heating_backup_electricity_need` alongside the usual
`heating_electricity_need` (their sum), plus `heat_pump_heat_delivered`.
Air-source configurations require the weather frame's `humidity` column and
fail loudly without it, rather than silently disabling the defrost derate.

## Running Simulations

The former `building_eload.scripts` entry points were removed in 0.8.0; use
the public API instead (the tutorials in `doc/tutorials/` walk through each
step):

- `building_eload.core.dynamic_simulation.DynamicSimulation` — dynamic hourly
  simulation (this package)
- `buildingcalibration.calibration.StaticSimulation` — static annual
  calibration (moved out in 0.9.0)
- `buildingcalibration.validation.Validation` — validation against measured
  consumption (moved out in 0.9.0)
- `buildingcalibration.clustering.cluster_districts` — representative-district
  selection (moved out in 0.9.0)
- EPW climate generation lives in the `buildingdata` package
  (`buildingdata.prefetch_era5` for the bulk ERA5 download,
  `buildingdata.get_era5_climate` for per-point EPW files)

For an end-to-end orchestrated pipeline, see the Snakemake workflow in the
`building_eload_paper` repository.
