Skip to main content

buildingcalibration

District-scale static calibration, validation and representative-district clustering for the building-energy model family.

It covers the annual (non-hourly) half of the modelling chain:

  • Calibration — the annual static calibration loop that fits a building stock's energy model over buildingmodel's static inference.
  • Validation — the Eq. 5 error decomposition of simulated versus measured district consumption (ORE / Enedis), read through buildingdata.
  • Clustering — reduction of a national building stock to representative districts, plus screening of districts whose measured data cannot support calibration.
  • Plots — the figures for both stages.

Status

0.2.0 is released (tagged and on PyPI). 0.1.0 shipped the modules extracted from building_eload's core — core/static_simulation, core/validation, core/clustering.py, core/unreliable_districts.py, core/building_loader.py, utils/district_list.py, plots/static_calibration.py, plots/validation.py. 0.2.0 added the two API items the paper-reproduction workflow was missing — build_representative_districts/write_representative_districts and run_validation_sweep/average_over_years — plus plots.clustering. See the CHANGELOG for the full history.

Tutorials

doc/tutorials/ has two worked examples, rebuilt against the current API (not carried over from building_eload's retired copies):

  • tutorial_01_static_simulation.ipynb — StaticParameters / StaticSimulation / StaticResults for one district.
  • tutorial_03_validation.ipynb — district clustering (cluster_districts, build_representative_districts/ write_representative_districts) then Validation against measured consumption, plus run_validation_sweep/average_over_years for parametric studies.

The clustering steps run on synthetic data with no network needed; steps that need real BDTOPO/census/ORE/Enedis data or a real dynamic-simulation run are marked and left unexecuted. building_eload's own doc/tutorials/tutorial_00_data_download.ipynb and tutorial_02_dynamic_simulation.ipynb cover reference-data acquisition and the hourly stage these two hand off to/from.

Quick start

Calibrate one district (static, annual)

from buildingcalibration import StaticParameters, StaticSimulation

parameters = StaticParameters(
    n_inference=5,            # stochastic building-stock draws to average over
    calibration_year=2023,
    climate_year=2023,
    output_root="results",    # -> results/static_simulation/2023/
)
results = StaticSimulation("751010101", parameters).run()
results.save_results()

Nothing else is needed: the building footprints (BDTOPO), the IRIS district layer and the ORE per-IRIS annual consumption the calibration fits against are all fetched through buildingdata. Pass calibration_file= (a frame or a path) to pin a vintage, or building_footprint_folder= to use a local BDTOPO mirror.

Validate reconstructed load curves against measurements

from buildingcalibration import Validation

validation = Validation(
    year=2023,
    n_clusters=20,
    scope="national",
    building_type="residential",
    input_path="results/dynamic_simulation/2023",          # hourly parquets
    output_path="results/validation/2023",
    clustering_file=clustering_frame,                      # frame or path
    unreliable_districts=unreliable_iris_frame,            # frame, ids or path
)
validation.run()
error = validation.get_error()                             # paper Eq. 5 terms

The measured Enedis load curves come from buildingdata (get_enedis_national() / get_enedis_regional()) when validation_residential_file= is left unset.

Reduce a stock to representative districts

from buildingcalibration import cluster_districts
from buildingcalibration.clustering import screen_unreliable_iris_from_parquet

unreliable = screen_unreliable_iris_from_parquet("enedis_iris_consumption.parquet")

result = cluster_districts(
    district_data,                                  # one row per district
    n_clusters=20,
    feature_columns=["heating_needs", "dhw_needs", "specific_needs"],
    unreliable_ids=unreliable["code_iris"],         # never pick these as medoids
    seed=42,
)
result.medoid_ids        # the districts to simulate
result.scaling_weights   # count-based multiplier per medoid

Publish the representative-districts table the rest of the chain reads

cluster_districts() answers one cut at a time; the validation stage and the medoid selectors read a single wide table holding several cuts side by side (cluster_label_<key> / cluster_medoid_<key> per cut, plus the district attributes). build_representative_districts() is the converter and write_representative_districts() the thin writer:

from buildingcalibration import write_representative_districts
from buildingcalibration.clustering import (
    cluster_districts,
    cluster_key_from_fraction,
    representative_districts_file,
)

results = {n: cluster_districts(features, n_clusters=n, seed=42) for n in (10, 20)}

write_representative_districts(
    results,
    features,                                        # one row per district
    representative_districts_file("results/clustering", scope="national"),
)

Key convention — the column suffix is always a positive integer: the cluster count for a national table (cluster_medoid_20), the integer percent of districts kept for a regional one (cluster_medoid_5 is 5 %). Fractions are rejected, not guessed: convert once with cluster_key_from_fraction(0.05) -> 5. representative_districts_file() is the single definition of the conventional file name, shared with the readers (get_representative_districts(), Validation(clustering_path=...)), so a table can never be written under one name and looked up under another.

Sweep a parameter and decompose the error per variant

from buildingcalibration import average_over_years, run_validation_sweep

frame = run_validation_sweep(
    {
        f"heating_threshold={value:g}": [
            {"year": year, "input_path": f"results/dynamic/{value:g}/{year}"}
            for year in (2021, 2022, 2023)
        ]
        for value in (14.0, 15.0, 16.0)
    },
    common={"n_clusters": 20, "scope": "national", "clustering_file": table,
            "unreliable_districts": unreliable},
)
# frame: long, one row per (label, year, metric) -- the full Eq. 5 terms
average_over_years(frame)   # one row per (label, metric)

Each case carries its own inputs (frames or explicit paths); the sweep invents no path and writes nothing.

Doctrine: frames first, no path registry

Nothing in this package resolves a filesystem path at import time, and there is no data/ tree to install:

  • Pipeline intermediates — dynamic-simulation results, clustering tables, the unreliable-district list, output directories — are passed in as in-memory frames or as explicit paths, and have no default. A missing one raises a ValueError naming the parameter rather than reading from somewhere you did not choose.
  • External open data — BDTOPO footprints, IRIS districts, ORE annual consumption, Enedis measured load curves — defaults to a buildingdata getter (get_bdtopo(), get_districts(), get_ore(), get_enedis_national() / get_enedis_regional()), which owns the download, the cache and the vintage. Pass a frame or a path to pin a specific vintage instead.

The one exception is the SDES parc résidentiel workbook read by plots.validation.read_sdes_data(): buildingdata has no getter for it yet, so it is a required frame-or-path argument (and reading the .xlsx form needs openpyxl, which is not a declared dependency).

Relationship to building_eload

buildingcalibration and building_eload have no import relationship in either direction. building_eload becomes a pure hourly dynamic-simulation library; this package owns the static/annual side. The two are coupled only by parquet data contracts on disk — a calibrated stock written here is read there, and vice versa. That seam already existed inside the old monolith; the split just makes it a package boundary. A structural test (tests/test_data_path_doctrine.py) enforces both halves of that: no path registry, and no building_eload import anywhere in the package.

The family

Package Role Host
buildingdata dataset access layer: BDTOPO/WFS, ERA5, INSEE census, Enedis/ORE, ELMAS gitlab.com/energytransition
buildingmodel static inference engine: building-stock physical characteristics and annual demand gitlab.com/energytransition
heatpumpmodel shared heat-pump seasonal-performance physics (Rogeau et al. 2024) git.persee
buildingcalibration static calibration, validation, representative-district clustering git.persee
building_eload hourly dynamic simulation of district electric load git.persee
building_eload_paper Snakemake reproduction workflow for the published paper; pinned to building_eload==0.4.3 and unaffected by this split git.persee

Install

pip install buildingcalibration
# or, from a checkout:
pip install -e ".[dev]"

Python 3.10–3.13.

Tests

pytest                       # full suite
pytest -m "not integration"  # hermetic subset, what CI runs

The hermetic subset needs no data and no network. The integration tests run the calibration and validation stages on real districts; they read a local reference-data tree, <repo>/data by default, overridable with the BUILDING_ELOAD_DATA environment variable (the name is shared with building_eload on purpose, so one setting covers both checkouts).

One integration test — tests/integration/test_static_to_dynamic_seam.py, the cross-package end-to-end seam — additionally needs building_eload installed. It is an optional test-time requirement only: building_eload is not a runtime dependency, not part of the dev extra, and the library itself must never import it (a structural test enforces that). Install it yourself if you want to run that test; otherwise it skips.

Licence

MIT — see LICENSE.

Metadata

Release files for buildingcalibration 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for buildingcalibration 0.3.0
File Size Uploaded
buildingcalibration-0.3.0.tar.gz 102.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for buildingcalibration 0.3.0
File Interpreter ABI Platform
buildingcalibration-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 218.1 kB

Release files / buildingcalibration-0.3.0.tar.gz

Download URL buildingcalibration-0.3.0.tar.gz
Size 102.9 kB
Tags Source
SHA-256 checksum
How to use checksums
d7af6024c2f7cb1bad9bd499292d39c8c6b9970319c53559da26bf8b982eefc4
BLAKE2b-256 checksum
How to use checksums
bb88f4ad77aff7675785444bb1d6c8a91442c06bdb8a7fe5949fe2295e8d29fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release files / buildingcalibration-0.3.0-py3-none-any.whl

Download URL buildingcalibration-0.3.0-py3-none-any.whl
Size 115.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3f1ed74955d430142c31794d4f00ca5ca3f1ac852f9717bdf50a5d4ceaf52630
BLAKE2b-256 checksum
How to use checksums
2bf53da5c8f12168fc00e54fa5b1d9b49f8501cace4039ef81e50cd637588fe4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release history Release notifications | RSS feed

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page