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.
Files changed (80) hide show
  1. ebm/__init__.py +0 -0
  2. ebm/__main__.py +152 -0
  3. ebm/__version__.py +1 -0
  4. ebm/cmd/__init__.py +0 -0
  5. ebm/cmd/calibrate.py +83 -0
  6. ebm/cmd/calibrate_excel_com_io.py +128 -0
  7. ebm/cmd/heating_systems_by_year.py +18 -0
  8. ebm/cmd/helpers.py +134 -0
  9. ebm/cmd/initialize.py +167 -0
  10. ebm/cmd/migrate.py +92 -0
  11. ebm/cmd/pipeline.py +227 -0
  12. ebm/cmd/prepare_main.py +174 -0
  13. ebm/cmd/result_handler.py +272 -0
  14. ebm/cmd/run_calculation.py +221 -0
  15. ebm/data/area.csv +92 -0
  16. ebm/data/area_new_residential_buildings.csv +3 -0
  17. ebm/data/area_per_person.csv +12 -0
  18. ebm/data/building_code_parameters.csv +9 -0
  19. ebm/data/energy_need_behaviour_factor.csv +6 -0
  20. ebm/data/energy_need_improvements.csv +7 -0
  21. ebm/data/energy_need_original_condition.csv +534 -0
  22. ebm/data/heating_system_efficiencies.csv +13 -0
  23. ebm/data/heating_system_forecast.csv +9 -0
  24. ebm/data/heating_system_initial_shares.csv +1113 -0
  25. ebm/data/holiday_home_energy_consumption.csv +24 -0
  26. ebm/data/holiday_home_stock.csv +25 -0
  27. ebm/data/improvement_building_upgrade.csv +9 -0
  28. ebm/data/new_buildings_residential.csv +32 -0
  29. ebm/data/population_forecast.csv +51 -0
  30. ebm/data/s_curve.csv +40 -0
  31. ebm/energy_consumption.py +307 -0
  32. ebm/extractors.py +115 -0
  33. ebm/heating_system_forecast.py +472 -0
  34. ebm/holiday_home_energy.py +341 -0
  35. ebm/migrations.py +224 -0
  36. ebm/model/__init__.py +0 -0
  37. ebm/model/area.py +403 -0
  38. ebm/model/bema.py +149 -0
  39. ebm/model/building_category.py +150 -0
  40. ebm/model/building_condition.py +78 -0
  41. ebm/model/calibrate_energy_requirements.py +84 -0
  42. ebm/model/calibrate_heating_systems.py +180 -0
  43. ebm/model/column_operations.py +157 -0
  44. ebm/model/construction.py +827 -0
  45. ebm/model/data_classes.py +223 -0
  46. ebm/model/database_manager.py +410 -0
  47. ebm/model/dataframemodels.py +115 -0
  48. ebm/model/defaults.py +30 -0
  49. ebm/model/energy_need.py +6 -0
  50. ebm/model/energy_need_filter.py +182 -0
  51. ebm/model/energy_purpose.py +115 -0
  52. ebm/model/energy_requirement.py +353 -0
  53. ebm/model/energy_use.py +202 -0
  54. ebm/model/enums.py +8 -0
  55. ebm/model/exceptions.py +4 -0
  56. ebm/model/file_handler.py +388 -0
  57. ebm/model/filter_scurve_params.py +83 -0
  58. ebm/model/filter_tek.py +152 -0
  59. ebm/model/heat_pump.py +53 -0
  60. ebm/model/heating_systems.py +20 -0
  61. ebm/model/heating_systems_parameter.py +17 -0
  62. ebm/model/heating_systems_projection.py +3 -0
  63. ebm/model/heating_systems_share.py +28 -0
  64. ebm/model/scurve.py +224 -0
  65. ebm/model/tek.py +1 -0
  66. ebm/s_curve.py +515 -0
  67. ebm/services/__init__.py +0 -0
  68. ebm/services/calibration_writer.py +262 -0
  69. ebm/services/console.py +106 -0
  70. ebm/services/excel_loader.py +66 -0
  71. ebm/services/files.py +38 -0
  72. ebm/services/spreadsheet.py +289 -0
  73. ebm/temp_calc.py +99 -0
  74. ebm/validators.py +565 -0
  75. ebm-0.99.5.dist-info/METADATA +212 -0
  76. ebm-0.99.5.dist-info/RECORD +80 -0
  77. ebm-0.99.5.dist-info/WHEEL +5 -0
  78. ebm-0.99.5.dist-info/entry_points.txt +3 -0
  79. ebm-0.99.5.dist-info/licenses/LICENSE +21 -0
  80. 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)