building-eload 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- building_eload-0.5.0/LICENCE.md +27 -0
- building_eload-0.5.0/PKG-INFO +274 -0
- building_eload-0.5.0/README.md +197 -0
- building_eload-0.5.0/building_eload/__init__.py +31 -0
- building_eload-0.5.0/building_eload/core/__init__.py +0 -0
- building_eload-0.5.0/building_eload/core/building_loader.py +127 -0
- building_eload-0.5.0/building_eload/core/clustering.py +448 -0
- building_eload-0.5.0/building_eload/core/dynamic_simulation.py +1242 -0
- building_eload-0.5.0/building_eload/core/static_simulation.py +1035 -0
- building_eload-0.5.0/building_eload/core/validation.py +1046 -0
- building_eload-0.5.0/building_eload/models/__init__.py +0 -0
- building_eload-0.5.0/building_eload/models/cooking.py +225 -0
- building_eload-0.5.0/building_eload/models/dwelling.py +339 -0
- building_eload-0.5.0/building_eload/models/heat_pump.py +836 -0
- building_eload-0.5.0/building_eload/models/heating.py +728 -0
- building_eload-0.5.0/building_eload/models/hot_water.py +602 -0
- building_eload-0.5.0/building_eload/models/non_residential.py +495 -0
- building_eload-0.5.0/building_eload/models/occupancy.py +129 -0
- building_eload-0.5.0/building_eload/models/specific.py +123 -0
- building_eload-0.5.0/building_eload/models/vacations.py +397 -0
- building_eload-0.5.0/building_eload/models/weather.py +121 -0
- building_eload-0.5.0/building_eload/plots/__init__.py +0 -0
- building_eload-0.5.0/building_eload/plots/clustering.py +463 -0
- building_eload-0.5.0/building_eload/plots/dynamic_model.py +511 -0
- building_eload-0.5.0/building_eload/plots/static_calibration.py +268 -0
- building_eload-0.5.0/building_eload/plots/validation.py +1463 -0
- building_eload-0.5.0/building_eload/scripts/__init__.py +0 -0
- building_eload-0.5.0/building_eload/scripts/calibration/__init__.py +0 -0
- building_eload-0.5.0/building_eload/scripts/calibration/run_plot_static_calibration_results.py +124 -0
- building_eload-0.5.0/building_eload/scripts/climate/generate_climates.py +425 -0
- building_eload-0.5.0/building_eload/scripts/clustering/__init__.py +0 -0
- building_eload-0.5.0/building_eload/scripts/clustering/annual_energy_from_clustering.py +67 -0
- building_eload-0.5.0/building_eload/scripts/clustering/district_dhu.py +346 -0
- building_eload-0.5.0/building_eload/scripts/clustering/national_district_clustering.py +500 -0
- building_eload-0.5.0/building_eload/scripts/clustering/regional_district_clustering.py +393 -0
- building_eload-0.5.0/building_eload/scripts/clustering/run_multiple_clustering.py +212 -0
- building_eload-0.5.0/building_eload/scripts/simulation/dynamic/run.py +77 -0
- building_eload-0.5.0/building_eload/scripts/simulation/dynamic/run_parallel.py +205 -0
- building_eload-0.5.0/building_eload/scripts/simulation/static/run.py +37 -0
- building_eload-0.5.0/building_eload/scripts/simulation/static/run_parallel.py +182 -0
- building_eload-0.5.0/building_eload/scripts/validation/run.py +67 -0
- building_eload-0.5.0/building_eload/utils/__init__.py +0 -0
- building_eload-0.5.0/building_eload/utils/district_list.py +105 -0
- building_eload-0.5.0/building_eload/utils/epw_reader.py +135 -0
- building_eload-0.5.0/building_eload/utils/logger.py +54 -0
- building_eload-0.5.0/building_eload/utils/plot_utils.py +20 -0
- building_eload-0.5.0/building_eload/utils/seeding.py +102 -0
- building_eload-0.5.0/building_eload.egg-info/PKG-INFO +274 -0
- building_eload-0.5.0/building_eload.egg-info/SOURCES.txt +52 -0
- building_eload-0.5.0/building_eload.egg-info/dependency_links.txt +1 -0
- building_eload-0.5.0/building_eload.egg-info/requires.txt +30 -0
- building_eload-0.5.0/building_eload.egg-info/top_level.txt +1 -0
- building_eload-0.5.0/pyproject.toml +118 -0
- building_eload-0.5.0/setup.cfg +4 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
=====================
|
|
3
|
+
|
|
4
|
+
- Copyright © `2025` `Yoann Chiche`
|
|
5
|
+
- Copyright © `2025` `Seddik Yassine Abdelouadoud`
|
|
6
|
+
- Copyright © `2025` `Anna Cocchi`
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person
|
|
9
|
+
obtaining a copy of this software and associated documentation
|
|
10
|
+
files (the “Software”), to deal in the Software without
|
|
11
|
+
restriction, including without limitation the rights to use,
|
|
12
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the
|
|
14
|
+
Software is furnished to do so, subject to the following
|
|
15
|
+
conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be
|
|
18
|
+
included in all copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
21
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
22
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
23
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
24
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
25
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
26
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
27
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: building_eload
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: District-level energy hourly simulation of buildings
|
|
5
|
+
Author-email: Yoann Chiche <yoann.chiche@minesparis.psl.eu>, Yassine Abdelouadoud <yassine.abdelouadoud@gmail.com>, Anna Cocchi <anna.cocchi@minesparis.psl.eu>
|
|
6
|
+
Maintainer-email: Yoann Chiche <yoann.chiche@minesparis.psl.eu>, Yassine Abdelouadoud <yassine.abdelouadoud@gmail.com>
|
|
7
|
+
License: The MIT License (MIT)
|
|
8
|
+
=====================
|
|
9
|
+
|
|
10
|
+
- Copyright © `2025` `Yoann Chiche`
|
|
11
|
+
- Copyright © `2025` `Seddik Yassine Abdelouadoud`
|
|
12
|
+
- Copyright © `2025` `Anna Cocchi`
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person
|
|
15
|
+
obtaining a copy of this software and associated documentation
|
|
16
|
+
files (the “Software”), to deal in the Software without
|
|
17
|
+
restriction, including without limitation the rights to use,
|
|
18
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
19
|
+
copies of the Software, and to permit persons to whom the
|
|
20
|
+
Software is furnished to do so, subject to the following
|
|
21
|
+
conditions:
|
|
22
|
+
|
|
23
|
+
The above copyright notice and this permission notice shall be
|
|
24
|
+
included in all copies or substantial portions of the Software.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
27
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
28
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
29
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
30
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
31
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
32
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
33
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
34
|
+
|
|
35
|
+
Classifier: Development Status :: 3 - Alpha
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
38
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
39
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
44
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
45
|
+
Requires-Python: <4.0.0,>=3.10
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
License-File: LICENCE.md
|
|
48
|
+
Requires-Dist: numpy
|
|
49
|
+
Requires-Dist: pandas
|
|
50
|
+
Requires-Dist: polars
|
|
51
|
+
Requires-Dist: scipy
|
|
52
|
+
Requires-Dist: matplotlib
|
|
53
|
+
Requires-Dist: seaborn
|
|
54
|
+
Requires-Dist: scikit-learn
|
|
55
|
+
Requires-Dist: geopandas
|
|
56
|
+
Requires-Dist: shapely
|
|
57
|
+
Requires-Dist: xarray
|
|
58
|
+
Requires-Dist: netcdf4
|
|
59
|
+
Requires-Dist: tqdm
|
|
60
|
+
Requires-Dist: fastcluster
|
|
61
|
+
Requires-Dist: ladybug-core
|
|
62
|
+
Requires-Dist: colorlog
|
|
63
|
+
Requires-Dist: requests
|
|
64
|
+
Requires-Dist: py7zr
|
|
65
|
+
Requires-Dist: pyproj
|
|
66
|
+
Requires-Dist: buildingmodel
|
|
67
|
+
Requires-Dist: buildingdata[era5]>=0.2.0
|
|
68
|
+
Provides-Extra: maps
|
|
69
|
+
Requires-Dist: contextily; extra == "maps"
|
|
70
|
+
Requires-Dist: folium; extra == "maps"
|
|
71
|
+
Provides-Extra: dev
|
|
72
|
+
Requires-Dist: pytest; extra == "dev"
|
|
73
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
74
|
+
Requires-Dist: ruff; extra == "dev"
|
|
75
|
+
Requires-Dist: ipykernel; extra == "dev"
|
|
76
|
+
Dynamic: license-file
|
|
77
|
+
|
|
78
|
+
# Building_eload
|
|
79
|
+
|
|
80
|
+
**Version:** `0.4.0`
|
|
81
|
+
|
|
82
|
+

