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.ipynbStaticParameters / 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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

buildingcalibration-0.2.2.tar.gz (100.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

buildingcalibration-0.2.2-py3-none-any.whl (113.4 kB view details)

Uploaded Python 3

File details

Details for the file buildingcalibration-0.2.2.tar.gz.

File metadata

  • Download URL: buildingcalibration-0.2.2.tar.gz
  • Upload date:
  • Size: 100.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.15

File hashes

Hashes for buildingcalibration-0.2.2.tar.gz
Algorithm Hash digest
SHA256 fe30dac9c6e91e026f17a34c37c7a7604b87f398b1ccdf60bcef2da11bb35c7e
MD5 6f06812ca31edfbeb236882ec06490a4
BLAKE2b-256 18e13a78b000fe49e123c430a365bc43b6ed7c2997c5083e56c3dd339d14247a

See more details on using hashes here.

File details

Details for the file buildingcalibration-0.2.2-py3-none-any.whl.

File metadata

File hashes

Hashes for buildingcalibration-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 61ad37d14c25779231b99315d018ac99bb0afa2aee13db41a80f113c32cf969c
MD5 962bc40e31b19d4cba8fa34d1b640c39
BLAKE2b-256 8e12d2e29e41e1b792c53ea9f75326b227ae040d12e16e1908899af96b82a16f

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 files

0.2.0

2 files

0.1.0

2 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