ebm 0.99.5__py3-none-any.whl
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.
- ebm/__init__.py +0 -0
- ebm/__main__.py +152 -0
- ebm/__version__.py +1 -0
- ebm/cmd/__init__.py +0 -0
- ebm/cmd/calibrate.py +83 -0
- ebm/cmd/calibrate_excel_com_io.py +128 -0
- ebm/cmd/heating_systems_by_year.py +18 -0
- ebm/cmd/helpers.py +134 -0
- ebm/cmd/initialize.py +167 -0
- ebm/cmd/migrate.py +92 -0
- ebm/cmd/pipeline.py +227 -0
- ebm/cmd/prepare_main.py +174 -0
- ebm/cmd/result_handler.py +272 -0
- ebm/cmd/run_calculation.py +221 -0
- ebm/data/area.csv +92 -0
- ebm/data/area_new_residential_buildings.csv +3 -0
- ebm/data/area_per_person.csv +12 -0
- ebm/data/building_code_parameters.csv +9 -0
- ebm/data/energy_need_behaviour_factor.csv +6 -0
- ebm/data/energy_need_improvements.csv +7 -0
- ebm/data/energy_need_original_condition.csv +534 -0
- ebm/data/heating_system_efficiencies.csv +13 -0
- ebm/data/heating_system_forecast.csv +9 -0
- ebm/data/heating_system_initial_shares.csv +1113 -0
- ebm/data/holiday_home_energy_consumption.csv +24 -0
- ebm/data/holiday_home_stock.csv +25 -0
- ebm/data/improvement_building_upgrade.csv +9 -0
- ebm/data/new_buildings_residential.csv +32 -0
- ebm/data/population_forecast.csv +51 -0
- ebm/data/s_curve.csv +40 -0
- ebm/energy_consumption.py +307 -0
- ebm/extractors.py +115 -0
- ebm/heating_system_forecast.py +472 -0
- ebm/holiday_home_energy.py +341 -0
- ebm/migrations.py +224 -0
- ebm/model/__init__.py +0 -0
- ebm/model/area.py +403 -0
- ebm/model/bema.py +149 -0
- ebm/model/building_category.py +150 -0
- ebm/model/building_condition.py +78 -0
- ebm/model/calibrate_energy_requirements.py +84 -0
- ebm/model/calibrate_heating_systems.py +180 -0
- ebm/model/column_operations.py +157 -0
- ebm/model/construction.py +827 -0
- ebm/model/data_classes.py +223 -0
- ebm/model/database_manager.py +410 -0
- ebm/model/dataframemodels.py +115 -0
- ebm/model/defaults.py +30 -0
- ebm/model/energy_need.py +6 -0
- ebm/model/energy_need_filter.py +182 -0
- ebm/model/energy_purpose.py +115 -0
- ebm/model/energy_requirement.py +353 -0
- ebm/model/energy_use.py +202 -0
- ebm/model/enums.py +8 -0
- ebm/model/exceptions.py +4 -0
- ebm/model/file_handler.py +388 -0
- ebm/model/filter_scurve_params.py +83 -0
- ebm/model/filter_tek.py +152 -0
- ebm/model/heat_pump.py +53 -0
- ebm/model/heating_systems.py +20 -0
- ebm/model/heating_systems_parameter.py +17 -0
- ebm/model/heating_systems_projection.py +3 -0
- ebm/model/heating_systems_share.py +28 -0
- ebm/model/scurve.py +224 -0
- ebm/model/tek.py +1 -0
- ebm/s_curve.py +515 -0
- ebm/services/__init__.py +0 -0
- ebm/services/calibration_writer.py +262 -0
- ebm/services/console.py +106 -0
- ebm/services/excel_loader.py +66 -0
- ebm/services/files.py +38 -0
- ebm/services/spreadsheet.py +289 -0
- ebm/temp_calc.py +99 -0
- ebm/validators.py +565 -0
- ebm-0.99.5.dist-info/METADATA +212 -0
- ebm-0.99.5.dist-info/RECORD +80 -0
- ebm-0.99.5.dist-info/WHEEL +5 -0
- ebm-0.99.5.dist-info/entry_points.txt +3 -0
- ebm-0.99.5.dist-info/licenses/LICENSE +21 -0
- ebm-0.99.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,827 @@
|
|
1
|
+
import itertools
|
2
|
+
import typing
|
3
|
+
from typing import Union
|
4
|
+
|
5
|
+
import numpy as np
|
6
|
+
import pandas as pd
|
7
|
+
from loguru import logger
|
8
|
+
|
9
|
+
from ebm.model.building_category import BuildingCategory
|
10
|
+
from ebm.model.data_classes import YearRange
|
11
|
+
from ebm.model.database_manager import DatabaseManager
|
12
|
+
|
13
|
+
|
14
|
+
class ConstructionCalculator:
|
15
|
+
"""
|
16
|
+
A class used to calculate various construction metrics for residential and commercial buildings.
|
17
|
+
|
18
|
+
The main method to use is calculate_construction
|
19
|
+
|
20
|
+
Methods
|
21
|
+
-------
|
22
|
+
calculate_construction(building_category, demolition_floor_area, database_manager)
|
23
|
+
Calculate constructed floor area for buildings using provided demolition floor area and input data from
|
24
|
+
database manager.
|
25
|
+
calculate_construction_as_list(building_category, demolition_floor_area, database_manager=None)
|
26
|
+
Calculate constructed floor area for buildings using provided demolition floor area and input data
|
27
|
+
from database manager.
|
28
|
+
calculate_industrial_construction(building_category, total_floor_area, constructed_floor_area=None, demolition_floor_area=None, population=None)
|
29
|
+
Calculate the commercial construction metrics over a given period.
|
30
|
+
calculate_residential_construction(population, household_size, building_category_share, build_area_sum, yearly_demolished_floor_area, average_floor_area=175)
|
31
|
+
Calculate various residential construction metrics based on population, household size, and building data.
|
32
|
+
|
33
|
+
calculate_yearly_new_building_floor_area_sum(yearly_new_building_floor_area_house)
|
34
|
+
Calculate the accumulated constructed floor area over the years.
|
35
|
+
|
36
|
+
calculate_yearly_constructed_floor_area(build_area_sum, yearly_floor_area_change, yearly_demolished_floor_area)
|
37
|
+
Calculate the yearly constructed floor area based on changes and demolitions.
|
38
|
+
|
39
|
+
calculate_yearly_floor_area_change(building_change, average_floor_area=175)
|
40
|
+
Calculate the yearly floor area change based on building changes and average floor area.
|
41
|
+
|
42
|
+
calculate_population_growth(population)
|
43
|
+
Calculate the annual growth in population.
|
44
|
+
|
45
|
+
calculate_building_growth(building_category_share, households_change)
|
46
|
+
Calculate the annual growth in building categories based on household changes.
|
47
|
+
|
48
|
+
calculate_household_change(households)
|
49
|
+
Calculate the annual change in the number of households.
|
50
|
+
|
51
|
+
calculate_households_by_year(household_size, population)
|
52
|
+
Calculate the number of households by year based on household size and population.
|
53
|
+
"""
|
54
|
+
|
55
|
+
def calculate_residential_construction(self, population: pd.Series, household_size: pd.Series,
|
56
|
+
building_category_share: pd.Series,
|
57
|
+
build_area_sum: pd.Series,
|
58
|
+
yearly_demolished_floor_area: pd.Series,
|
59
|
+
average_floor_area: typing.Union[pd.Series, int] = 175,
|
60
|
+
period=YearRange(2010, 2050)) -> pd.DataFrame:
|
61
|
+
"""
|
62
|
+
Calculate various residential construction metrics based on population, household size, and building data.
|
63
|
+
|
64
|
+
Parameters
|
65
|
+
----------
|
66
|
+
population : pd.Series
|
67
|
+
A pandas Series representing the population indexed by year.
|
68
|
+
household_size : pd.Series
|
69
|
+
A pandas Series representing the average household size over time.
|
70
|
+
building_category_share : pd.Series
|
71
|
+
A pandas Series representing the share of each building category.
|
72
|
+
build_area_sum : pd.Series
|
73
|
+
A pandas Series representing the accumulated building area sum.
|
74
|
+
yearly_demolished_floor_area : pd.Series
|
75
|
+
A pandas Series representing the yearly demolished floor area.
|
76
|
+
period : YearRange
|
77
|
+
contains start and end year for the model
|
78
|
+
|
79
|
+
Returns
|
80
|
+
-------
|
81
|
+
pd.DataFrame
|
82
|
+
A pandas DataFrame containing various residential construction metrics.
|
83
|
+
|
84
|
+
Notes
|
85
|
+
-----
|
86
|
+
The function calculates several metrics including population growth, household changes, building growth,
|
87
|
+
yearly constructed floor area, and accumulated constructed floor area.
|
88
|
+
"""
|
89
|
+
# A subset of population should be equal to period
|
90
|
+
self._check_index(period, population)
|
91
|
+
# building_category_share or a subset should be equal to population
|
92
|
+
self._check_index(period, building_category_share)
|
93
|
+
# yearly_demolished_floor_area to or replace period as the guiding factor
|
94
|
+
self._check_index(period, yearly_demolished_floor_area)
|
95
|
+
# household_size or a subset should be equal to population
|
96
|
+
self._check_index(period, household_size)
|
97
|
+
|
98
|
+
# It might be sensible to calculate total floor area and work from there (like commercial) rather than going
|
99
|
+
# through average_floor_area <-> building_growth <-> households_change <-> population_growth
|
100
|
+
population = population[yearly_demolished_floor_area.index]
|
101
|
+
building_category_share = building_category_share[yearly_demolished_floor_area.index]
|
102
|
+
household_size = household_size[yearly_demolished_floor_area.index]
|
103
|
+
if isinstance(average_floor_area, pd.Series):
|
104
|
+
average_floor_area = average_floor_area[yearly_demolished_floor_area.index]
|
105
|
+
|
106
|
+
households_by_year = self.calculate_households_by_year(household_size, population)
|
107
|
+
|
108
|
+
# Årlig endring i antall boliger (brukt Årlig endring i antall småhus)
|
109
|
+
households_change = self.calculate_household_change(households_by_year)
|
110
|
+
building_growth = self.calculate_building_growth(building_category_share, households_change)
|
111
|
+
|
112
|
+
# Årlig endring areal småhus (brukt Årlig nybygget areal småhus)
|
113
|
+
yearly_floor_area_constructed = self.calculate_yearly_floor_area_change(building_growth, average_floor_area)
|
114
|
+
|
115
|
+
# Årlig revet areal småhus
|
116
|
+
floor_area_change = self.calculate_yearly_constructed_floor_area(
|
117
|
+
build_area_sum, yearly_floor_area_constructed, yearly_demolished_floor_area)
|
118
|
+
|
119
|
+
# Nybygget småhus akkumulert
|
120
|
+
floor_area_change_accumulated = self.calculate_yearly_new_building_floor_area_sum(floor_area_change)
|
121
|
+
|
122
|
+
df = pd.DataFrame(data={
|
123
|
+
'population': population,
|
124
|
+
'household_size': household_size,
|
125
|
+
'households': households_by_year,
|
126
|
+
'households_change': households_change,
|
127
|
+
'net_constructed_floor_area': yearly_floor_area_constructed,
|
128
|
+
'demolished_floor_area': yearly_demolished_floor_area,
|
129
|
+
'constructed_floor_area': floor_area_change,
|
130
|
+
'accumulated_constructed_floor_area': floor_area_change_accumulated},
|
131
|
+
index=floor_area_change_accumulated.index)
|
132
|
+
|
133
|
+
return df
|
134
|
+
|
135
|
+
def _check_index(self, period, values: pd.Series) -> bool:
|
136
|
+
name = values.name
|
137
|
+
if not all([y in values.index for y in period]):
|
138
|
+
logger.debug(
|
139
|
+
f'{name}.index({values.index[0]}, {values.index[-1]}) is not equal to period(start={period.start}, {period.end})')
|
140
|
+
return False
|
141
|
+
return True
|
142
|
+
|
143
|
+
@staticmethod
|
144
|
+
def calculate_yearly_new_building_floor_area_sum(yearly_new_building_floor_area_house: pd.Series):
|
145
|
+
"""
|
146
|
+
Calculate the accumulated constructed floor area over the years.
|
147
|
+
|
148
|
+
Parameters
|
149
|
+
----------
|
150
|
+
yearly_new_building_floor_area_house : pd.Series
|
151
|
+
A pandas Series representing the yearly new building floor area.
|
152
|
+
|
153
|
+
Returns
|
154
|
+
-------
|
155
|
+
pd.Series
|
156
|
+
A pandas Series representing the accumulated constructed floor area, named
|
157
|
+
'accumulated_constructed_floor_area'.
|
158
|
+
|
159
|
+
Notes
|
160
|
+
-----
|
161
|
+
The function calculates the cumulative sum of the yearly new building floor area.
|
162
|
+
"""
|
163
|
+
return pd.Series(yearly_new_building_floor_area_house.cumsum(), name='accumulated_constructed_floor_area')
|
164
|
+
|
165
|
+
@staticmethod
|
166
|
+
def calculate_yearly_constructed_floor_area(build_area_sum: pd.Series,
|
167
|
+
yearly_floor_area_change: pd.Series,
|
168
|
+
yearly_demolished_floor_area: pd.Series) -> pd.Series:
|
169
|
+
"""
|
170
|
+
Calculate the yearly constructed floor area based on changes and demolitions.
|
171
|
+
|
172
|
+
Parameters
|
173
|
+
----------
|
174
|
+
build_area_sum : pd.Series
|
175
|
+
A pandas Series representing the accumulated building area sum.
|
176
|
+
yearly_floor_area_change : pd.Series
|
177
|
+
A pandas Series representing the yearly change in floor area.
|
178
|
+
yearly_demolished_floor_area : pd.Series
|
179
|
+
A pandas Series representing the yearly demolished floor area.
|
180
|
+
|
181
|
+
Returns
|
182
|
+
-------
|
183
|
+
pd.Series
|
184
|
+
A pandas Series representing the yearly constructed floor area, named 'constructed_floor_area'.
|
185
|
+
|
186
|
+
Notes
|
187
|
+
-----
|
188
|
+
The function calculates the yearly new building floor area by adding the yearly floor area change
|
189
|
+
to the yearly demolished floor area. It then updates the values based on the build_area_sum index.
|
190
|
+
|
191
|
+
"""
|
192
|
+
bas_missing_year = [str(y) for y in yearly_demolished_floor_area.iloc[0:2].index if
|
193
|
+
y not in build_area_sum.index or
|
194
|
+
np.isnan(build_area_sum.loc[y])]
|
195
|
+
|
196
|
+
if bas_missing_year:
|
197
|
+
msg = f'missing constructed floor area for {", ".join(bas_missing_year)}'
|
198
|
+
raise ValueError(msg)
|
199
|
+
|
200
|
+
yearly_new_building_floor_area_house = yearly_floor_area_change + yearly_demolished_floor_area
|
201
|
+
yearly_new_building_floor_area_house.loc[build_area_sum.index.to_numpy()] = build_area_sum.loc[
|
202
|
+
build_area_sum.index.to_numpy()]
|
203
|
+
|
204
|
+
return pd.Series(yearly_new_building_floor_area_house, name='constructed_floor_area')
|
205
|
+
|
206
|
+
@staticmethod
|
207
|
+
def calculate_yearly_floor_area_change(building_change: pd.Series,
|
208
|
+
average_floor_area: typing.Union[pd.Series, int] = 175) -> pd.Series:
|
209
|
+
"""
|
210
|
+
Calculate the yearly floor area change based on building changes and average floor area.
|
211
|
+
|
212
|
+
Parameters
|
213
|
+
----------
|
214
|
+
building_change : pd.Series
|
215
|
+
A pandas Series representing the change in the number of buildings.
|
216
|
+
average_floor_area : typing.Union[pd.Series, int], optional
|
217
|
+
The average floor area per building. Can be a pandas Series or an integer. Default is 175.
|
218
|
+
|
219
|
+
Returns
|
220
|
+
-------
|
221
|
+
pd.Series
|
222
|
+
A pandas Series representing the yearly floor area change, named 'house_floor_area_change'.
|
223
|
+
|
224
|
+
Notes
|
225
|
+
-----
|
226
|
+
The function calculates the yearly floor area change by multiplying the building change by the average floor
|
227
|
+
area.
|
228
|
+
The floor area change for the first two years set to 0.
|
229
|
+
"""
|
230
|
+
average_floor_area = average_floor_area.loc[building_change.index] if isinstance(average_floor_area, pd.Series) else average_floor_area
|
231
|
+
yearly_floor_area_change = average_floor_area * building_change
|
232
|
+
yearly_floor_area_change.iloc[0:2] = 0.0
|
233
|
+
return pd.Series(yearly_floor_area_change, name='house_floor_area_change')
|
234
|
+
|
235
|
+
@staticmethod
|
236
|
+
def calculate_population_growth(population: pd.Series) -> pd.Series:
|
237
|
+
"""
|
238
|
+
Calculate the annual growth in building categories based on household changes.
|
239
|
+
|
240
|
+
Parameters
|
241
|
+
----------
|
242
|
+
population : pd.Series
|
243
|
+
A pandas Series representing the population indexed by year.
|
244
|
+
Returns
|
245
|
+
-------
|
246
|
+
pd.Series
|
247
|
+
A pandas Series representing the annual growth population.
|
248
|
+
|
249
|
+
"""
|
250
|
+
population_growth = (population / population.shift(1)) - 1
|
251
|
+
population_growth.name = 'population_growth'
|
252
|
+
return population_growth
|
253
|
+
|
254
|
+
@staticmethod
|
255
|
+
def calculate_building_growth(building_category_share: pd.Series, households_change: pd.Series) -> pd.Series:
|
256
|
+
"""
|
257
|
+
Calculate the annual growth in building categories based on household changes.
|
258
|
+
|
259
|
+
Parameters
|
260
|
+
----------
|
261
|
+
building_category_share : pd.Series
|
262
|
+
A pandas Series representing the share (0 to 1 ) of each building category.
|
263
|
+
households_change : pd.Series
|
264
|
+
A pandas Series representing the annual change in the number of households.
|
265
|
+
|
266
|
+
Returns
|
267
|
+
-------
|
268
|
+
pd.Series
|
269
|
+
A pandas Series representing the annual growth in building categories, named 'building_growth'.
|
270
|
+
|
271
|
+
"""
|
272
|
+
|
273
|
+
house_share = building_category_share
|
274
|
+
# Årlig endring i antall småhus (brukt Årlig endring areal småhus)
|
275
|
+
house_change = households_change * house_share
|
276
|
+
return pd.Series(house_change, name='building_growth')
|
277
|
+
|
278
|
+
@staticmethod
|
279
|
+
def calculate_household_change(households: pd.Series) -> pd.Series:
|
280
|
+
"""
|
281
|
+
Calculate the annual change in the number of households.
|
282
|
+
|
283
|
+
Parameters
|
284
|
+
----------
|
285
|
+
households : pd.Series
|
286
|
+
A pandas Series representing the number of households over time.
|
287
|
+
|
288
|
+
Returns
|
289
|
+
-------
|
290
|
+
pd.Series
|
291
|
+
A pandas Series representing the annual change in the number of households, named 'household_change'.
|
292
|
+
"""
|
293
|
+
return pd.Series(households - households.shift(1), name='household_change')
|
294
|
+
|
295
|
+
@staticmethod
|
296
|
+
def calculate_households_by_year(household_size: pd.Series, population: pd.Series) -> pd.Series:
|
297
|
+
"""
|
298
|
+
Calculate the number of households by year based on household size and population.
|
299
|
+
|
300
|
+
Parameters
|
301
|
+
----------
|
302
|
+
household_size : pd.Series
|
303
|
+
A pandas Series representing the average household size over time.
|
304
|
+
population : pd.Series
|
305
|
+
A pandas Series representing the population over time.
|
306
|
+
|
307
|
+
Returns
|
308
|
+
-------
|
309
|
+
pd.Series
|
310
|
+
A pandas Series representing the number of households by year, named 'households'.
|
311
|
+
|
312
|
+
Notes
|
313
|
+
-----
|
314
|
+
The function calculates the number of households by dividing the population by the average household size.
|
315
|
+
|
316
|
+
"""
|
317
|
+
households = population / household_size
|
318
|
+
households.name = 'households'
|
319
|
+
return households
|
320
|
+
|
321
|
+
@staticmethod
|
322
|
+
def calculate_industrial_construction(building_category: BuildingCategory,
|
323
|
+
total_floor_area: typing.Union[pd.Series, int],
|
324
|
+
constructed_floor_area: pd.Series = None,
|
325
|
+
demolition_floor_area: pd.Series = None,
|
326
|
+
population: pd.Series = None,
|
327
|
+
period=YearRange(2010, 2050)) -> pd.DataFrame:
|
328
|
+
"""
|
329
|
+
Calculate the industrial construction metrics over a given period.
|
330
|
+
|
331
|
+
Parameters
|
332
|
+
----------
|
333
|
+
|
334
|
+
building_category : BuildingCategory, optional
|
335
|
+
The category of the building.
|
336
|
+
total_floor_area : pd.Series | int
|
337
|
+
The total floor area at the start of the period.
|
338
|
+
constructed_floor_area : pd.Series, optional
|
339
|
+
Series containing the constructed floor area for each year.
|
340
|
+
demolition_floor_area : pd.Series, optional
|
341
|
+
Series containing the demolished floor area for each year.
|
342
|
+
population : pd.DataFrame, optional
|
343
|
+
DataFrame containing the population data for each year.
|
344
|
+
period : YearRange
|
345
|
+
start and end of model in years
|
346
|
+
Returns
|
347
|
+
-------
|
348
|
+
pd.DataFrame
|
349
|
+
DataFrame containing the calculated construction metrics:
|
350
|
+
- total_floor_area: Total floor area for each year.
|
351
|
+
- building_growth: Growth rate of the building floor area.
|
352
|
+
- demolished_floor_area: Demolished floor area for each year.
|
353
|
+
- constructed_floor_area: Constructed floor area for each year.
|
354
|
+
- accumulated_constructed_floor_area: Cumulative constructed floor area.
|
355
|
+
- floor_area_over_population_growth: Ratio of floor area growth over population growth.
|
356
|
+
|
357
|
+
Notes
|
358
|
+
-----
|
359
|
+
- If `building_category` is `BuildingCategory.STORAGE_REPAIRS`, the total floor area remains constant from 2010
|
360
|
+
to 2051.
|
361
|
+
- The function assumes that the input Series and DataFrame have appropriate indices corresponding to the years.
|
362
|
+
"""
|
363
|
+
|
364
|
+
logger.debug(f'calculate_industrial_construction {building_category}')
|
365
|
+
|
366
|
+
ConstructionCalculator()._check_index(period, demolition_floor_area)
|
367
|
+
ConstructionCalculator()._check_index(period, population)
|
368
|
+
|
369
|
+
# Filter out constructed_floor_area outside period
|
370
|
+
constructed_floor_area = constructed_floor_area.loc[[y for y in constructed_floor_area.index if y in period]]
|
371
|
+
|
372
|
+
if not isinstance(total_floor_area, pd.Series):
|
373
|
+
total_floor_area = pd.Series(data=[total_floor_area], index=[period.start])
|
374
|
+
|
375
|
+
for year in constructed_floor_area.index:
|
376
|
+
if year not in period.subset(1, 4):
|
377
|
+
logger.warning(f'Construction floor area for {building_category} {year} is missing from input')
|
378
|
+
continue
|
379
|
+
total_floor_area.loc[year] = total_floor_area.loc[year - 1] - \
|
380
|
+
demolition_floor_area.loc[year] + \
|
381
|
+
constructed_floor_area.loc[year]
|
382
|
+
|
383
|
+
population_growth = ConstructionCalculator.calculate_population_growth(population)
|
384
|
+
building_growth = ConstructionCalculator.calculate_floor_area_growth(total_floor_area, period)
|
385
|
+
|
386
|
+
floor_area_over_population_growth = ConstructionCalculator.calculate_floor_area_over_building_growth(
|
387
|
+
building_growth=building_growth,
|
388
|
+
population_growth=population_growth,
|
389
|
+
years=period)
|
390
|
+
|
391
|
+
total_floor_area = ConstructionCalculator.calculate_total_floor_area(
|
392
|
+
floor_area_over_population_growth=floor_area_over_population_growth,
|
393
|
+
population_growth=population_growth,
|
394
|
+
total_floor_area=total_floor_area,
|
395
|
+
period=period)
|
396
|
+
|
397
|
+
# STORAGE_REPAIRS has constant floor area
|
398
|
+
if building_category == BuildingCategory.STORAGE:
|
399
|
+
total_floor_area.loc[period.start:period.end + 1] = total_floor_area.loc[period.start]
|
400
|
+
|
401
|
+
constructed_floor_area = ConstructionCalculator.calculate_constructed_floor_area(
|
402
|
+
constructed_floor_area=constructed_floor_area,
|
403
|
+
demolition_floor_area=demolition_floor_area,
|
404
|
+
total_floor_area=total_floor_area,
|
405
|
+
period=period)
|
406
|
+
|
407
|
+
accumulated_constructed_floor_area = constructed_floor_area.cumsum()
|
408
|
+
|
409
|
+
# Make the resulting DataFrame
|
410
|
+
construction = pd.DataFrame(data={
|
411
|
+
'total_floor_area': total_floor_area,
|
412
|
+
'building_growth': building_growth,
|
413
|
+
'demolished_floor_area': demolition_floor_area,
|
414
|
+
'constructed_floor_area': constructed_floor_area,
|
415
|
+
'accumulated_constructed_floor_area': accumulated_constructed_floor_area,
|
416
|
+
'floor_area_over_population_growth': floor_area_over_population_growth
|
417
|
+
}, index=period.to_index())
|
418
|
+
|
419
|
+
return construction
|
420
|
+
|
421
|
+
@staticmethod
|
422
|
+
def calculate_total_floor_area(floor_area_over_population_growth: pd.Series,
|
423
|
+
population_growth: pd.Series,
|
424
|
+
total_floor_area: pd.Series,
|
425
|
+
period: YearRange):
|
426
|
+
"""
|
427
|
+
Calculate the total floor area over a given period based on population growth.
|
428
|
+
|
429
|
+
Parameters
|
430
|
+
----------
|
431
|
+
floor_area_over_population_growth : pd.Series
|
432
|
+
A pandas Series containing the floor area change over population growth for each year.
|
433
|
+
population_growth : pd.Series
|
434
|
+
A pandas Series containing the population growth for each year.
|
435
|
+
total_floor_area : pd.Series
|
436
|
+
A pandas Series containing the total floor area for each year.
|
437
|
+
period : YearRange
|
438
|
+
A named tuple containing the start and end years of the period.
|
439
|
+
|
440
|
+
Returns
|
441
|
+
-------
|
442
|
+
pd.Series
|
443
|
+
Updated pandas Series with the total floor area for each year in the given period.
|
444
|
+
|
445
|
+
Notes
|
446
|
+
-----
|
447
|
+
The calculation starts from `period.start + 5` to `period.end`. For each year, the total floor area is updated
|
448
|
+
based on the formula:
|
449
|
+
|
450
|
+
total_floor_area[year] = ((change_ratio * pop_growth) + 1) * previous_floor_area
|
451
|
+
"""
|
452
|
+
calculated_total_floor_area = total_floor_area.copy()
|
453
|
+
|
454
|
+
# Dette er grusomt.
|
455
|
+
years_to_update = period.subset(offset=list(period).index(max(total_floor_area.index) + 1), length=-1)
|
456
|
+
for year in years_to_update:
|
457
|
+
change_ratio = floor_area_over_population_growth.loc[year]
|
458
|
+
growth = population_growth.loc[year]
|
459
|
+
previous_floor_area = calculated_total_floor_area.loc[year - 1]
|
460
|
+
calculated_total_floor_area.loc[year] = ((change_ratio * growth) + 1) * previous_floor_area
|
461
|
+
calculated_total_floor_area.name = 'total_floor_area'
|
462
|
+
return calculated_total_floor_area
|
463
|
+
|
464
|
+
@staticmethod
|
465
|
+
def calculate_constructed_floor_area(constructed_floor_area: pd.Series,
|
466
|
+
demolition_floor_area: pd.Series,
|
467
|
+
total_floor_area: pd.Series,
|
468
|
+
period: YearRange) -> pd.Series:
|
469
|
+
"""
|
470
|
+
Calculate the constructed floor area over a specified period.
|
471
|
+
|
472
|
+
Parameters
|
473
|
+
----------
|
474
|
+
constructed_floor_area : pd.Series
|
475
|
+
A pandas Series to store the constructed floor area for each previous year.
|
476
|
+
demolition_floor_area : pd.Series
|
477
|
+
A pandas Series containing the demolition floor area for each year.
|
478
|
+
total_floor_area : pd.Series
|
479
|
+
A pandas Series containing the total floor area for each year.
|
480
|
+
period : YearRange
|
481
|
+
An object containing the start year, end year, and the range of years.
|
482
|
+
|
483
|
+
Returns
|
484
|
+
-------
|
485
|
+
pd.Series
|
486
|
+
A pandas Series containing the constructed floor area for each year in the period.
|
487
|
+
|
488
|
+
Notes
|
489
|
+
-----
|
490
|
+
The constructed floor area is calculated from year 6 onwards by subtracting the previous year's
|
491
|
+
floor area from the current year's floor area and adding the previous year's demolition floor area.
|
492
|
+
|
493
|
+
Examples
|
494
|
+
--------
|
495
|
+
>>> construction = pd.Series({2020: 0, 2021: 0, 2022: 0, 2023: 0, 2024: 0, 2025: 0})
|
496
|
+
>>> demolition = pd.Series({2020: 50, 2021: 60, 2022: 70, 2023: 80, 2024: 90, 2025: 100})
|
497
|
+
>>> total = pd.Series({2020: 1000, 2021: 1100, 2022: 1200, 2023: 1300, 2024: 1400, 2025: 1500})
|
498
|
+
>>> years = YearRange(2020, 2025)
|
499
|
+
>>> ConstructionCalculator.calculate_constructed_floor_area(construction, demolition, total, years)
|
500
|
+
2020 0.0
|
501
|
+
2021 0.0
|
502
|
+
2022 0.0
|
503
|
+
2023 0.0
|
504
|
+
2024 0.0
|
505
|
+
2025 200.0
|
506
|
+
dtype: float64
|
507
|
+
"""
|
508
|
+
|
509
|
+
# Calculate constructed floor area from year 6 by subtracting last year's floor area with current floor area
|
510
|
+
# and adding last year's demolition.
|
511
|
+
# Calculate constructed floor area from year 6 by substracting last years floor area with current floor area
|
512
|
+
# and adding last years demolition.
|
513
|
+
for year in [y for y in period if y not in constructed_floor_area.index and y > period.start]:
|
514
|
+
floor_area = total_floor_area.loc[year]
|
515
|
+
previous_year_floor_area = total_floor_area.loc[year - 1]
|
516
|
+
demolished = demolition_floor_area.loc[year]
|
517
|
+
constructed = floor_area - previous_year_floor_area + demolished
|
518
|
+
constructed_floor_area[year] = constructed
|
519
|
+
constructed_floor_area.name = 'constructed_floor_area'
|
520
|
+
return constructed_floor_area
|
521
|
+
|
522
|
+
@staticmethod
|
523
|
+
def calculate_floor_area_growth(total_floor_area: pd.Series, period: YearRange) -> pd.Series:
|
524
|
+
"""
|
525
|
+
Calculate the growth of floor area over a specified period.
|
526
|
+
|
527
|
+
Parameters
|
528
|
+
----------
|
529
|
+
total_floor_area : pd.Series
|
530
|
+
A pandas Series containing the total floor area for each year.
|
531
|
+
period : YearRange
|
532
|
+
An object containing the start year, end year, and the range of years.
|
533
|
+
|
534
|
+
Returns
|
535
|
+
-------
|
536
|
+
pd.Series
|
537
|
+
A pandas Series containing the floor area growth for each year in the period.
|
538
|
+
|
539
|
+
Notes
|
540
|
+
-----
|
541
|
+
The growth for the first year in the period is set to NaN. The growth for the next four years
|
542
|
+
is calculated based on the change in total floor area from the previous year.
|
543
|
+
|
544
|
+
Examples
|
545
|
+
--------
|
546
|
+
>>> total_floor_area = pd.Series({2020: 1000, 2021: 1100, 2022: 1210, 2023: 1331, 2024: 1464})
|
547
|
+
>>> period = YearRange(2020, 2024)
|
548
|
+
>>> ConstructionCalculator.calculate_floor_area_growth(total_floor_area, period)
|
549
|
+
2020 NaN
|
550
|
+
2021 0.1000
|
551
|
+
2022 0.1000
|
552
|
+
2023 0.1000
|
553
|
+
2024 0.1000
|
554
|
+
dtype: float64
|
555
|
+
"""
|
556
|
+
floor_area_growth = pd.Series(data=itertools.repeat(0.0, len(period.year_range)), index=period.year_range)
|
557
|
+
floor_area_growth.loc[period.start] = np.nan
|
558
|
+
# The next 4 years of building growth is calculated from change in total_floor_area
|
559
|
+
for year in range(period.start + 1, period.start + 5):
|
560
|
+
if year in total_floor_area.index:
|
561
|
+
floor_area_growth.loc[year] = (total_floor_area.loc[year] / total_floor_area.loc[year - 1]) - 1
|
562
|
+
return floor_area_growth
|
563
|
+
|
564
|
+
@staticmethod
|
565
|
+
def calculate_floor_area_over_building_growth(building_growth: pd.Series,
|
566
|
+
population_growth: pd.Series,
|
567
|
+
years: YearRange) -> pd.Series:
|
568
|
+
"""
|
569
|
+
Calculate the floor area over building growth for a given range of years.
|
570
|
+
|
571
|
+
Parameters
|
572
|
+
----------
|
573
|
+
building_growth : pd.Series
|
574
|
+
A pandas Series representing the building growth over the years.
|
575
|
+
population_growth : pd.Series
|
576
|
+
A pandas Series representing the population growth over the years.
|
577
|
+
years : YearRange
|
578
|
+
An object representing the range of years for the calculation.
|
579
|
+
|
580
|
+
Returns
|
581
|
+
-------
|
582
|
+
pd.Series
|
583
|
+
A pandas Series representing the floor area over building growth for each year in the specified range.
|
584
|
+
|
585
|
+
Notes
|
586
|
+
-----
|
587
|
+
- The first year in the range is initialized with NaN.
|
588
|
+
- For the first 4 years, the floor area over building growth is calculated directly from the building and population growth.
|
589
|
+
- For the next 5 years, the mean floor area over building growth is used.
|
590
|
+
- From the 11th year onwards, the value is set to 1.
|
591
|
+
- For the years between the 11th and 21st, the value is interpolated linearly.
|
592
|
+
|
593
|
+
Examples
|
594
|
+
--------
|
595
|
+
>>> building_growth = pd.Series([1.2, 1.3, 1.4, 1.5, 1.6], index=[2010, 2011, 2012, 2013, 2014])
|
596
|
+
>>> pd.Series([1.1, 1.2, 1.3, 1.4, 1.5], index=[2010, 2011, 2012, 2013, 2014])
|
597
|
+
>>> years = YearRange(start=2010, end=2050)
|
598
|
+
>>> ConstructionCalculator.calculate_floor_area_over_building_growth(building_growth, population_growth, years)
|
599
|
+
2010 NaN
|
600
|
+
2011 1.083333
|
601
|
+
2012 1.076923
|
602
|
+
2013 1.071429
|
603
|
+
2014 1.066667
|
604
|
+
2015 1.074588
|
605
|
+
2016 1.074588
|
606
|
+
…
|
607
|
+
2050 1.000000
|
608
|
+
2051 1.000000
|
609
|
+
dtype: float64
|
610
|
+
"""
|
611
|
+
|
612
|
+
floor_area_over_population_growth = pd.Series(
|
613
|
+
data=[np.nan] + list(itertools.repeat(1, len(years) - 1)),
|
614
|
+
index=years.to_index())
|
615
|
+
|
616
|
+
# Initialize with NaN for the first year
|
617
|
+
floor_area_over_population_growth.loc[years.start] = np.nan
|
618
|
+
|
619
|
+
# Calculate for the next 4 years
|
620
|
+
for year in building_growth[(building_growth > 0) & (building_growth.index > years.start)].index:
|
621
|
+
floor_area_over_population_growth[year] = building_growth.loc[year] / population_growth.loc[year]
|
622
|
+
|
623
|
+
mean_idx = building_growth[building_growth > 0].index
|
624
|
+
|
625
|
+
# If there is no growth, return 0
|
626
|
+
if not any(mean_idx):
|
627
|
+
return floor_area_over_population_growth
|
628
|
+
|
629
|
+
# Calculate for the next 6 years using the mean
|
630
|
+
mean_floor_area_population = floor_area_over_population_growth.loc[mean_idx].mean()
|
631
|
+
for year in years.subset(list(years).index(max(mean_idx) + 1), 6):
|
632
|
+
floor_area_over_population_growth.loc[year] = mean_floor_area_population
|
633
|
+
|
634
|
+
# Set to 1 from the 11th year onwards
|
635
|
+
if len(years) > 11:
|
636
|
+
for year in years.subset(11):
|
637
|
+
floor_area_over_population_growth.loc[year] = 1
|
638
|
+
|
639
|
+
# Interpolate linearly between the 11th and 21st years
|
640
|
+
for year in years.subset(11, 10):
|
641
|
+
floor_area_over_population_growth.loc[year] = \
|
642
|
+
(floor_area_over_population_growth.loc[years.start + 10] - (year - (years.start + 10)) * (
|
643
|
+
(floor_area_over_population_growth.loc[
|
644
|
+
years.start + 10] -
|
645
|
+
floor_area_over_population_growth.loc[
|
646
|
+
years.start + 20]) / 10))
|
647
|
+
return floor_area_over_population_growth
|
648
|
+
|
649
|
+
@staticmethod
|
650
|
+
def calculate_construction_as_list(building_category: BuildingCategory,
|
651
|
+
demolition_floor_area: Union[pd.Series, list],
|
652
|
+
database_manager: DatabaseManager = None,
|
653
|
+
period: YearRange = YearRange(2010, 2050)) -> typing.List[float]:
|
654
|
+
"""
|
655
|
+
Calculates constructed floor area for buildings based using provided demolition_floor_area
|
656
|
+
and input data from database_manager
|
657
|
+
|
658
|
+
Parameters
|
659
|
+
----------
|
660
|
+
building_category: BuildingCategory
|
661
|
+
demolition_floor_area: pd.Series expects index=2010..2050
|
662
|
+
database_manager: DatabaseManager (optional)
|
663
|
+
period : YearRange
|
664
|
+
|
665
|
+
Returns
|
666
|
+
-------
|
667
|
+
accumulated_constructed_floor_area: List
|
668
|
+
"""
|
669
|
+
|
670
|
+
yearly_constructed = ConstructionCalculator.calculate_construction(
|
671
|
+
building_category=building_category,
|
672
|
+
demolition_floor_area=demolition_floor_area,
|
673
|
+
database_manager=database_manager if database_manager else DatabaseManager(),
|
674
|
+
period=period)
|
675
|
+
|
676
|
+
accumulated_constructed_floor_area = yearly_constructed['accumulated_constructed_floor_area'].to_list()
|
677
|
+
return accumulated_constructed_floor_area
|
678
|
+
|
679
|
+
@staticmethod
|
680
|
+
def calculate_commercial_construction(building_category: BuildingCategory,
|
681
|
+
population: pd.Series,
|
682
|
+
area_by_person: typing.Union[float, pd.Series],
|
683
|
+
demolition: pd.Series) -> pd.DataFrame:
|
684
|
+
"""
|
685
|
+
Calculate a projection of contructed floor area by building_category. The calculation makes the assumption that
|
686
|
+
all demolished floor area will be replaced with construction.
|
687
|
+
|
688
|
+
Parameters
|
689
|
+
----------
|
690
|
+
building_category : BuildingCategory
|
691
|
+
possibly redundant building_category for the construction projection
|
692
|
+
population : pd.Series
|
693
|
+
population by year
|
694
|
+
area_by_person : pd.Series
|
695
|
+
float or pd.Series containing the floor area per person for the building_category
|
696
|
+
demolition : pd.Series
|
697
|
+
yearly demolition to be added to the floor area.
|
698
|
+
|
699
|
+
Returns
|
700
|
+
-------
|
701
|
+
pd.Dataframe
|
702
|
+
floor area constructed by year
|
703
|
+
accumalated contructed floor area
|
704
|
+
"""
|
705
|
+
if not demolition.index.isin(population.index).all():
|
706
|
+
raise ValueError('years in demolition series not present in popolutation series')
|
707
|
+
|
708
|
+
total_area = area_by_person * population.loc[demolition.index]
|
709
|
+
demolition_prev_year = demolition.shift(periods=1, fill_value=0)
|
710
|
+
yearly_constructed = total_area.diff().fillna(0) + demolition_prev_year
|
711
|
+
|
712
|
+
accumulated_constructed = yearly_constructed.cumsum()
|
713
|
+
commercial_construction = pd.DataFrame({
|
714
|
+
'demolished_floor_area': demolition,
|
715
|
+
"constructed_floor_area": yearly_constructed,
|
716
|
+
"accumulated_constructed_floor_area": accumulated_constructed
|
717
|
+
})
|
718
|
+
return commercial_construction
|
719
|
+
|
720
|
+
@staticmethod
|
721
|
+
def calculate_construction(building_category: BuildingCategory, demolition_floor_area: Union[pd.Series, list],
|
722
|
+
database_manager: DatabaseManager, period: YearRange) -> pd.DataFrame:
|
723
|
+
"""
|
724
|
+
Calculates constructed floor area for buildings based using provided demolition_floor_area
|
725
|
+
and input data from database_manager
|
726
|
+
Parameters
|
727
|
+
----------
|
728
|
+
|
729
|
+
building_category: BuildingCategory
|
730
|
+
demolition_floor_area: pd.Series expects index=2010..2050
|
731
|
+
database_manager: DatabaseManager
|
732
|
+
period : YearRange
|
733
|
+
|
734
|
+
Returns
|
735
|
+
-------
|
736
|
+
calculated_construction: pd.DataFrame
|
737
|
+
dataframe columns include;
|
738
|
+
(building_growth)
|
739
|
+
(demolished_floor_area)
|
740
|
+
(constructed_floor_area)
|
741
|
+
(accumulated_constructed_floor_area)
|
742
|
+
(total_floor_area)
|
743
|
+
(floor_area_over_population_growth)
|
744
|
+
(households)
|
745
|
+
(household_size)
|
746
|
+
(population)
|
747
|
+
(population_growth)
|
748
|
+
|
749
|
+
-------
|
750
|
+
|
751
|
+
"""
|
752
|
+
if isinstance(demolition_floor_area, list):
|
753
|
+
demolition_floor_area = pd.Series(demolition_floor_area, index=period.range())
|
754
|
+
|
755
|
+
new_buildings_population = database_manager.get_construction_population()[['population', 'household_size']]
|
756
|
+
if building_category.is_non_residential():
|
757
|
+
return ConstructionCalculator.calculate_commercial_construction(
|
758
|
+
building_category=building_category,
|
759
|
+
population=new_buildings_population['population'],
|
760
|
+
area_by_person=database_manager.get_area_per_person(building_category),
|
761
|
+
demolition=demolition_floor_area
|
762
|
+
)
|
763
|
+
yearly_construction_floor_area = database_manager.get_building_category_floor_area(building_category)
|
764
|
+
area_parameters = database_manager.get_area_parameters()
|
765
|
+
total_floor_area = area_parameters[area_parameters.building_category == building_category].area.sum()
|
766
|
+
|
767
|
+
if building_category.is_residential():
|
768
|
+
household_size = new_buildings_population['household_size']
|
769
|
+
population = new_buildings_population['population']
|
770
|
+
|
771
|
+
share_name, floor_area_name = 'new_house_share', 'floor_area_new_house'
|
772
|
+
if building_category == BuildingCategory.APARTMENT_BLOCK:
|
773
|
+
share_name = 'new_apartment_block_share'
|
774
|
+
floor_area_name = 'flood_area_new_apartment_block'
|
775
|
+
new_buildings_category_share = database_manager.get_new_buildings_category_share()
|
776
|
+
building_category_share = new_buildings_category_share[share_name]
|
777
|
+
average_floor_area = new_buildings_category_share[floor_area_name]
|
778
|
+
build_area_sum = pd.Series(
|
779
|
+
data=yearly_construction_floor_area,
|
780
|
+
index=range(period.start, period.start+len(yearly_construction_floor_area)))
|
781
|
+
|
782
|
+
return ConstructionCalculator().calculate_residential_construction(population=population,
|
783
|
+
household_size=household_size,
|
784
|
+
building_category_share=building_category_share,
|
785
|
+
build_area_sum=build_area_sum,
|
786
|
+
yearly_demolished_floor_area=demolition_floor_area,
|
787
|
+
average_floor_area=average_floor_area,
|
788
|
+
period=period)
|
789
|
+
|
790
|
+
return ConstructionCalculator.calculate_industrial_construction(building_category=building_category,
|
791
|
+
total_floor_area=total_floor_area,
|
792
|
+
constructed_floor_area=yearly_construction_floor_area,
|
793
|
+
demolition_floor_area=demolition_floor_area,
|
794
|
+
population=new_buildings_population[
|
795
|
+
'population'],
|
796
|
+
period=period)
|
797
|
+
|
798
|
+
|
799
|
+
@staticmethod
|
800
|
+
def calculate_all_construction(demolition_by_year: Union[pd.Series, list],
|
801
|
+
database_manager: DatabaseManager, period: YearRange) -> pd.DataFrame:
|
802
|
+
"""
|
803
|
+
|
804
|
+
Parameters
|
805
|
+
----------
|
806
|
+
demolition_by_year : pd.Series, List[float]
|
807
|
+
database_manager : DatabaseManager
|
808
|
+
period : YearRange
|
809
|
+
|
810
|
+
Returns
|
811
|
+
-------
|
812
|
+
DataFrame:
|
813
|
+
with building_category and area
|
814
|
+
"""
|
815
|
+
|
816
|
+
construction = []
|
817
|
+
for building_category in demolition_by_year.index.get_level_values(level='building_category').unique():
|
818
|
+
df = demolition_by_year.to_frame().query(f'building_category=="{building_category}"').reset_index().set_index(['year'])
|
819
|
+
c = ConstructionCalculator.calculate_construction(
|
820
|
+
BuildingCategory.from_string(building_category),
|
821
|
+
df.demolition,
|
822
|
+
database_manager,
|
823
|
+
period)
|
824
|
+
c['building_category'] = building_category
|
825
|
+
construction.append(c.reset_index())
|
826
|
+
|
827
|
+
return pd.concat(construction)
|