|
|
83
|
+

|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
District-level building energy simulation at hourly resolution.
|
|
87
|
+
|
|
88
|
+
This model was first presented in the research article : <https://doi.org/10.1016/j.enbuild.2026.117409>
|
|
89
|
+
|
|
90
|
+
`building_eload` is developed as a **library**, released on PyPI. The Snakemake
|
|
91
|
+
workflow reproducing the paper's results lives in a separate repository
|
|
92
|
+
(`building_eload_paper`), pinned to the published `0.4.x` releases of this
|
|
93
|
+
package.
|
|
94
|
+
|
|
95
|
+
## Overview
|
|
96
|
+
|
|
97
|
+
`building_eload` runs a two-stage pipeline:
|
|
98
|
+
|
|
99
|
+
1. Static simulation: annual building-level energy estimates and calibration.
|
|
100
|
+
2. Dynamic simulation: hourly electricity load profiles from static outputs.
|
|
101
|
+
|
|
102
|
+
Main dependency:
|
|
103
|
+
|
|
104
|
+
- `buildingmodel` (for static building physics simulation)
|
|
105
|
+
|
|
106
|
+
## Installation
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
git clone https://git.persee.minesparis.psl.eu/planeterr/building_eload.git
|
|
110
|
+
cd building_eload
|
|
111
|
+
pip install -e .
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If needed by your workflow, also install `buildingmodel` in editable mode.
|
|
115
|
+
|
|
116
|
+
## Data Paths Exposed by the Package
|
|
117
|
+
|
|
118
|
+
Defined in `building_eload/__init__.py`:
|
|
119
|
+
|
|
120
|
+
- `data_path["data"]`
|
|
121
|
+
- `data_path["bdtopo"]`
|
|
122
|
+
- `data_path["elmas"]`
|
|
123
|
+
- `data_path["simulation"]`
|
|
124
|
+
- `data_path["activity_calendar"]`
|
|
125
|
+
- `data_path["validation"]`
|
|
126
|
+
- `data_path["climate"]`
|
|
127
|
+
- `data_path["representative_districts"]`
|
|
128
|
+
- `plot_path`
|
|
129
|
+
- `download_link`
|
|
130
|
+
|
|
131
|
+
## Data Layout
|
|
132
|
+
|
|
133
|
+
The `data/` directory is **not tracked in git** (multi-GB). All paths in
|
|
134
|
+
`data_path` resolve relative to the repository root:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
data/
|
|
138
|
+
├── activity_calendar/ # occupant activity calendars (10-min time step)
|
|
139
|
+
├── bdtopo/ # BDTOPO building footprints (downloaded per region)
|
|
140
|
+
├── calibration/ # static-model calibration outputs
|
|
141
|
+
├── climate/ # ERA5-derived weather files (see scripts/climate)
|
|
142
|
+
├── elecdom/ # Elecdom appliance panel data
|
|
143
|
+
├── elmas/ # ELMAS dataset
|
|
144
|
+
├── representative_districts/ # clustering outputs (medoid districts)
|
|
145
|
+
├── simulation/ # static + dynamic simulation results
|
|
146
|
+
└── validation/ # ORE / Enedis / RTE measured consumption
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Download sources are listed in `building_eload.download_link` (BDTOPO regions,
|
|
150
|
+
district list, occupant diaries, ORE annual consumption, ELMAS); helpers live
|
|
151
|
+
in `building_eload.utils.data_download`. Reference data (districts, BDTOPO
|
|
152
|
+
fallback) is otherwise fetched through the `buildingdata` package. The unit
|
|
153
|
+
test suite runs without `data/`; the integration tests
|
|
154
|
+
(`StaticSimulation`/`DynamicSimulation`/validation runs) require it.
|
|
155
|
+
|
|
156
|
+
## Current Core API
|
|
157
|
+
|
|
158
|
+
### Static simulation (`building_eload.core.static_simulation`)
|
|
159
|
+
|
|
160
|
+
Main classes:
|
|
161
|
+
|
|
162
|
+
- `StaticParameters`
|
|
163
|
+
- `StaticSimulation`
|
|
164
|
+
- `StaticResults`
|
|
165
|
+
- `BuildingModelResults`
|
|
166
|
+
- `StaticProcessor`
|
|
167
|
+
|
|
168
|
+
Primary methods used by users:
|
|
169
|
+
|
|
170
|
+
- `StaticSimulation.run()`
|
|
171
|
+
- `StaticSimulation.run_energy_demand()`
|
|
172
|
+
- `StaticResults.save_results(save_dict: Optional[dict[str, bool]] = None)`
|
|
173
|
+
|
|
174
|
+
### Dynamic simulation (`building_eload.core.dynamic_simulation`)
|
|
175
|
+
|
|
176
|
+
Main classes:
|
|
177
|
+
|
|
178
|
+
- `DynamicParameters`
|
|
179
|
+
- `DynamicSimulation`
|
|
180
|
+
|
|
181
|
+
Primary methods used by users:
|
|
182
|
+
|
|
183
|
+
- `DynamicSimulation.from_files(district_id, parameters)`
|
|
184
|
+
- `DynamicSimulation.from_static_results(static_results, parameters)`
|
|
185
|
+
- `DynamicSimulation.run()`
|
|
186
|
+
- `DynamicSimulation.save_results()`
|
|
187
|
+
- `DynamicSimulation.plot_results(...)`
|
|
188
|
+
|
|
189
|
+
## Minimal Usage
|
|
190
|
+
|
|
191
|
+
Tutorials for both simulation processes are available in : `/doc/tutorials`
|
|
192
|
+
|
|
193
|
+
### 1) Static simulation
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
import numpy as np
|
|
197
|
+
from building_eload.core.static_simulation import StaticParameters, StaticSimulation
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
district_id = "262320000"
|
|
201
|
+
eu = np.arange(0.7, 1.3, 0.05).round(2)
|
|
202
|
+
hs = np.arange(16.0, 22.5, 0.5).round(1)
|
|
203
|
+
energy_use_parameters = [
|
|
204
|
+
{"actual_heating_set_point": hs[i], "energy_use_factor": eu[i]}
|
|
205
|
+
for i in range(len(hs))
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
params = StaticParameters(
|
|
209
|
+
n_inference=10,
|
|
210
|
+
calibration_year=2023,
|
|
211
|
+
climate_year=None,
|
|
212
|
+
energy_use_parameters=energy_use_parameters,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
sim = StaticSimulation(district_id=district_id, parameters=params)
|
|
216
|
+
results = sim.run()
|
|
217
|
+
results.save_results()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 2) Dynamic simulation
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from building_eload.core.dynamic_simulation import DynamicParameters, DynamicSimulation
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
district_id = "262320000"
|
|
227
|
+
params = DynamicParameters(
|
|
228
|
+
year=2023,
|
|
229
|
+
run_non_residential=True,
|
|
230
|
+
run_again=True,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
sim = DynamicSimulation.from_files(district_id=district_id, parameters=params)
|
|
234
|
+
sim.run()
|
|
235
|
+
sim.save_results()
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Script Entry Points (Current)
|
|
239
|
+
|
|
240
|
+
- Static simulation:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
python -m building_eload.scripts.simulation.static.run
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- Parallel static simulation:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
python -m building_eload.scripts.simulation.static.run_parallel
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
- Dynamic simulation:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
python -m building_eload.scripts.simulation.dynamic.run
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
- Parallel dynamic simulation:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
python -m building_eload.scripts.simulation.dynamic.run_parallel
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
- Validation:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
python -m building_eload.scripts.validation.run
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
- Climate preprocessing:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
python -m building_eload.scripts.climate.generate_climates
|
|
274
|
+
```
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Building_eload
|
|
2
|
+
|
|
3
|
+
**Version:** `0.4.0`
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
District-level building energy simulation at hourly resolution.
|
|
10
|
+
|
|
11
|
+
This model was first presented in the research article : <https://doi.org/10.1016/j.enbuild.2026.117409>
|
|
12
|
+
|
|
13
|
+
`building_eload` is developed as a **library**, released on PyPI. The Snakemake
|
|
14
|
+
workflow reproducing the paper's results lives in a separate repository
|
|
15
|
+
(`building_eload_paper`), pinned to the published `0.4.x` releases of this
|
|
16
|
+
package.
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
`building_eload` runs a two-stage pipeline:
|
|
21
|
+
|
|
22
|
+
1. Static simulation: annual building-level energy estimates and calibration.
|
|
23
|
+
2. Dynamic simulation: hourly electricity load profiles from static outputs.
|
|
24
|
+
|
|
25
|
+
Main dependency:
|
|
26
|
+
|
|
27
|
+
- `buildingmodel` (for static building physics simulation)
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://git.persee.minesparis.psl.eu/planeterr/building_eload.git
|
|
33
|
+
cd building_eload
|
|
34
|
+
pip install -e .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If needed by your workflow, also install `buildingmodel` in editable mode.
|
|
38
|
+
|
|
39
|
+
## Data Paths Exposed by the Package
|
|
40
|
+
|
|
41
|
+
Defined in `building_eload/__init__.py`:
|
|
42
|
+
|
|
43
|
+
- `data_path["data"]`
|
|
44
|
+
- `data_path["bdtopo"]`
|
|
45
|
+
- `data_path["elmas"]`
|
|
46
|
+
- `data_path["simulation"]`
|
|
47
|
+
- `data_path["activity_calendar"]`
|
|
48
|
+
- `data_path["validation"]`
|
|
49
|
+
- `data_path["climate"]`
|
|
50
|
+
- `data_path["representative_districts"]`
|
|
51
|
+
- `plot_path`
|
|
52
|
+
- `download_link`
|
|
53
|
+
|
|
54
|
+
## Data Layout
|
|
55
|
+
|
|
56
|
+
The `data/` directory is **not tracked in git** (multi-GB). All paths in
|
|
57
|
+
`data_path` resolve relative to the repository root:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
data/
|
|
61
|
+
├── activity_calendar/ # occupant activity calendars (10-min time step)
|
|
62
|
+
├── bdtopo/ # BDTOPO building footprints (downloaded per region)
|
|
63
|
+
├── calibration/ # static-model calibration outputs
|
|
64
|
+
├── climate/ # ERA5-derived weather files (see scripts/climate)
|
|
65
|
+
├── elecdom/ # Elecdom appliance panel data
|
|
66
|
+
├── elmas/ # ELMAS dataset
|
|
67
|
+
├── representative_districts/ # clustering outputs (medoid districts)
|
|
68
|
+
├── simulation/ # static + dynamic simulation results
|
|
69
|
+
└── validation/ # ORE / Enedis / RTE measured consumption
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Download sources are listed in `building_eload.download_link` (BDTOPO regions,
|
|
73
|
+
district list, occupant diaries, ORE annual consumption, ELMAS); helpers live
|
|
74
|
+
in `building_eload.utils.data_download`. Reference data (districts, BDTOPO
|
|
75
|
+
fallback) is otherwise fetched through the `buildingdata` package. The unit
|
|
76
|
+
test suite runs without `data/`; the integration tests
|
|
77
|
+
(`StaticSimulation`/`DynamicSimulation`/validation runs) require it.
|
|
78
|
+
|
|
79
|
+
## Current Core API
|
|
80
|
+
|
|
81
|
+
### Static simulation (`building_eload.core.static_simulation`)
|
|
82
|
+
|
|
83
|
+
Main classes:
|
|
84
|
+
|
|
85
|
+
- `StaticParameters`
|
|
86
|
+
- `StaticSimulation`
|
|
87
|
+
- `StaticResults`
|
|
88
|
+
- `BuildingModelResults`
|
|
89
|
+
- `StaticProcessor`
|
|
90
|
+
|
|
91
|
+
Primary methods used by users:
|
|
92
|
+
|
|
93
|
+
- `StaticSimulation.run()`
|
|
94
|
+
- `StaticSimulation.run_energy_demand()`
|
|
95
|
+
- `StaticResults.save_results(save_dict: Optional[dict[str, bool]] = None)`
|
|
96
|
+
|
|
97
|
+
### Dynamic simulation (`building_eload.core.dynamic_simulation`)
|
|
98
|
+
|
|
99
|
+
Main classes:
|
|
100
|
+
|
|
101
|
+
- `DynamicParameters`
|
|
102
|
+
- `DynamicSimulation`
|
|
103
|
+
|
|
104
|
+
Primary methods used by users:
|
|
105
|
+
|
|
106
|
+
- `DynamicSimulation.from_files(district_id, parameters)`
|
|
107
|
+
- `DynamicSimulation.from_static_results(static_results, parameters)`
|
|
108
|
+
- `DynamicSimulation.run()`
|
|
109
|
+
- `DynamicSimulation.save_results()`
|
|
110
|
+
- `DynamicSimulation.plot_results(...)`
|
|
111
|
+
|
|
112
|
+
## Minimal Usage
|
|
113
|
+
|
|
114
|
+
Tutorials for both simulation processes are available in : `/doc/tutorials`
|
|
115
|
+
|
|
116
|
+
### 1) Static simulation
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import numpy as np
|
|
120
|
+
from building_eload.core.static_simulation import StaticParameters, StaticSimulation
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
district_id = "262320000"
|
|
124
|
+
eu = np.arange(0.7, 1.3, 0.05).round(2)
|
|
125
|
+
hs = np.arange(16.0, 22.5, 0.5).round(1)
|
|
126
|
+
energy_use_parameters = [
|
|
127
|
+
{"actual_heating_set_point": hs[i], "energy_use_factor": eu[i]}
|
|
128
|
+
for i in range(len(hs))
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
params = StaticParameters(
|
|
132
|
+
n_inference=10,
|
|
133
|
+
calibration_year=2023,
|
|
134
|
+
climate_year=None,
|
|
135
|
+
energy_use_parameters=energy_use_parameters,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
sim = StaticSimulation(district_id=district_id, parameters=params)
|
|
139
|
+
results = sim.run()
|
|
140
|
+
results.save_results()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 2) Dynamic simulation
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from building_eload.core.dynamic_simulation import DynamicParameters, DynamicSimulation
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
district_id = "262320000"
|
|
150
|
+
params = DynamicParameters(
|
|
151
|
+
year=2023,
|
|
152
|
+
run_non_residential=True,
|
|
153
|
+
run_again=True,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
sim = DynamicSimulation.from_files(district_id=district_id, parameters=params)
|
|
157
|
+
sim.run()
|
|
158
|
+
sim.save_results()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Script Entry Points (Current)
|
|
162
|
+
|
|
163
|
+
- Static simulation:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
python -m building_eload.scripts.simulation.static.run
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- Parallel static simulation:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
python -m building_eload.scripts.simulation.static.run_parallel
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- Dynamic simulation:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
python -m building_eload.scripts.simulation.dynamic.run
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- Parallel dynamic simulation:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
python -m building_eload.scripts.simulation.dynamic.run_parallel
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- Validation:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
python -m building_eload.scripts.validation.run
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- Climate preprocessing:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
python -m building_eload.scripts.climate.generate_climates
|
|
197
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
# Utilisation de Path(__file__).parents[1] pour naviguer vers le bon répertoire parent
|
|
4
|
+
data_path = {
|
|
5
|
+
"data": Path(__file__).parents[1] / "data",
|
|
6
|
+
"bdtopo": Path(__file__).parents[1] / "data" / "bdtopo",
|
|
7
|
+
"elmas": Path(__file__).parents[1] / "data" / "elmas",
|
|
8
|
+
"simulation": Path(__file__).parents[1] / "data" / "simulation",
|
|
9
|
+
"activity_calendar": Path(__file__).parents[1] / "data" / "activity_calendar",
|
|
10
|
+
"validation": Path(__file__).parents[1] / "data" / "validation",
|
|
11
|
+
"climate": Path(__file__).parents[1] / "data" / "climate",
|
|
12
|
+
"representative_districts": Path(__file__).parents[1] / "data" / "representative_districts",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
plot_path = Path(__file__).parents[1] / "plot"
|
|
16
|
+
|
|
17
|
+
# NOTE: the former ``download_link`` dict and ``utils.data_download`` helpers
|
|
18
|
+
# (``download_and_extract``/``flatten_folder``) were removed in 0.5.0. Reference
|
|
19
|
+
# dataset acquisition now belongs entirely to the ``buildingdata`` package,
|
|
20
|
+
# which owns the GCS-cached download+schema contract for every dataset this
|
|
21
|
+
# model consumes:
|
|
22
|
+
# - ELMAS non-residential load curves -> ``buildingdata.get_elmas(table=...)``
|
|
23
|
+
# (wired into ``models.non_residential.NonResidentialModel``)
|
|
24
|
+
# - occupant activity diaries -> ``buildingdata.get_occupant_diaries()``
|
|
25
|
+
# - ORE per-IRIS annual consumption -> ``buildingdata.get_ore()``
|
|
26
|
+
# - Enedis measured conso-inf36 -> ``buildingdata.get_enedis_national()`` /
|
|
27
|
+
# ``buildingdata.get_enedis_regional()``
|
|
28
|
+
# - IRIS districts / BDTOPO / census / DPE -> already via ``buildingdata``.
|
|
29
|
+
# The old Google-Drive links silently saved Google's "file too large to scan"
|
|
30
|
+
# HTML interstitial instead of the archive; moving to ``buildingdata`` closes
|
|
31
|
+
# that bug. See the buildingdata CHANGELOG (0.2.0) and its doc/pending_uploads.md.
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# building_loader.py
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import warnings
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import buildingdata
|
|
9
|
+
import geopandas as gpd
|
|
10
|
+
from shapely import geometry
|
|
11
|
+
|
|
12
|
+
from building_eload import data_path
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BuildingLoader:
|
|
19
|
+
"""Load buildings from a district and filter them based on intersection with the district geometry."""
|
|
20
|
+
|
|
21
|
+
INTERSECTION_THRESHOLD = 0.5
|
|
22
|
+
BUILDING_LAYER_NAME = "batiment"
|
|
23
|
+
GIS_ENGINE = "pyogrio"
|
|
24
|
+
BUILDING_FILE_PREFIX = "BDTOPO_R"
|
|
25
|
+
BUILDING_FILE_SUFFIX = ".gpkg"
|
|
26
|
+
DEFAULT_DISTRICT_DATA_FILE = None
|
|
27
|
+
DEFAULT_BUILDING_FOOTPRINT_FOLDER = Path(data_path["bdtopo"])
|
|
28
|
+
|
|
29
|
+
def __init__(self, district_id: str, district_data_file: str = None, building_footprint_folder: str = None):
|
|
30
|
+
|
|
31
|
+
self.district_id = str(district_id)
|
|
32
|
+
self.district_data_file = (
|
|
33
|
+
Path(district_data_file) if district_data_file is not None else self.DEFAULT_DISTRICT_DATA_FILE
|
|
34
|
+
)
|
|
35
|
+
self.building_footprint_folder = Path(
|
|
36
|
+
building_footprint_folder or self.DEFAULT_BUILDING_FOOTPRINT_FOLDER
|
|
37
|
+
)
|
|
38
|
+
logger.debug(f"{self.logger_header} District data file: {self.district_data_file}.")
|
|
39
|
+
logger.debug(f"{self.logger_header} Building footprint folder: {self.building_footprint_folder}.")
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def logger_header(self):
|
|
43
|
+
if self.district_id is None:
|
|
44
|
+
return "[BuildingLoader]"
|
|
45
|
+
return f"[District {self.district_id}]"
|
|
46
|
+
|
|
47
|
+
# -------------------------------------------------------
|
|
48
|
+
# Main function
|
|
49
|
+
# -------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
def load_buildings(self) -> Optional[gpd.GeoDataFrame]:
|
|
52
|
+
"""Load buildings for a specific district."""
|
|
53
|
+
logger.info(f"{self.logger_header} Loading buildings.")
|
|
54
|
+
|
|
55
|
+
district_data = self._load_district(self.district_id)
|
|
56
|
+
if district_data.empty:
|
|
57
|
+
logger.warning(f"{self.logger_header} District {self.district_id} not found in district file.")
|
|
58
|
+
return None
|
|
59
|
+
district_geometry = self._get_district_geometry(district_data)
|
|
60
|
+
buildings = self._load_buildings_in_district_bounds(district_geometry, district_data)
|
|
61
|
+
filtered_buildings = self._filter_buildings_on_bounds(buildings, district_geometry)
|
|
62
|
+
filtered_buildings["district"] = self.district_id
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
logger.info(f"{self.logger_header} Loaded {len(filtered_buildings)} buildings.")
|
|
66
|
+
|
|
67
|
+
return filtered_buildings
|
|
68
|
+
|
|
69
|
+
# -------------------------------------------------------
|
|
70
|
+
# Utilis
|
|
71
|
+
# -------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def _load_district(self, district_id: str) -> gpd.GeoDataFrame:
|
|
74
|
+
district_id = str(district_id)
|
|
75
|
+
if self.district_data_file is not None:
|
|
76
|
+
districts = gpd.read_parquet(self.district_data_file)
|
|
77
|
+
else:
|
|
78
|
+
districts = buildingdata.get_districts()
|
|
79
|
+
|
|
80
|
+
district_codes = districts["district"].astype(str).str.zfill(len(district_id))
|
|
81
|
+
return districts[district_codes == district_id]
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _get_district_geometry(district_data: gpd.GeoDataFrame) -> geometry.Polygon:
|
|
85
|
+
return district_data["geometry"].iloc[0]
|
|
86
|
+
|
|
87
|
+
def _load_buildings_in_district_bounds(
|
|
88
|
+
self, district_geometry: geometry.Polygon, district_data: gpd.GeoDataFrame
|
|
89
|
+
) -> gpd.GeoDataFrame:
|
|
90
|
+
bounds = district_geometry.convex_hull.bounds
|
|
91
|
+
building_path = self._get_building_path(district_data)
|
|
92
|
+
|
|
93
|
+
if not building_path.exists():
|
|
94
|
+
logger.debug(
|
|
95
|
+
f"{self.logger_header} Local BDTOPO file {building_path} not found; "
|
|
96
|
+
"loading buildings through buildingdata."
|
|
97
|
+
)
|
|
98
|
+
return buildingdata.get_bdtopo(self.district_id)
|
|
99
|
+
|
|
100
|
+
with warnings.catch_warnings():
|
|
101
|
+
warnings.filterwarnings("ignore")
|
|
102
|
+
logger.debug(f"{self.logger_header} Loading buildings from {building_path}.")
|
|
103
|
+
return gpd.read_file(
|
|
104
|
+
building_path,
|
|
105
|
+
layer=self.BUILDING_LAYER_NAME,
|
|
106
|
+
engine=self.GIS_ENGINE,
|
|
107
|
+
bbox=bounds,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def _filter_buildings_on_bounds(
|
|
111
|
+
self, buildings: gpd.GeoDataFrame, district_geometry: geometry.Polygon
|
|
112
|
+
) -> gpd.GeoDataFrame:
|
|
113
|
+
buildings["intersection_share"] = (
|
|
114
|
+
buildings["geometry"].intersection(district_geometry).area / buildings["geometry"].area
|
|
115
|
+
)
|
|
116
|
+
filtered_buildings = buildings.loc[
|
|
117
|
+
buildings.intersection_share >= self.INTERSECTION_THRESHOLD
|
|
118
|
+
].copy()
|
|
119
|
+
filtered_buildings.drop(columns=["intersection_share"], inplace=True)
|
|
120
|
+
return filtered_buildings
|
|
121
|
+
|
|
122
|
+
def _get_building_path(self, district_data: gpd.GeoDataFrame) -> Path:
|
|
123
|
+
region_id = int(district_data["region"].iloc[0])
|
|
124
|
+
return (
|
|
125
|
+
self.building_footprint_folder
|
|
126
|
+
/ f"{self.BUILDING_FILE_PREFIX}{region_id}{self.BUILDING_FILE_SUFFIX}"
|
|
127
|
+
)
|