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,353 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
import numpy as np
|
5
|
+
import pandas as pd
|
6
|
+
|
7
|
+
from ebm.model.database_manager import DatabaseManager
|
8
|
+
from ebm.model.building_category import BuildingCategory
|
9
|
+
from ebm.model.building_condition import BuildingCondition
|
10
|
+
from ebm.model.data_classes import YearRange
|
11
|
+
from ebm.model.energy_purpose import EnergyPurpose
|
12
|
+
from ebm.model.file_handler import FileHandler
|
13
|
+
from ebm.services.files import make_unique_path
|
14
|
+
|
15
|
+
|
16
|
+
def yearly_reduction(x):
|
17
|
+
if x.year < x.period_start_year:
|
18
|
+
return 1.0
|
19
|
+
if x.year > x.period_end_year:
|
20
|
+
return round(1.0 - x.improvement_at_period_end, 15)
|
21
|
+
ls = np.linspace(1.0, 1.0 - x.improvement_at_period_end, int(x.period_end_year - x.period_start_year + 1.0))[
|
22
|
+
x.year_no]
|
23
|
+
return round(ls, 15) # x.year_no.astype(int)
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
class EnergyRequirement:
|
28
|
+
def __init__(self,
|
29
|
+
building_code_list: typing.List[str],
|
30
|
+
period: YearRange = YearRange(2010, 2050),
|
31
|
+
calibration_year: int = 1999,
|
32
|
+
database_manager = None):
|
33
|
+
self.building_code_list = building_code_list
|
34
|
+
self.period = period
|
35
|
+
self.calibration_year = calibration_year
|
36
|
+
if calibration_year == period.start:
|
37
|
+
logger.trace(f'Calibration year {calibration_year} is same as start year {period.start}')
|
38
|
+
elif calibration_year not in period.subset(1):
|
39
|
+
logger.debug(f'Calibration year {calibration_year} is outside period {period.start}-{period.end}')
|
40
|
+
|
41
|
+
self.database_manager = database_manager
|
42
|
+
|
43
|
+
|
44
|
+
def calculate_for_building_category(self, database_manager: DatabaseManager = None) -> pd.DataFrame:
|
45
|
+
"""
|
46
|
+
Calculates energy requirements for a single building category
|
47
|
+
|
48
|
+
Parameters
|
49
|
+
----------
|
50
|
+
database_manager: DatabaseManager
|
51
|
+
optional database_manager used to load input parameters
|
52
|
+
|
53
|
+
Returns
|
54
|
+
-------
|
55
|
+
Iterable of pd.Series
|
56
|
+
indexed by year, building_category, TEK, purpose, building_condition
|
57
|
+
column kwh_m2 representing energy requirement
|
58
|
+
|
59
|
+
"""
|
60
|
+
database_manager = database_manager if database_manager else self.database_manager
|
61
|
+
|
62
|
+
all_building_codes = database_manager.get_building_code_list().tolist()
|
63
|
+
all_building_categories = list(BuildingCategory)
|
64
|
+
all_purpose = list(EnergyPurpose)
|
65
|
+
most_conditions = list(BuildingCondition.existing_conditions())
|
66
|
+
model_years = YearRange(2020, 2050)
|
67
|
+
erq_oc = database_manager.get_energy_req_original_condition()
|
68
|
+
|
69
|
+
merged = self.calculate_energy_requirement(all_building_categories, all_purpose, all_building_codes, erq_oc, model_years,
|
70
|
+
most_conditions, database_manager)
|
71
|
+
|
72
|
+
merged = merged.drop_duplicates('building_category,building_code,building_condition,year,purpose'.split(','), keep='first')
|
73
|
+
return merged[['building_category', 'building_code', 'building_condition','year', 'purpose',
|
74
|
+
'original_kwh_m2', 'reduction_yearly', 'reduction_policy', 'reduction_condition',
|
75
|
+
'reduced_kwh_m2', 'behaviour_factor', 'kwh_m2']]
|
76
|
+
|
77
|
+
def calculate_energy_requirement(self, all_building_categories, all_purpose, all_building_codes, energy_requirement_original_condition, model_years,
|
78
|
+
most_conditions, database_manager) -> pd.DataFrame:
|
79
|
+
df_bc = pd.DataFrame(all_building_categories, columns=['building_category'])
|
80
|
+
df_building_code = pd.merge(df_bc, pd.DataFrame({'building_code': all_building_codes}), how='cross')
|
81
|
+
df_purpose = pd.merge(df_building_code, pd.DataFrame(all_purpose, columns=['purpose']), how='cross')
|
82
|
+
df_condition = pd.merge(df_purpose, pd.DataFrame({'building_condition': most_conditions}), how='cross')
|
83
|
+
df_years = pd.merge(df_condition, pd.DataFrame({'year': model_years}), how='cross')
|
84
|
+
|
85
|
+
energy_requirement_original_condition = energy_requirement_original_condition.copy()
|
86
|
+
|
87
|
+
energy_requirement_original_condition = energy_requirement_original_condition.join(
|
88
|
+
pd.DataFrame({'building_condition_r': most_conditions}),
|
89
|
+
how='cross',
|
90
|
+
)
|
91
|
+
energy_requirement_original_condition['building_condition'] = energy_requirement_original_condition.building_condition_r
|
92
|
+
energy_requirement_original_condition = energy_requirement_original_condition.drop(columns=['building_condition_r'])
|
93
|
+
|
94
|
+
erq_all_years = pd.merge(left=df_years, right=energy_requirement_original_condition, how='left')
|
95
|
+
energy_requirements = erq_all_years.drop(columns=['index', 'level_0'], errors='ignore')
|
96
|
+
|
97
|
+
reduction_per_condition = database_manager.get_energy_req_reduction_per_condition()
|
98
|
+
policy_improvement = database_manager.get_energy_need_policy_improvement()
|
99
|
+
yearly_improvement = database_manager.get_energy_need_yearly_improvements()
|
100
|
+
|
101
|
+
return self.calculate_energy_reduction(energy_requirements, model_years, policy_improvement,
|
102
|
+
reduction_per_condition, yearly_improvement)
|
103
|
+
|
104
|
+
def calculate_energy_reduction(self, energy_requirements: pd.DataFrame,
|
105
|
+
model_years: YearRange,
|
106
|
+
policy_improvement: pd.DataFrame,
|
107
|
+
reduction_per_condition: pd.DataFrame,
|
108
|
+
yearly_improvement: pd.DataFrame) -> pd.DataFrame:
|
109
|
+
"""
|
110
|
+
Calculate and combine all reduction factors for energy needs into a single Dataframe.
|
111
|
+
|
112
|
+
Parameters
|
113
|
+
----------
|
114
|
+
energy_requirements : pd.DataFrame
|
115
|
+
model_years : YearRange
|
116
|
+
policy_improvement : pd.DataFrame
|
117
|
+
reduction_per_condition : pd.DataFrame
|
118
|
+
yearly_improvement : pd.DataFrame
|
119
|
+
|
120
|
+
Returns
|
121
|
+
-------
|
122
|
+
pd.DataFrame
|
123
|
+
"""
|
124
|
+
reduction_condition = self.calculate_reduction_condition(reduction_per_condition)
|
125
|
+
condition_factor = pd.merge(left=energy_requirements, right=reduction_condition,
|
126
|
+
on=['building_category', 'building_code', 'building_condition', 'purpose'],
|
127
|
+
how='left')
|
128
|
+
|
129
|
+
reduction_policy = self.calculate_reduction_policy(policy_improvement, energy_requirements)
|
130
|
+
reduction_yearly = self.calculate_reduction_yearly(energy_requirements, yearly_improvement)
|
131
|
+
|
132
|
+
merged = self.merge_energy_requirement_reductions(condition_factor, reduction_yearly, reduction_policy)
|
133
|
+
|
134
|
+
return merged
|
135
|
+
|
136
|
+
def merge_energy_requirement_reductions(self, condition_factor, yearly_improvements, reduction_policy):
|
137
|
+
m_nrg_yi = pd.merge(left=condition_factor,
|
138
|
+
right=yearly_improvements.copy(),
|
139
|
+
on=['building_category', 'building_code', 'purpose', 'year'],
|
140
|
+
how='left')
|
141
|
+
m_nrg_yi = pd.merge(left=m_nrg_yi,
|
142
|
+
right=reduction_policy.copy(),
|
143
|
+
on=['building_category', 'building_code', 'purpose', 'year'],
|
144
|
+
how='left')
|
145
|
+
merged = m_nrg_yi.copy()
|
146
|
+
merged.loc[:, 'reduction_yearly'] = merged.loc[:, 'reduction_yearly'].fillna(1.0)
|
147
|
+
|
148
|
+
merged.loc[:, 'reduction_policy'] = merged.loc[:, 'reduction_policy'].fillna(1.0)
|
149
|
+
merged['reduction_condition'] = merged['reduction_condition'].fillna(1.0)
|
150
|
+
merged['reduced_kwh_m2'] = (merged['kwh_m2'] * merged['reduction_condition'].fillna(1.0) *
|
151
|
+
merged['reduction_yearly'].fillna(1.0) * merged['reduction_policy'].fillna(1.0))
|
152
|
+
merged['behavior_kwh_m2'] = merged['reduced_kwh_m2'] * merged['behaviour_factor'].fillna(1.0)
|
153
|
+
merged = merged.rename(columns={'kwh_m2': 'original_kwh_m2'})
|
154
|
+
merged['kwh_m2'] = merged['behavior_kwh_m2']
|
155
|
+
return merged
|
156
|
+
|
157
|
+
def calculate_reduction_yearly(self, df_years: pd.DataFrame, yearly_improvement: pd.DataFrame) -> pd.DataFrame:
|
158
|
+
"""
|
159
|
+
Calculate factor for yearly reduction for each entry in the DataFrame yearly_improvement.
|
160
|
+
|
161
|
+
This method merges the yearly improvement data with the policy improvement data, adjusts the
|
162
|
+
efficiency start year if the period end year is greater, and calculates the yearly reduction
|
163
|
+
based on the yearly efficiency improvement.
|
164
|
+
|
165
|
+
Parameters
|
166
|
+
----------
|
167
|
+
df_years : pd.DataFrame
|
168
|
+
DataFrame containing all years for which to calculate factors. Must include column 'year'.
|
169
|
+
yearly_improvement : pd.DataFrame
|
170
|
+
DataFrame containing yearly improvement information. Must include columns 'yearly_efficiency_improvement', and 'efficiency_start_year'.
|
171
|
+
|
172
|
+
Returns
|
173
|
+
-------
|
174
|
+
pd.DataFrame
|
175
|
+
DataFrame with the calculated 'reduction_yearly' column and updated entries.
|
176
|
+
"""
|
177
|
+
required_in_yearly_improvement = {'yearly_efficiency_improvement', 'start_year', 'end_year'}
|
178
|
+
if not required_in_yearly_improvement.issubset(yearly_improvement.columns):
|
179
|
+
logger.debug(f'Got columns {", ".join(yearly_improvement.columns)}')
|
180
|
+
missing = required_in_yearly_improvement.difference(yearly_improvement.columns)
|
181
|
+
raise ValueError('Required column{} not found in yearly_improvement: {}'.format(
|
182
|
+
's' if len(missing) != 1 else '', missing
|
183
|
+
))
|
184
|
+
if 'year' not in df_years:
|
185
|
+
logger.debug(f'Got columns {", ".join(df_years.columns)}')
|
186
|
+
raise ValueError('df_years does not contain column year')
|
187
|
+
|
188
|
+
years = pd.DataFrame(data=[y for y in df_years.year.unique()], columns=['year'])
|
189
|
+
|
190
|
+
df = pd.merge(left=yearly_improvement, right=years, how='cross')
|
191
|
+
rows_in_range = df[(df.year >= df.start_year) & (df.year <= df.end_year)].index
|
192
|
+
|
193
|
+
df.loc[rows_in_range, 'yearly_change'] = (1.0 - df.loc[rows_in_range, 'yearly_efficiency_improvement'])
|
194
|
+
df.loc[rows_in_range, 'pow'] = (df.loc[rows_in_range, 'year'] - df.loc[rows_in_range, 'start_year']) + 1
|
195
|
+
df.loc[rows_in_range, 'reduction_yearly'] = df.loc[rows_in_range, 'yearly_change'] ** df.loc[rows_in_range, 'pow']
|
196
|
+
|
197
|
+
df.loc[df[df.start_year > df.year].index, 'reduction_yearly'] = df.loc[
|
198
|
+
df[df.start_year > df.year].index, 'reduction_yearly'].fillna(1.0)
|
199
|
+
df.loc[:, 'reduction_yearly'] = df.loc[:, 'reduction_yearly'].ffill()
|
200
|
+
|
201
|
+
return df[['building_category', 'building_code', 'purpose', 'year', 'reduction_yearly']]
|
202
|
+
|
203
|
+
|
204
|
+
def calculate_reduction_policy(self, policy_improvement: pd.DataFrame, all_things) -> pd.DataFrame:
|
205
|
+
"""
|
206
|
+
Calculate the reduction policy for each entry in the DataFrame.
|
207
|
+
|
208
|
+
This method computes the reduction policy by first calculating the number of years since the
|
209
|
+
start of the period. It then applies the `yearly_reduction` function to each relevant entry
|
210
|
+
to determine the reduction policy.
|
211
|
+
|
212
|
+
Parameters
|
213
|
+
----------
|
214
|
+
policy_improvement : pd.DataFrame
|
215
|
+
DataFrame containing policy improvement information. Must include columns 'year' and 'period_start_year'.
|
216
|
+
all_things: pd.DataFrame
|
217
|
+
DataFrame containing every combination of building_category, TEK, purpose, year
|
218
|
+
|
219
|
+
Returns
|
220
|
+
-------
|
221
|
+
pd.DataFrame
|
222
|
+
DataFrame with the calculated 'reduction_policy' column and updated entries.
|
223
|
+
"""
|
224
|
+
policy_improvement = policy_improvement.sort_values(
|
225
|
+
by=['building_category', 'building_code', 'purpose', 'start_year', 'end_year'])
|
226
|
+
|
227
|
+
policy_improvement[['building_category_s', 'TEK_s', 'purpose_s', 'start_year_s', 'end_year_s']] = \
|
228
|
+
policy_improvement[
|
229
|
+
['building_category', 'building_code', 'purpose', 'start_year', 'end_year']]
|
230
|
+
|
231
|
+
policy_improvement = policy_improvement.set_index(
|
232
|
+
['building_category', 'building_code', 'purpose', 'start_year', 'end_year'], drop=True)
|
233
|
+
|
234
|
+
shifted = policy_improvement.shift(1).reset_index()
|
235
|
+
|
236
|
+
shifted = shifted.query('building_category==building_category_s & building_code==TEK_s & purpose==purpose_s')
|
237
|
+
shifted['improvement_at_start_year'] = shifted['improvement_at_end_year']
|
238
|
+
shifted = shifted[['building_category', 'building_code', 'purpose', 'start_year', 'end_year', 'improvement_at_start_year']]
|
239
|
+
|
240
|
+
start_year_from_previous = shifted
|
241
|
+
|
242
|
+
policy_improvement = pd.merge(left=policy_improvement,
|
243
|
+
right=start_year_from_previous,
|
244
|
+
left_on=['building_category', 'building_code', 'purpose', 'start_year', 'end_year'],
|
245
|
+
right_on=['building_category', 'building_code', 'purpose', 'start_year', 'end_year'],
|
246
|
+
how='left'
|
247
|
+
)
|
248
|
+
|
249
|
+
policy_improvement[['start_year', 'end_year']] = policy_improvement[['start_year', 'end_year']].astype(int)
|
250
|
+
policy_improvement = policy_improvement.set_index(
|
251
|
+
['building_category', 'building_code', 'purpose', 'start_year', 'end_year'], drop=True)
|
252
|
+
policy_improvement['improvement_at_start_year'] = 1.0-policy_improvement['improvement_at_start_year'].fillna(0.0)
|
253
|
+
|
254
|
+
policy_improvement = policy_improvement[['improvement_at_start_year', 'improvement_at_end_year']].reset_index()
|
255
|
+
|
256
|
+
df = pd.merge(left=all_things[['building_category', 'building_code', 'purpose', 'year']],
|
257
|
+
right=policy_improvement,
|
258
|
+
on=['building_category', 'building_code', 'purpose'], how='left')
|
259
|
+
|
260
|
+
df['num_values'] = df['end_year'] - df['start_year'] + 1.0
|
261
|
+
df['n'] = (df.year - df.start_year).clip(upper=df.num_values-1, lower=0)
|
262
|
+
|
263
|
+
df['step'] = ((1.0-df['improvement_at_end_year']) - df['improvement_at_start_year']) / (df['num_values']-1.0)
|
264
|
+
|
265
|
+
df['reduction_policy'] = df['improvement_at_start_year'] + (df['n']) * df['step']
|
266
|
+
df['reduction_policy'] = df['reduction_policy'].fillna(1.0)
|
267
|
+
df['_col_to_filter'] = (df['year'] < df['start_year']) | (df['year'] > df['end_year'])
|
268
|
+
df = df.sort_values(by=['building_category', 'building_code', 'purpose', 'year', '_col_to_filter'])
|
269
|
+
df = df.drop_duplicates(['building_category', 'building_code', 'purpose', 'year'])
|
270
|
+
|
271
|
+
return df[['building_category', 'building_code', 'purpose', 'year', 'reduction_policy']]
|
272
|
+
|
273
|
+
|
274
|
+
def calculate_reduction_condition(self, reduction_per_condition: pd.DataFrame) -> pd.DataFrame:
|
275
|
+
"""
|
276
|
+
Calculate the reduction condition for each entry in the DataFrame.
|
277
|
+
|
278
|
+
This method computes the reduction condition by subtracting the reduction share from 1.0.
|
279
|
+
It also fills any NaN values in the 'reduction_condition' column with 1.0.
|
280
|
+
|
281
|
+
Parameters
|
282
|
+
----------
|
283
|
+
reduction_per_condition : pd.DataFrame
|
284
|
+
DataFrame containing the reduction share information. Must include columns 'reduction_share' and 'building_code'.
|
285
|
+
|
286
|
+
Returns
|
287
|
+
-------
|
288
|
+
pd.DataFrame
|
289
|
+
DataFrame with the calculated 'reduction_condition' column and filtered entries.
|
290
|
+
"""
|
291
|
+
reduction_per_condition['reduction_condition'] = 1.0 - reduction_per_condition['reduction_share']
|
292
|
+
reduction_per_condition.loc[:, 'reduction_condition'] = reduction_per_condition.loc[:,
|
293
|
+
'reduction_condition'].fillna(1.0)
|
294
|
+
return reduction_per_condition
|
295
|
+
|
296
|
+
def calculate_energy_requirements(
|
297
|
+
self,
|
298
|
+
building_categories: typing.Iterable[BuildingCategory] = None) -> pd.DataFrame:
|
299
|
+
"""
|
300
|
+
Calculates energy requirements for building categories
|
301
|
+
|
302
|
+
Parameters
|
303
|
+
----------
|
304
|
+
building_categories : Iterable[BuildingCategory]
|
305
|
+
Iterable containing building categories on which to calculate energy requirements.
|
306
|
+
|
307
|
+
Returns
|
308
|
+
-------
|
309
|
+
Iterable of pd.Series
|
310
|
+
indexed by year, building_category, TEK, purpose, building_condition
|
311
|
+
column kwh_m2 representing energy requirement
|
312
|
+
|
313
|
+
"""
|
314
|
+
building_categories = building_categories if building_categories else iter(BuildingCategory)
|
315
|
+
|
316
|
+
return self.calculate_for_building_category(self.database_manager)
|
317
|
+
|
318
|
+
@staticmethod
|
319
|
+
def new_instance(period, calibration_year, database_manager=None):
|
320
|
+
if period.start != 2010 and calibration_year != calibration_year:
|
321
|
+
logger.warning(f'EnergyRequirements {period.start=} {calibration_year=}')
|
322
|
+
dm = database_manager if isinstance(database_manager, DatabaseManager) else DatabaseManager()
|
323
|
+
instance = EnergyRequirement(building_code_list=dm.get_building_code_list(), period=period, calibration_year=calibration_year,
|
324
|
+
database_manager=dm)
|
325
|
+
return instance
|
326
|
+
|
327
|
+
|
328
|
+
def main():
|
329
|
+
import os
|
330
|
+
import sys
|
331
|
+
import pathlib
|
332
|
+
log_format = """
|
333
|
+
<blue>{elapsed}</blue> | <level>{level: <8}</level> | <cyan>{function: <20}</cyan>:<cyan>{line: <3}</cyan> - <level>{message}</level>
|
334
|
+
""".strip()
|
335
|
+
logger.remove()
|
336
|
+
logger.add(sys.stderr, format=log_format, level='WARNING')
|
337
|
+
|
338
|
+
dm = DatabaseManager(FileHandler(directory='kalibrering'))
|
339
|
+
er = EnergyRequirement.new_instance(YearRange(2020, 2050), calibration_year=2020, database_manager=dm)
|
340
|
+
|
341
|
+
logger.error('Calculating')
|
342
|
+
df = er.calculate_energy_requirements()
|
343
|
+
logger.error('Writing to file')
|
344
|
+
|
345
|
+
xlsx_filename = make_unique_path(pathlib.Path('output/er.xlsx'))
|
346
|
+
df.to_excel(xlsx_filename)
|
347
|
+
|
348
|
+
logger.error('DONE')
|
349
|
+
os.startfile(xlsx_filename, 'open')
|
350
|
+
|
351
|
+
|
352
|
+
if __name__ == '__main__':
|
353
|
+
main()
|
ebm/model/energy_use.py
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import pandas as pd
|
3
|
+
|
4
|
+
from ebm import extractors
|
5
|
+
from ebm.energy_consumption import HEATING_SYSTEM_SHARE, GRUNNLAST_ANDEL, BASE_LOAD_EFFICIENCY, BASE_LOAD_ENERGY_PRODUCT, \
|
6
|
+
PEAK_LOAD_ENERGY_PRODUCT, PEAK_LOAD_COVERAGE, PEAK_LOAD_EFFICIENCY, TERTIARY_LOAD_COVERAGE, TERTIARY_LOAD_EFFICIENCY, \
|
7
|
+
TERTIARY_LOAD_ENERGY_PRODUCT, COOLING_EFFICIENCY, DHW_EFFICIENCY, DOMESTIC_HOT_WATER_ENERGY_PRODUCT
|
8
|
+
from ebm.model import energy_need as e_n, heating_systems_parameter as h_s_param
|
9
|
+
from ebm.model.data_classes import YearRange
|
10
|
+
from ebm.model.database_manager import DatabaseManager
|
11
|
+
from ebm.s_curve import calculate_s_curves
|
12
|
+
|
13
|
+
|
14
|
+
def base_load(heating_systems_projection: pd.DataFrame) -> pd.DataFrame:
|
15
|
+
heating_systems_projection['heating_system'] = '-'
|
16
|
+
df = heating_systems_projection[
|
17
|
+
['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, GRUNNLAST_ANDEL, BASE_LOAD_EFFICIENCY,
|
18
|
+
BASE_LOAD_ENERGY_PRODUCT, 'heating_system']].copy()
|
19
|
+
df = df.rename(columns={GRUNNLAST_ANDEL: 'load_share', BASE_LOAD_EFFICIENCY: 'load_efficiency',
|
20
|
+
BASE_LOAD_ENERGY_PRODUCT: 'energy_product'})
|
21
|
+
df.loc[:, 'load'] = 'base'
|
22
|
+
df.loc[:, 'purpose'] = 'heating_rv'
|
23
|
+
df['heating_system'] = df.heating_systems.apply(lambda s: s.split('-')[0])
|
24
|
+
df['heating_system'] = df['heating_system'].str.strip()
|
25
|
+
return df
|
26
|
+
|
27
|
+
|
28
|
+
def peak_load(heating_systems_projection:pd.DataFrame) -> pd.DataFrame:
|
29
|
+
df = heating_systems_projection[
|
30
|
+
['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, PEAK_LOAD_COVERAGE, PEAK_LOAD_EFFICIENCY,
|
31
|
+
PEAK_LOAD_ENERGY_PRODUCT, 'heating_system']].copy()
|
32
|
+
df = df.rename(columns={PEAK_LOAD_COVERAGE: 'load_share', PEAK_LOAD_EFFICIENCY: 'load_efficiency',
|
33
|
+
PEAK_LOAD_ENERGY_PRODUCT: 'energy_product'})
|
34
|
+
df.loc[:, 'load'] = 'peak'
|
35
|
+
df.loc[:, 'purpose'] = 'heating_rv'
|
36
|
+
df['heating_system'] = df.heating_systems.apply(lambda s: s.split('-')[1:2]).explode('heating_system')
|
37
|
+
df['heating_system'] = df['heating_system'].str.strip()
|
38
|
+
return df
|
39
|
+
|
40
|
+
|
41
|
+
def tertiary_load(heating_systems_projection: pd.DataFrame) ->pd.DataFrame:
|
42
|
+
df = heating_systems_projection[
|
43
|
+
['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, TERTIARY_LOAD_COVERAGE, TERTIARY_LOAD_EFFICIENCY,
|
44
|
+
TERTIARY_LOAD_ENERGY_PRODUCT, 'heating_system']].copy()
|
45
|
+
df = df.rename(columns={TERTIARY_LOAD_COVERAGE: 'load_share', TERTIARY_LOAD_EFFICIENCY: 'load_efficiency',
|
46
|
+
TERTIARY_LOAD_ENERGY_PRODUCT: 'energy_product'})
|
47
|
+
df.loc[:, 'load'] = 'tertiary'
|
48
|
+
df.loc[:, 'purpose'] = 'heating_rv'
|
49
|
+
df['heating_system'] = df.heating_systems.apply(lambda s: s.split('-')[2:3]).explode('heating_system')
|
50
|
+
df['heating_system'] = df['heating_system'].str.strip()
|
51
|
+
return df
|
52
|
+
|
53
|
+
|
54
|
+
def heating_rv(heating_systems_projection: pd.DataFrame) -> pd.DataFrame:
|
55
|
+
df = heating_systems_projection.copy()
|
56
|
+
|
57
|
+
base = base_load(df)
|
58
|
+
peak = peak_load(df)
|
59
|
+
tertiary = tertiary_load(df)
|
60
|
+
|
61
|
+
return pd.concat([base, peak, tertiary])
|
62
|
+
|
63
|
+
|
64
|
+
def heating_dhw(heating_systems_projection: pd.DataFrame) ->pd.DataFrame:
|
65
|
+
df = heating_systems_projection[
|
66
|
+
['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, GRUNNLAST_ANDEL, DHW_EFFICIENCY,
|
67
|
+
DOMESTIC_HOT_WATER_ENERGY_PRODUCT]].copy()
|
68
|
+
df.loc[:, GRUNNLAST_ANDEL] = 1.0
|
69
|
+
df = df.rename(columns={GRUNNLAST_ANDEL: 'load_share', DHW_EFFICIENCY: 'load_efficiency',
|
70
|
+
DOMESTIC_HOT_WATER_ENERGY_PRODUCT: 'energy_product'})
|
71
|
+
|
72
|
+
df.loc[:, 'load'] = 'dhw'
|
73
|
+
df['purpose'] = 'heating_dhw'
|
74
|
+
return df
|
75
|
+
|
76
|
+
|
77
|
+
def cooling(heating_systems_projection: pd.DataFrame) -> pd.DataFrame:
|
78
|
+
df = heating_systems_projection[
|
79
|
+
['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, GRUNNLAST_ANDEL, COOLING_EFFICIENCY,
|
80
|
+
BASE_LOAD_ENERGY_PRODUCT]].copy()
|
81
|
+
df.loc[:, GRUNNLAST_ANDEL] = 1.0
|
82
|
+
df.loc[:, BASE_LOAD_ENERGY_PRODUCT] = 'Electricity'
|
83
|
+
df = df.rename(columns={GRUNNLAST_ANDEL: 'load_share', COOLING_EFFICIENCY: 'load_efficiency',
|
84
|
+
BASE_LOAD_ENERGY_PRODUCT: 'energy_product'})
|
85
|
+
|
86
|
+
df.loc[:, 'load'] = 'base'
|
87
|
+
df.loc[:, 'purpose'] = 'cooling'
|
88
|
+
return df
|
89
|
+
|
90
|
+
|
91
|
+
def other(heating_systems_projection: pd.DataFrame) -> pd.DataFrame:
|
92
|
+
df = heating_systems_projection[
|
93
|
+
['building_category', 'building_code', 'year', 'heating_systems', HEATING_SYSTEM_SHARE, GRUNNLAST_ANDEL, BASE_LOAD_EFFICIENCY,
|
94
|
+
BASE_LOAD_ENERGY_PRODUCT]].copy()
|
95
|
+
df.loc[:, GRUNNLAST_ANDEL] = 1.0
|
96
|
+
df.loc[:, BASE_LOAD_EFFICIENCY] = 1.0
|
97
|
+
df.loc[:, BASE_LOAD_ENERGY_PRODUCT] = 'Electricity'
|
98
|
+
df = df.rename(columns={GRUNNLAST_ANDEL: 'load_share', BASE_LOAD_EFFICIENCY: 'load_efficiency',
|
99
|
+
BASE_LOAD_ENERGY_PRODUCT: 'energy_product'})
|
100
|
+
df.loc[:, 'load'] = 'base'
|
101
|
+
df.loc[:, '_purposes'] = 'electrical_equipment,fans_and_pumps,lighting'
|
102
|
+
df = df.assign(**{'purpose': df['_purposes'].str.split(',')}).explode('purpose')
|
103
|
+
df = df.drop(columns=['_purposes'], errors='ignore')
|
104
|
+
|
105
|
+
return df.reset_index().drop(columns=['index'], errors='ignore')
|
106
|
+
|
107
|
+
|
108
|
+
def all_purposes(heating_systems_projection: pd.DataFrame) -> pd.DataFrame:
|
109
|
+
return pd.concat([heating_rv(heating_systems_projection), heating_dhw(heating_systems_projection),
|
110
|
+
cooling(heating_systems_projection), other(heating_systems_projection)])
|
111
|
+
|
112
|
+
|
113
|
+
def efficiency_factor(heating_systems: pd.DataFrame) -> pd.DataFrame:
|
114
|
+
df = heating_systems
|
115
|
+
df.loc[:, 'efficiency_factor'] = df.loc[:, 'heating_system_share'] * df.loc[:, 'load_share'] / df.loc[:, 'load_efficiency']
|
116
|
+
|
117
|
+
return df
|
118
|
+
|
119
|
+
|
120
|
+
def energy_use_kwh(energy_need: pd.DataFrame, efficiency_factor: pd.DataFrame) -> pd.DataFrame:
|
121
|
+
nrj = energy_need.copy()
|
122
|
+
|
123
|
+
df = nrj.reset_index().merge(efficiency_factor,
|
124
|
+
left_on=['building_category', 'building_code', 'purpose', 'year'],
|
125
|
+
right_on=['building_category', 'building_code', 'purpose', 'year'])
|
126
|
+
|
127
|
+
df['kwh'] = df['energy_requirement'] * df['heating_system_share'] * df['load_share'] / df['load_efficiency']
|
128
|
+
if 'kwh_m2' in df.columns:
|
129
|
+
df['kwh_m2'] = df['kwh_m2'] * df['efficiency_factor']
|
130
|
+
else:
|
131
|
+
df['kwh_m2'] = np.nan
|
132
|
+
return df
|
133
|
+
|
134
|
+
|
135
|
+
def building_group_energy_use_kwh(heating_systems_parameter: pd.DataFrame, energy_need: pd.DataFrame) -> pd.DataFrame:
|
136
|
+
df = all_purposes(heating_systems_parameter)
|
137
|
+
df.loc[:, 'building_group'] = 'yrkesbygg'
|
138
|
+
df.loc[df.building_category.isin(['house', 'apartment_block']), 'building_group'] = 'bolig'
|
139
|
+
|
140
|
+
efficiency_factor_df = efficiency_factor(df)
|
141
|
+
df = energy_use_kwh(energy_need=energy_need, efficiency_factor=efficiency_factor_df)
|
142
|
+
|
143
|
+
return df
|
144
|
+
|
145
|
+
|
146
|
+
def energy_use_gwh_by_building_group(energy_use_kwh: pd.DataFrame) -> pd.DataFrame:
|
147
|
+
energy_use_by_building_group = energy_use_kwh[['building_group', 'year', 'energy_product', 'kwh']].groupby(
|
148
|
+
by=['building_group', 'energy_product', 'year']).sum() / 1_000_000
|
149
|
+
energy_use_wide = energy_use_by_building_group.reset_index().pivot(columns=['year'],
|
150
|
+
index=['building_group', 'energy_product'],
|
151
|
+
values=['kwh'])
|
152
|
+
energy_use_wide = energy_use_wide.reset_index()
|
153
|
+
energy_use_wide.columns = ['building_group', 'energy_source'] + [c for c in energy_use_wide.columns.get_level_values(1)[2:]]
|
154
|
+
return energy_use_wide
|
155
|
+
|
156
|
+
|
157
|
+
def calculate_energy_use(database_manager: 'DatabaseManager',
|
158
|
+
years: YearRange|None=YearRange(2020, 2050),
|
159
|
+
area_parameters:pd.DataFrame|None=None,
|
160
|
+
scurve_parameters: pd.DataFrame|None=None,
|
161
|
+
building_code_parameters:pd.DataFrame|None=None) -> pd.DataFrame:
|
162
|
+
"""
|
163
|
+
calculates energy use in KWh by building_category, TEK, building_condition, year, purpose.
|
164
|
+
|
165
|
+
The dataframe is index by row index. (subject to change)
|
166
|
+
|
167
|
+
extra columns
|
168
|
+
m2, original_kwh_m2, reduction_yearly, reduction_policy, reduction_condition, reduced_kwh_m2,
|
169
|
+
behaviour_factor, kwh_m2, energy_requirement, heating_systems,
|
170
|
+
heating_system_share, load_share, load_efficiency, energy_product,
|
171
|
+
heating_system, load, building_group, efficiency_factor
|
172
|
+
|
173
|
+
Parameters
|
174
|
+
----------
|
175
|
+
database_manager : pd.DataFrame
|
176
|
+
years : YearRange, optional
|
177
|
+
area_parameters : pd.DataFrame, optional
|
178
|
+
scurve_parameters : pd.DataFrame, optional
|
179
|
+
building_code_parameters : pd.DataFrame, optional
|
180
|
+
|
181
|
+
Returns
|
182
|
+
-------
|
183
|
+
pd.DataFrame
|
184
|
+
energy use in KWh by building_category, TEK, building_condition, year, purpose
|
185
|
+
,
|
186
|
+
"""
|
187
|
+
scurve_parameters = database_manager.get_scurve_params() if scurve_parameters is None else scurve_parameters
|
188
|
+
area_parameters = database_manager.get_area_parameters() if area_parameters is None else area_parameters
|
189
|
+
area_parameters['year'] = years.start
|
190
|
+
building_code_parameters = database_manager.file_handler.get_building_code() if building_code_parameters is None else building_code_parameters
|
191
|
+
|
192
|
+
s_curves_by_condition = calculate_s_curves(scurve_parameters, building_code_parameters, years) # 📌
|
193
|
+
area_forecast = extractors.extract_area_forecast(years, s_curves_by_condition, building_code_parameters, area_parameters, database_manager) # 📍
|
194
|
+
|
195
|
+
energy_need_kwh_m2 = extractors.extract_energy_need(years, database_manager) # 📍
|
196
|
+
total_energy_need = e_n.transform_total_energy_need(energy_need_kwh_m2, area_forecast) # 📌
|
197
|
+
|
198
|
+
heating_systems_projection = extractors.extract_heating_systems_forecast(years, database_manager) # 📍
|
199
|
+
heating_systems_parameter = h_s_param.heating_systems_parameter_from_projection(heating_systems_projection) # 📌
|
200
|
+
energy_use_kwh_with_building_group = building_group_energy_use_kwh(heating_systems_parameter, total_energy_need)
|
201
|
+
|
202
|
+
return energy_use_kwh_with_building_group
|
ebm/model/enums.py
ADDED
ebm/model/exceptions.py
ADDED