From 8d820e32c45cc657605d091e452c20922a3b82d0 Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Thu, 11 Apr 2024 12:15:12 +0200 Subject: [PATCH 01/31] VS code debugger config for Jean --- .gitignore | 3 +++ .vscode/launch.json | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index 4431552f..1b4081ab 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ __pycache__/ **/__pycache__ *.py[cod] /project/config/hidden_cost/ + +/venv/* +/.vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..796a0daf --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Python Debugger: Current File", + "type": "debugpy", + "request": "launch", + "console": "integratedTerminal", + "cwd": "${workspaceFolder}", + "module": "project.main", + "args": ["--config","project/config/config.json"] + } + ] +} \ No newline at end of file From eb15051e6064831b2d2c6bfe2f8d6a3ccc9b23e4 Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Thu, 11 Apr 2024 12:15:25 +0200 Subject: [PATCH 02/31] Correct bug --- project/utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/project/utils.py b/project/utils.py index 8f5da803..05b38a0e 100644 --- a/project/utils.py +++ b/project/utils.py @@ -1254,10 +1254,13 @@ def make_stacked_bar_subplot(df, format_y=lambda y, _: '{:.0f}€'.format(y), fo # group.sum(axis=1) # Adjust legend - if ncol is None: - ncol = len(labels) - fig.legend(handles, labels, loc='lower center', ncol=ncol, fontsize=fonttick, frameon=False, - bbox_to_anchor=(0.5, 0)) + try: + if ncol is None: + ncol = len(labels) + fig.legend(handles, labels, loc='lower center', ncol=ncol, fontsize=fonttick, frameon=False, + bbox_to_anchor=(0.5, 0)) + except UnboundLocalError: + pass plt.tight_layout() plt.subplots_adjust(bottom=bottom) # Adjust the bottom margin From 858633e6c096fbed1d0bbdd59532357af473d925 Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Thu, 11 Apr 2024 12:15:34 +0200 Subject: [PATCH 03/31] High-perf insulation and renovation flow for each possible pair of certificates --- project/building.py | 108 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/project/building.py b/project/building.py index a85d1f35..ea6a84dd 100644 --- a/project/building.py +++ b/project/building.py @@ -1064,6 +1064,11 @@ def __init__(self, stock, surface, ratio_surface, efficiency, income, preference method_health_cost = 'epc' self.method_health_cost = method_health_cost + self.sum_performance_insulation = None + self.sum_performance_insulation_obligation = None + self.flow_by_certificate_couples = None + self.flow_by_certificate_couples_obligation = None + @property def year(self): return self._year @@ -3955,12 +3960,97 @@ def store_information_insulation(self, condition, cost_insulation_raw, tax, cost 'hidden_cost': hidden_cost }) + def certificate_flow(self, stock, renovation_rate, market_share, certificate_before_heater, certificate_after, call_from_obligation=False): + """ Calculates the renovation flow for each possible pair of certificates, and the sum of high-performance renovations. + Take certificates into account before changing heating systems, but the flows are those of insulation. + + Parameters + ---------- + stock: Series + renovation_rate: Series + market_share: DataFrame + certificate_before_heater: Series + certificate_after: DataFrame + call_from_obligation : Boolean (Optional) + + + Returns + ------- + None + """ + + # In obligation_flow everyone targeted renovates + if call_from_obligation: + renovation_rate.values.fill(1) + + # Calculate the flows for each possible renovation choice + renovation_flow = stock * renovation_rate + + market_flow = market_share.copy() + for choice in market_share.columns: + market_flow[choice] = renovation_flow.values * market_share[choice].values + market_flow = market_flow.fillna(0) + + # Replace columns names with four rows by columns names Choices_0, Choices_1 etc. + market_flow_tmp = pd.DataFrame() + certificate_after_tmp = pd.DataFrame() + i = 0 + for col in market_flow.columns: + market_flow_tmp["Flow_Choice_{f}".format(f=i)] = market_flow[col] + certificate_after_tmp["Certif_after_Choice_{f}".format(f=i)] = certificate_after[col] + i += 1 + + # Merge in a df : the flows for each possible renovation choice, the certificates after for each possible renovation choice, and certificate_before + merged_df = market_flow_tmp.merge(certificate_after_tmp, left_index=True, right_index=True, how='inner') + certificate_before_heater = certificate_before_heater.rename('Certificate_before_heater') + merged_df = merged_df.merge(certificate_before_heater, left_index=True, right_index=True, how='inner') + + # Calculate the renovation flow for each possible couple of certificates + flow_by_certificate_couples = {} + + for i in range(len(market_flow.columns)): + category_after_col = f'Certif_after_Choice_{i}' + flow_choice_col = f'Flow_Choice_{i}' + + for category_before, category_after, flow_choice in zip(merged_df['Certificate_before_heater'], merged_df[category_after_col], merged_df[flow_choice_col]): + category_change = (category_before, category_after) + flow_by_certificate_couples[category_change] = flow_by_certificate_couples.get(category_change, 0) + flow_choice + + # Check that the sum of the flows for each possible pair of certificates equals the sum of renovation_flow. + sum_check = 0 + for key in flow_by_certificate_couples: + sum_check += flow_by_certificate_couples[key] + assert round(sum_check, 0) == round(sum(renovation_flow), 0), 'Flow between certificate pairs problem' + + # Calculate the number of high-performance renovations + condition_reno_performante = "(category_before in ['C', 'D', 'E', 'F', 'G'] and category_after in ['A', 'B']) or (category_before in ['F', 'G'] and category_after=='C' )" + sum_performance_insulation = 0 + + for category_change, sum_value in flow_by_certificate_couples.items(): + category_before, category_after = category_change + if eval(condition_reno_performante): + sum_performance_insulation += sum_value + + # Put results in a Series instead of a Dict + flow_by_certificate_couples = pd.Series(flow_by_certificate_couples) + flow_by_certificate_couples = flow_by_certificate_couples.sort_index(level=[0, 1]) + + # Put the results in the buildings object's attributes. + if not call_from_obligation: + self.flow_by_certificate_couples = flow_by_certificate_couples + self.sum_performance_insulation = sum_performance_insulation + else: + self.flow_by_certificate_couples_obligation = flow_by_certificate_couples + self.sum_performance_insulation_obligation = sum_performance_insulation + + return None + def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetime_insulation, policies_insulation=None, financing_cost=None, calib_renovation=None, min_performance=None, exogenous_social=None, prices_before=None, supply=None, carbon_value=None, carbon_content=None, calculate_condition=True, bill_rebate=0, - credit_constraint=True): + credit_constraint=True, call_from_obligation=False): """Calculate insulation retrofit in the dwelling stock. 1. Intensive margin @@ -4202,7 +4292,9 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim consumption_saved_actual, consumption_saved_no_rebound, amount_debt, amount_saving, discount, subsidies_loan, eligible, hidden_cost) - + + self.certificate_flow(stock, renovation_rate, market_share, certificate_before_heater, certificate_after, call_from_obligation) + return renovation_rate, market_share else: renovation_rate = Series(0, index=stock_ini.index) @@ -4535,7 +4627,7 @@ def flow_obligation(self, policies_insulation, prices, cost_insulation, financin policies_insulation=policies_insulation, financing_cost=financing_cost, min_performance=obligation.min_performance, - credit_constraint=False) + credit_constraint=False, call_from_obligation=True) if obligation.intensive == 'market_share': # market_share endogenously calculated by insulation_replacement @@ -5790,6 +5882,16 @@ def condition_decarbonizing(x): output['Cost-benefits analysis (Billion euro)'] = output['CBA benefits (Billion euro)'] + output['CBA cost (Billion euro)'] + if self.flow_by_certificate_couples is not None: + output['High-performance renovation (Thousand households)'] = self.sum_performance_insulation / 10 ** 3 + flow_by_certificate_couples = self.flow_by_certificate_couples / 10 ** 3 + output.update({'Renovation from {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples.loc[(i,j)] for (i,j) in flow_by_certificate_couples.index}) + + if self.flow_by_certificate_couples_obligation is not None: + output['Obligatory High-performance renovation (Thousand households)'] = self.sum_performance_insulation_obligation / 10 ** 3 + flow_by_certificate_couples_obligation = self.flow_by_certificate_couples_obligation / 10 ** 3 + output.update({'Obligatory renovation from {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples_obligation.loc[(i,j)] for (i,j) in flow_by_certificate_couples_obligation.index}) + output = Series(output).rename(self.year) stock = stock.rename(self.year) return stock, output From 5d64d7ede25bc44e6e3ab70d4a89ae07f83800e6 Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Fri, 12 Apr 2024 11:43:22 +0200 Subject: [PATCH 04/31] New outputs --- project/building.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/project/building.py b/project/building.py index ea6a84dd..54bbbd65 100644 --- a/project/building.py +++ b/project/building.py @@ -3979,7 +3979,7 @@ def certificate_flow(self, stock, renovation_rate, market_share, certificate_bef None """ - # In obligation_flow everyone targeted renovates + # In obligation_flow everyone in the replaced_by df renovates if call_from_obligation: renovation_rate.values.fill(1) @@ -4883,7 +4883,11 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, for key, item in heater.items(): output['Stock {} (Million)'.format(key)] = temp[[i for i in item if i in temp.index]].sum() / 10 ** 6 - temp.index = temp.index.map(lambda x: 'Stock {} (Million)'.format(x)) + temp.index = temp.index.map(lambda x: 'Stock {} (Million)'.format(x)) # A SUPPRIMER APRES AVOIR TESTE + output.update(temp.T / 10 ** 6) # A SUPPRIMER APRES AVOIR TESTE + + temp = self.stock.groupby(['Heating system', 'Housing type']).sum() + temp.index = ['Stock {} '.format(y) + '{} (Million)'.format(x) for (x,y) in temp.index] output.update(temp.T / 10 ** 6) # energy expenditures : do we really need it ? @@ -5803,6 +5807,13 @@ def condition_decarbonizing(x): temp.index = temp.index.map(lambda x: 'Subsidies total {} - {} (Million euro)'.format(x[0], x[1])) output.update(temp.T / 10 ** 6 / step) + temp = subsidies_total.groupby(['Income owner']).sum() + temp.index = temp.index.map(lambda x: 'Subsidies total {} (Million euro)'.format(x)) + output.update(temp.T / 10 ** 6 / step) + + temp = subsidies_total[subsidies_total.index.get_level_values('Occupancy status') == 'Social-housing'].sum() / subsidies_total.sum() + output['Share of subsidies going to Social-housing (Million euro)'] = temp.sum() + self.store_over_years[self.year].update( {'Annuities heater (Billion euro/year)': output['Annuities heater (Billion euro/year)'], 'Annuities insulation (Billion euro/year)': output['Annuities insulation (Billion euro/year)'], From ab5fd31c352be78f04f214f4b2c2252ead7d2935 Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Fri, 12 Apr 2024 11:45:50 +0200 Subject: [PATCH 05/31] New outputs --- project/building.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/building.py b/project/building.py index 54bbbd65..d5093ba0 100644 --- a/project/building.py +++ b/project/building.py @@ -4883,8 +4883,8 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, for key, item in heater.items(): output['Stock {} (Million)'.format(key)] = temp[[i for i in item if i in temp.index]].sum() / 10 ** 6 - temp.index = temp.index.map(lambda x: 'Stock {} (Million)'.format(x)) # A SUPPRIMER APRES AVOIR TESTE - output.update(temp.T / 10 ** 6) # A SUPPRIMER APRES AVOIR TESTE + temp.index = temp.index.map(lambda x: 'Stock {} (Million)'.format(x)) + output.update(temp.T / 10 ** 6) temp = self.stock.groupby(['Heating system', 'Housing type']).sum() temp.index = ['Stock {} '.format(y) + '{} (Million)'.format(x) for (x,y) in temp.index] From a5a51acc3ca73adff9c06b9a96a49a9311011f52 Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Mon, 22 Apr 2024 16:33:58 +0200 Subject: [PATCH 06/31] Flow between certificate pairs for heating replacement only --- project/building.py | 67 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/project/building.py b/project/building.py index d5093ba0..516a00fd 100644 --- a/project/building.py +++ b/project/building.py @@ -1066,8 +1066,10 @@ def __init__(self, stock, surface, ratio_surface, efficiency, income, preference self.sum_performance_insulation = None self.sum_performance_insulation_obligation = None - self.flow_by_certificate_couples = None + self.flow_by_certificate_couples_insulation = None self.flow_by_certificate_couples_obligation = None + self.flow_by_certificate_couples_heater = None + self.sum_performance_changes_heater = None @property def year(self): @@ -3960,7 +3962,7 @@ def store_information_insulation(self, condition, cost_insulation_raw, tax, cost 'hidden_cost': hidden_cost }) - def certificate_flow(self, stock, renovation_rate, market_share, certificate_before_heater, certificate_after, call_from_obligation=False): + def certificate_flow_insulation(self, stock, renovation_rate, market_share, certificate_before_heater, certificate_after, call_from_obligation=False): """ Calculates the renovation flow for each possible pair of certificates, and the sum of high-performance renovations. Take certificates into account before changing heating systems, but the flows are those of insulation. @@ -4037,7 +4039,7 @@ def certificate_flow(self, stock, renovation_rate, market_share, certificate_bef # Put the results in the buildings object's attributes. if not call_from_obligation: - self.flow_by_certificate_couples = flow_by_certificate_couples + self.flow_by_certificate_couples_insulation = flow_by_certificate_couples self.sum_performance_insulation = sum_performance_insulation else: self.flow_by_certificate_couples_obligation = flow_by_certificate_couples @@ -4293,8 +4295,7 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim amount_debt, amount_saving, discount, subsidies_loan, eligible, hidden_cost) - self.certificate_flow(stock, renovation_rate, market_share, certificate_before_heater, certificate_after, call_from_obligation) - + self.certificate_flow_insulation(stock, renovation_rate, market_share, certificate_before_heater, certificate_after, call_from_obligation) return renovation_rate, market_share else: renovation_rate = Series(0, index=stock_ini.index) @@ -4303,6 +4304,52 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim return renovation_rate, market_share + def certificate_flow_heater(self): + """ Calculates the flow for each possible pair of certificates for those who change their heater but do not renovate. + + Parameters + ---------- + + + Returns + ------- + None + """ + + # Creation of 3 Series : flow of heater replacement, certificates before and after heater replacement + flow = self._only_heater + index = flow.index + _, _, certificate_before_heater = self.consumption_heating_store(index, level_heater='Heating system') + _, _, certificate_after_heater = self.consumption_heating_store(index, level_heater='Heating system final') + + # Merging flow of heater replacement only with certificates before and after heater replacement + flow = flow.rename('Flow') + certificate_before_heater = certificate_before_heater.rename("certificate_before_heater") + certificate_after_heater = certificate_after_heater.rename("certificate_after_heater") + + merged_df = pd.DataFrame(flow).merge(certificate_before_heater, left_index=True, right_index=True, how='inner') + merged_df = merged_df.merge(certificate_after_heater, left_index=True, right_index=True, how='inner') + + # Flow grouped by certificates couples + flow_by_certificate_couples = merged_df.set_index(['certificate_before_heater', 'certificate_after_heater']) + flow_by_certificate_couples = flow_by_certificate_couples.groupby(['certificate_before_heater', 'certificate_after_heater']).sum() + flow_by_certificate_couples = pd.Series(flow_by_certificate_couples['Flow'], index=flow_by_certificate_couples.index) + + # Calculate the number of high-performance changes + condition_reno_performante = "(category_before in ['C', 'D', 'E', 'F', 'G'] and category_after in ['A', 'B']) or (category_before in ['F', 'G'] and category_after=='C' )" + sum_performance_changes = 0 + dict_series = dict(zip(flow_by_certificate_couples.index.map(lambda x: (x[0], x[1])), flow_by_certificate_couples)) + + for category_change, sum_value in dict_series.items(): + category_before, category_after = category_change + if eval(condition_reno_performante): + sum_performance_changes += sum_value + + self.flow_by_certificate_couples_heater = flow_by_certificate_couples + self.sum_performance_changes_heater = sum_performance_changes + + return None + def flow_retrofit(self, prices, cost_heater, cost_insulation, lifetime_insulation, policies_heater=None, policies_insulation=None, calib_heater=None, district_heating=None, financing_cost=None, calib_renovation=None, @@ -4491,6 +4538,7 @@ def supply_demand_insulation_equilibrium(_prices_insulation): self.logger.debug('Store information retrofit') self._replaced_by = replaced_by.copy() self._only_heater = only_heater.copy() + self.certificate_flow_heater() # removing heater replacement level replaced_by = replaced_by.groupby( @@ -5893,9 +5941,9 @@ def condition_decarbonizing(x): output['Cost-benefits analysis (Billion euro)'] = output['CBA benefits (Billion euro)'] + output['CBA cost (Billion euro)'] - if self.flow_by_certificate_couples is not None: + if self.flow_by_certificate_couples_insulation is not None: output['High-performance renovation (Thousand households)'] = self.sum_performance_insulation / 10 ** 3 - flow_by_certificate_couples = self.flow_by_certificate_couples / 10 ** 3 + flow_by_certificate_couples = self.flow_by_certificate_couples_insulation / 10 ** 3 output.update({'Renovation from {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples.loc[(i,j)] for (i,j) in flow_by_certificate_couples.index}) if self.flow_by_certificate_couples_obligation is not None: @@ -5903,6 +5951,11 @@ def condition_decarbonizing(x): flow_by_certificate_couples_obligation = self.flow_by_certificate_couples_obligation / 10 ** 3 output.update({'Obligatory renovation from {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples_obligation.loc[(i,j)] for (i,j) in flow_by_certificate_couples_obligation.index}) + if self.flow_by_certificate_couples_heater is not None: + output['High-performance flow for heater replacement only (Thousand households)'] = self.sum_performance_changes_heater / 10 ** 3 + flow_by_certificate_couples = self.flow_by_certificate_couples_heater / 10 ** 3 + output.update({'Heater replacement only - {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples.loc[(i,j)] for (i,j) in flow_by_certificate_couples.index}) + output = Series(output).rename(self.year) stock = stock.rename(self.year) return stock, output From a1d61ddf5678c532e140070f454cab438d53dc6f Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Wed, 24 Apr 2024 15:07:19 +0200 Subject: [PATCH 07/31] Total of high-perf renovations --- project/building.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/project/building.py b/project/building.py index 516a00fd..fb69bef0 100644 --- a/project/building.py +++ b/project/building.py @@ -5941,21 +5941,30 @@ def condition_decarbonizing(x): output['Cost-benefits analysis (Billion euro)'] = output['CBA benefits (Billion euro)'] + output['CBA cost (Billion euro)'] + tmp1, tmp2, tmp3 = 0, 0, 0 + if self.flow_by_certificate_couples_insulation is not None: - output['High-performance renovation (Thousand households)'] = self.sum_performance_insulation / 10 ** 3 + tmp1 = self.sum_performance_insulation / 10 ** 3 + output['High-performance renovation (Thousand households)'] = tmp1 flow_by_certificate_couples = self.flow_by_certificate_couples_insulation / 10 ** 3 output.update({'Renovation from {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples.loc[(i,j)] for (i,j) in flow_by_certificate_couples.index}) if self.flow_by_certificate_couples_obligation is not None: - output['Obligatory High-performance renovation (Thousand households)'] = self.sum_performance_insulation_obligation / 10 ** 3 + tmp2 = self.sum_performance_insulation_obligation / 10 ** 3 + output['Obligatory High-performance renovation (Thousand households)'] = tmp2 flow_by_certificate_couples_obligation = self.flow_by_certificate_couples_obligation / 10 ** 3 output.update({'Obligatory renovation from {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples_obligation.loc[(i,j)] for (i,j) in flow_by_certificate_couples_obligation.index}) if self.flow_by_certificate_couples_heater is not None: - output['High-performance flow for heater replacement only (Thousand households)'] = self.sum_performance_changes_heater / 10 ** 3 + tmp3 = self.sum_performance_changes_heater / 10 ** 3 + output['High-performance flow for heater replacement only (Thousand households)'] = tmp3 flow_by_certificate_couples = self.flow_by_certificate_couples_heater / 10 ** 3 output.update({'Heater replacement only - {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples.loc[(i,j)] for (i,j) in flow_by_certificate_couples.index}) + temp = tmp1 + tmp2 + tmp3 + if temp > 0: + output['Total High-performance renovation (Thousand households)'] = temp + output = Series(output).rename(self.year) stock = stock.rename(self.year) return stock, output From 42a6b06c884807c1b740c6018a11db4e9c610e68 Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Mon, 17 Jun 2024 09:22:23 +0200 Subject: [PATCH 08/31] =?UTF-8?q?MPR=20multi=20:=20prendre=20en=20compte?= =?UTF-8?q?=20gains=20d'EE=20du=20chgt=20de=20chaudi=C3=A8re?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project/building.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/building.py b/project/building.py index fb69bef0..18089705 100644 --- a/project/building.py +++ b/project/building.py @@ -4097,7 +4097,7 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim if not stock.empty: # select index that can undertake insulation replacement - _, _, certificate_before_heater = self.consumption_heating_store(index, level_heater='Heating system') + _, consumption_3uses_before_heater, certificate_before_heater = self.consumption_heating_store(index, level_heater='Heating system') # before include the change of heating system consumption_before, consumption_3uses_before, certificate_before = self.consumption_heating_store(index, level_heater='Heating system final') @@ -4107,7 +4107,7 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim consumption_after, consumption_3uses, certificate_after = self.prepare_consumption(self._choice_insulation, index=index, level_heater='Heating system final') - energy_saved_3uses = ((consumption_3uses_before - consumption_3uses.T) / consumption_3uses_before).T + energy_saved_3uses = ((consumption_3uses_before_heater - consumption_3uses.T) / consumption_3uses_before_heater).T energy_saved_3uses.dropna(inplace=True) consumption_saved = (consumption_before - consumption_after.T).T From d724feb45184e2127fbc2e91415e77a990ad2e3b Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Fri, 5 Jul 2024 14:06:55 +0200 Subject: [PATCH 09/31] =?UTF-8?q?New=20outputs=20:=20MPR=20perf=20et=20MPR?= =?UTF-8?q?=20multi=20par=20d=C3=A9ciles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project/building.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/project/building.py b/project/building.py index 18089705..bd44b1c2 100644 --- a/project/building.py +++ b/project/building.py @@ -5774,6 +5774,20 @@ def condition_decarbonizing(x): energy_saved = ((energy_saved * self._replaced_by.fillna(0)).T * eligible).T energy_saved_renovation[key] = energy_saved.sum().sum() / replacement_eligible.sum() + # subsidies - details: policies amount and number of beneficiaries, by decile + if key in ['mpr_performance', 'mpr_multifamily_updated', 'mpr_multifamily_deep']: + amount_tmp = subsidies_details_renovation[key] + subsidies_details_by_decile = amount_tmp.groupby(['Income owner']).sum() + amount_by_decile = subsidies_details_by_decile.T.sum() + amount_by_decile = amount_by_decile / 10 ** 6 / step + output.update({'{} '.format(key.capitalize().replace('_', ' ')) + ' {} (Million euro)'.format(i): amount_by_decile.loc[i] for i in amount_by_decile.index}) + + eligible = self._renovation_store['eligible'][key] + count_tmp = self._replaced_by.fillna(0).sum(axis=1) * eligible + count_by_decile = count_tmp.groupby(['Income owner']).sum() + count_by_decile = count_by_decile / 1e3 / step + output.update({'{} '.format(key.capitalize().replace('_', ' ')) + ' {} (Thousand households)'.format(i): count_by_decile.loc[i] for i in count_by_decile.index}) + del self._renovation_store['subsidies_details_households'] gc.collect() From 969ba494cf6b4a241053c873aa669f5ccff785c1 Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Fri, 5 Jul 2024 16:39:12 +0200 Subject: [PATCH 10/31] =?UTF-8?q?Config=20mpr=5Fmultifamily=5Fupdated=20co?= =?UTF-8?q?rrig=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project/input/policies/policies_2024.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/project/input/policies/policies_2024.json b/project/input/policies/policies_2024.json index 7c2d8c3c..025a9f6a 100644 --- a/project/input/policies/policies_2024.json +++ b/project/input/policies/policies_2024.json @@ -125,7 +125,8 @@ "mpr_efficacite", "mpr_serenite", "mpr_performance", - "mpr_multifamily" + "mpr_multifamily", + "mpr_multifamily_updated" ], "target": "energy_condition_50", "policy": "subsidy_ad_valorem" @@ -163,6 +164,7 @@ "mpr_performance", "mpr_multifamily", "mpr_multifamily_deep", + "mpr_multifamily_updated", "mpr", "mpr_efficacite", "cite", From a220c578719c0e6746199399308b5c2760c66b55 Mon Sep 17 00:00:00 2001 From: JeanWolff10 Date: Fri, 5 Jul 2024 16:41:36 +0200 Subject: [PATCH 11/31] =?UTF-8?q?New=20outputs=20:=20MPR=20perf=20et=20MPR?= =?UTF-8?q?=20multi=20par=20d=C3=A9ciles=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project/building.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/building.py b/project/building.py index bd44b1c2..1daba86c 100644 --- a/project/building.py +++ b/project/building.py @@ -5775,7 +5775,7 @@ def condition_decarbonizing(x): energy_saved_renovation[key] = energy_saved.sum().sum() / replacement_eligible.sum() # subsidies - details: policies amount and number of beneficiaries, by decile - if key in ['mpr_performance', 'mpr_multifamily_updated', 'mpr_multifamily_deep']: + if key in ['mpr_performance', 'mpr_multifamily', 'mpr_multifamily_updated', 'mpr_multifamily_deep']: amount_tmp = subsidies_details_renovation[key] subsidies_details_by_decile = amount_tmp.groupby(['Income owner']).sum() amount_by_decile = subsidies_details_by_decile.T.sum() From b1d71a4bd471b198ce2666ed2eb84a083fbeebdd Mon Sep 17 00:00:00 2001 From: remydoudard Date: Fri, 13 Sep 2024 09:30:44 +0200 Subject: [PATCH 12/31] add_output_subsidies_by_income_class --- project/building.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/project/building.py b/project/building.py index 1daba86c..9ad81a64 100644 --- a/project/building.py +++ b/project/building.py @@ -1101,8 +1101,10 @@ def year(self, year): 'subsidies_count': {}, 'subsidies_average': {}, 'cost_average': {}, - 'replacement_eligible': {} - } + 'replacement_eligible': {}, + 'replacement_eligible_income': {} + + } for k, item in ini.items(): self._heater_store[k] = item @@ -2140,6 +2142,9 @@ def store_information_heater(self, cost_heater, subsidies_total, bill_saved, sub self._heater_store['replacement_eligible'].update( {key: replacement_eligible.groupby('Housing type').sum()}) + self._heater_store['replacement_eligible_income'].update( + {key: replacement_eligible.groupby('Income owner').sum()}) + if eligible.sum().sum() > 0: self._heater_store['subsidies_average'].update({key: sub.sum().sum() / replacement_eligible.sum()}) self._heater_store['cost_average'].update({key: cost.sum().sum() / replacement_eligible.sum()}) @@ -5745,7 +5750,7 @@ def condition_decarbonizing(x): self._balance_state_ini = output['Balance state (Billion euro)'] # subsidies - details: policies amount and number of beneficiaries - subsidies_details_renovation, replacement_eligible_renovation, subsidies_average_renovation, cost_average_renovation = {}, {}, {}, {} + subsidies_details_renovation, replacement_eligible_renovation, replacement_eligible_renovation_income, subsidies_average_renovation, cost_average_renovation = {}, {}, {}, {}, {} energy_saved_renovation = {} for key, sub in self._renovation_store['subsidies_details_households'].items(): subsidies_details_renovation[key] = ( @@ -5760,6 +5765,7 @@ def condition_decarbonizing(x): replacement_eligible = self._replaced_by.fillna(0).sum(axis=1) * eligible replacement_eligible_renovation[key] = replacement_eligible.groupby('Housing type').sum() + replacement_eligible_renovation_income[key] = replacement_eligible.groupby('Income owner').sum() if eligible.sum().sum() == 0: subsidies_average_renovation[key] = 0 @@ -5791,15 +5797,17 @@ def condition_decarbonizing(x): del self._renovation_store['subsidies_details_households'] gc.collect() - subsidies, replacement_eligible, sub_count, cost_average, energy_average = None, None, None, None, None + subsidies, replacement_eligible, sub_count, sub_count_income, cost_average, energy_average = None, None, None, None, None, None for gest, subsidies_details in {'heater': self._heater_store['subsidies_details'], 'insulation': subsidies_details_renovation}.items(): if gest == 'heater': sub_count = DataFrame(self._heater_store['replacement_eligible'], dtype=float) + sub_count_income = DataFrame(self._heater_store['replacement_eligible_income'], dtype=float) cost_average = Series(self._heater_store['cost_average'], dtype=float) elif gest == 'insulation': sub_count = DataFrame(replacement_eligible_renovation, dtype=float) + sub_count_income = DataFrame(replacement_eligible_renovation_income, dtype=float) cost_average = Series(cost_average_renovation, dtype=float) energy_average = Series(energy_saved_renovation, dtype=float) @@ -5811,14 +5819,27 @@ def condition_decarbonizing(x): use_subsidies = inputs['use_subsidies'].loc['{} {}'.format(i, gest)] subsidies_details[i] *= use_subsidies sub_count[i] *= use_subsidies + sub_count_income[i] *= use_subsidies temp = sub_count[i].copy() + temp_income = sub_count_income[i].copy() + temp.index = temp.index.map( lambda x: '{} {} {} (Thousand households)'.format(i.capitalize().replace('_', ' '), gest, x)) output.update(temp.T / 10 ** 3 / step) + temp_income.index = temp_income.index.map( + lambda x: '{} {} {} (Thousand households)'.format(i.capitalize().replace('_', ' '), gest, x)) + output.update(temp_income.T / 10 ** 3 / step) + output.update({'{} {} (Thousand households)'.format(i.capitalize().replace('_', ' '), gest): sub_count[i].sum() / 1e3 / step}) + + output['Average cost {} {} (euro)'.format(i.capitalize().replace('_', ' '), gest)] = cost_average.loc[i] + + output.update({'{} {} (Thousand households)'.format(i.capitalize().replace('_', ' '), gest): + sub_count_income[i].sum() / 1e3 / step}) + output['Average cost {} {} (euro)'.format(i.capitalize().replace('_', ' '), gest)] = cost_average.loc[i] if gest == 'insulation' and i in energy_average.keys(): From 0a58ca57b649aeb596c360442a0594df8dc16d16 Mon Sep 17 00:00:00 2001 From: remydoudard Date: Fri, 28 Mar 2025 17:02:17 +0100 Subject: [PATCH 13/31] update_snbc3_run3_dgec --- .gitignore | 4 + project/building.py | 232 +++++++++++++++++- project/config/config.json | 9 +- project/config/reference.json | 18 +- .../input/energy/energy_prices_wt_ame2024.csv | 35 +++ project/input/macro/consumption_ini_agg.csv | 6 + project/input/macro/energy_taxes_ame2021.csv | 34 +++ project/input/macro/energy_taxes_ams_run3.csv | 34 +++ project/input/macro/energy_vta.csv | 5 + project/input/macro/energy_vta_ams_run3.csv | 5 + .../input/policies/current/mpr_insulation.csv | 8 +- project/input/policies/policies_2024.json | 1 + project/model.py | 20 +- 13 files changed, 382 insertions(+), 29 deletions(-) create mode 100644 project/input/energy/energy_prices_wt_ame2024.csv create mode 100644 project/input/macro/consumption_ini_agg.csv create mode 100644 project/input/macro/energy_taxes_ame2021.csv create mode 100644 project/input/macro/energy_taxes_ams_run3.csv create mode 100644 project/input/macro/energy_vta.csv create mode 100644 project/input/macro/energy_vta_ams_run3.csv diff --git a/.gitignore b/.gitignore index 1b4081ab..7bba0adb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ /project/config/validation/ /project/test /project/input/stock/buildingstock_sdes2018_medium_3.csv +/project/input/stock/20241205_buildingstock_sdes2023_medium_3.csv +/project/input/stock/20241205_buildingstock_sdes2023_update.csv /project/analysis/ /project/input/macro/_old/ /project/input/policies/_old/ @@ -26,6 +28,8 @@ /project/input/revealed_data/_old/ /project/config/coupling/old/ /project/input/investment/_old/ +/project/input/sfec/ +/project/config/sfec/ # Byte-compiled / optimized / DLL files diff --git a/project/building.py b/project/building.py index 9ad81a64..46e662bd 100644 --- a/project/building.py +++ b/project/building.py @@ -1068,6 +1068,12 @@ def __init__(self, stock, surface, ratio_surface, efficiency, income, preference self.sum_performance_insulation_obligation = None self.flow_by_certificate_couples_insulation = None self.flow_by_certificate_couples_obligation = None + self.flow_by_certificate_couples_ampleur_insulation = None + self.flow_by_certificate_couples_ampleur_obligation = None + self.flow_by_operation_insulation = None + self.l5 = None + self.l5_obligation = None + self.merged_df_heater = None self.flow_by_certificate_couples_heater = None self.sum_performance_changes_heater = None @@ -1923,6 +1929,15 @@ def calibration(x, _ms, _utility_ini, _flow, _idx, _ref, _u_shock, _target, utility_subsidies = subsidies_total * self.preferences_heater['subsidy'] / 1000 cost_heater = cost_heater.reindex(index).reindex(choice_heater, axis=1) + + #Florian test: on augmente la TVA sur le prix des chaudière gaz + if self.year>2024: + # print('Cout chaudiere') + # print(cost_heater['Natural gas-Performance boiler']) + # cost_heater['Natural gas-Collective boiler']*=1.2/1.055 + cost_heater['Natural gas-Performance boiler']*=1.2/1.055 + # print(cost_heater['Natural gas-Performance boiler']) + pref_investment = reindex_mi(self.preferences_heater['cost'], index) utility_cost = (pref_investment * cost_heater.T).T / 1000 @@ -3967,7 +3982,7 @@ def store_information_insulation(self, condition, cost_insulation_raw, tax, cost 'hidden_cost': hidden_cost }) - def certificate_flow_insulation(self, stock, renovation_rate, market_share, certificate_before_heater, certificate_after, call_from_obligation=False): + def certificate_flow_insulation(self, stock, renovation_rate, market_share, certificate_before_heater, certificate_before, certificate_after, call_from_obligation=False): """ Calculates the renovation flow for each possible pair of certificates, and the sum of high-performance renovations. Take certificates into account before changing heating systems, but the flows are those of insulation. @@ -3985,6 +4000,38 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert ------- None """ + + mapping_operations = {"False False False True" : "Wi", + "False False True False" : "R", + "False False True True": "RWi", + "False True False False": "F", + "False True False True": "FWi", + "False True True False": "FR", + "False True True True": "FRWi", + "True False False False": "Wa", + "True False False True": "WaWi", + "True False True False": "WR", + "True False True True": "WaRWi", + "True True False False": "WaF", + "True True False True": "WaFWi", + "True True True False": "WaFR", + "True True True True": "WaFRWi"} + + mapping_operations_2 = {0 : "Wi", + 1 : "R", + 2 : "RWi", + 3 : "F", + 4 : "FWi", + 5 : "FR", + 6 : "FRWi", + 7 : "Wa", + 8 : "WaWi", + 9 : "WR", + 10 : "WaRWi", + 11: "WaF", + 12: "WaFWi", + 13: "WaFR", + 14: "WaFRWi"} # In obligation_flow everyone in the replaced_by df renovates if call_from_obligation: @@ -4000,9 +4047,13 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert # Replace columns names with four rows by columns names Choices_0, Choices_1 etc. market_flow_tmp = pd.DataFrame() + flow_by_operation = pd.Series() certificate_after_tmp = pd.DataFrame() i = 0 for col in market_flow.columns: + res = ' '.join(str(val) for val in col) + flow_by_operation[res] = market_flow[col].sum() + flow_by_operation.rename(mapping_operations, inplace=True) market_flow_tmp["Flow_Choice_{f}".format(f=i)] = market_flow[col] certificate_after_tmp["Certif_after_Choice_{f}".format(f=i)] = certificate_after[col] i += 1 @@ -4010,19 +4061,46 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert # Merge in a df : the flows for each possible renovation choice, the certificates after for each possible renovation choice, and certificate_before merged_df = market_flow_tmp.merge(certificate_after_tmp, left_index=True, right_index=True, how='inner') certificate_before_heater = certificate_before_heater.rename('Certificate_before_heater') + certificate_before = certificate_before.rename('Certificate_before') merged_df = merged_df.merge(certificate_before_heater, left_index=True, right_index=True, how='inner') + merged_df = merged_df.merge(certificate_before, left_index=True, right_index=True, how='inner') # Calculate the renovation flow for each possible couple of certificates flow_by_certificate_couples = {} + flow_by_certificate_couples_ampleur = {} + certificate_diffs=[] + certificate_diffs_results = pd.DataFrame() for i in range(len(market_flow.columns)): category_after_col = f'Certif_after_Choice_{i}' flow_choice_col = f'Flow_Choice_{i}' + flow_choice_diff = f'Flow_Choice_diff{i}' + + merged_df_2 = merged_df.reset_index(level=['Heater replacement']) + merged_df_3 = merged_df_2.reset_index(level=['Housing type']) + merged_df_4 = merged_df_3.reset_index(level=['Occupancy status']) + # merged_df_4 = merged_df_3.reset_index(level=['Heating system']) + # merged_df_5 = merged_df_4.reset_index(level=['Heating system final']) + # merged_df_6 = merged_df_5.reset_index(level=['Occupancy status']) + # merged_df_7 = merged_df_6.reset_index(level=['Income owner']) + merged_df_4[flow_choice_diff] = merged_df_4['Occupancy status'] + "_" + merged_df_4['Housing type'] + "_" + merged_df_4['Heater replacement'].astype(str) + "_" + merged_df_4['Certificate_before_heater'] + "_" + merged_df_4['Certificate_before'] + "_" + merged_df_4[category_after_col] + certificate_diffs.append(merged_df_4.groupby(flow_choice_diff)[flow_choice_col].sum()) + for category_before, category_after, flow_choice in zip(merged_df['Certificate_before_heater'], merged_df[category_after_col], merged_df[flow_choice_col]): category_change = (category_before, category_after) flow_by_certificate_couples[category_change] = flow_by_certificate_couples.get(category_change, 0) + flow_choice + certificate_diffs_results = pd.concat(certificate_diffs, axis=1) + + certificate_diffs_results = certificate_diffs_results.reset_index() + l = pd.wide_to_long(certificate_diffs_results, stubnames='Flow_Choice_', i= 'index', j='operation') + l2 = l.reset_index(level=['operation']) + l2['operation_map'] = l2['operation'].map(mapping_operations_2) + l3 = l2.reset_index() + l3['operation_details'] = l3['index'] + "_" + l3['operation_map'] + l4 = l3.drop(['operation', 'index','operation_map'], axis=1) + l5 = l4.set_index(['operation_details']) # Check that the sum of the flows for each possible pair of certificates equals the sum of renovation_flow. sum_check = 0 for key in flow_by_certificate_couples: @@ -4034,7 +4112,7 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert sum_performance_insulation = 0 for category_change, sum_value in flow_by_certificate_couples.items(): - category_before, category_after = category_change + category_before, category_after = category_change if eval(condition_reno_performante): sum_performance_insulation += sum_value @@ -4049,6 +4127,49 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert else: self.flow_by_certificate_couples_obligation = flow_by_certificate_couples self.sum_performance_insulation_obligation = sum_performance_insulation + + for i in [2,4,5,6,8,9,10,11,12,13,14]: + category_after_col = f'Certif_after_Choice_{i}' + flow_choice_col = f'Flow_Choice_{i}' + + for category_before, category_after, flow_choice in zip(merged_df['Certificate_before_heater'], merged_df[category_after_col], merged_df[flow_choice_col]): + category_change = (category_before, category_after) + flow_by_certificate_couples_ampleur[category_change] = flow_by_certificate_couples_ampleur.get(category_change, 0) + flow_choice + + # Check that the sum of the flows for each possible pair of certificates equals the sum of renovation_flow. + sum_check = 0 + for key in flow_by_certificate_couples_ampleur: + sum_check += flow_by_certificate_couples_ampleur[key] + # assert round(sum_check, 0) == round(sum(renovation_flow), 0), 'Flow between certificate pairs problem' + + # Calculate the number of high-performance renovations + condition_reno_ampleur = "(category_diff >= 2)" + sum_performance_insulation_ampleur = 0 + + category_mapping= {"A" : 7, "B" : 6, "C" : 5, "D" : 4, "E" : 3, "F" : 2, "G" : 1} + + for category_change, sum_value in flow_by_certificate_couples_ampleur.items(): + category_before, category_after = category_change + category_before_number = category_mapping[category_before] + category_after_number = category_mapping[category_after] + category_diff = category_after_number - category_before_number + if eval(condition_reno_ampleur): + sum_performance_insulation_ampleur += sum_value + + # Put results in a Series instead of a Dict + flow_by_certificate_couples_ampleur = pd.Series(flow_by_certificate_couples_ampleur) + flow_by_certificate_couples_ampleur = flow_by_certificate_couples_ampleur.sort_index(level=[0, 1]) + + # Put the results in the buildings object's attributes. + if not call_from_obligation: + self.flow_by_certificate_couples_ampleur_insulation = flow_by_certificate_couples_ampleur + self.sum_performance_insulation_ampleur = sum_performance_insulation_ampleur + self.flow_by_operation_insulation = flow_by_operation + self.l5 = l5 + else: + self.flow_by_certificate_couples_ampleur_obligation = flow_by_certificate_couples_ampleur + self.sum_performance_insulation_ampleur_obligation = sum_performance_insulation_ampleur + self.l5_obligation = l5 return None @@ -4300,7 +4421,7 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim amount_debt, amount_saving, discount, subsidies_loan, eligible, hidden_cost) - self.certificate_flow_insulation(stock, renovation_rate, market_share, certificate_before_heater, certificate_after, call_from_obligation) + self.certificate_flow_insulation(stock, renovation_rate, market_share, certificate_before_heater, certificate_before, certificate_after, call_from_obligation) return renovation_rate, market_share else: renovation_rate = Series(0, index=stock_ini.index) @@ -4335,6 +4456,8 @@ def certificate_flow_heater(self): merged_df = pd.DataFrame(flow).merge(certificate_before_heater, left_index=True, right_index=True, how='inner') merged_df = merged_df.merge(certificate_after_heater, left_index=True, right_index=True, how='inner') + merged_df_heater = merged_df + # Flow grouped by certificates couples flow_by_certificate_couples = merged_df.set_index(['certificate_before_heater', 'certificate_after_heater']) flow_by_certificate_couples = flow_by_certificate_couples.groupby(['certificate_before_heater', 'certificate_after_heater']).sum() @@ -4352,6 +4475,7 @@ def certificate_flow_heater(self): self.flow_by_certificate_couples_heater = flow_by_certificate_couples self.sum_performance_changes_heater = sum_performance_changes + self.merged_df_heater = merged_df_heater return None @@ -4748,6 +4872,9 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, stock = self.simplified_stock() output = dict() + df_renovations = pd.DataFrame(columns=["Occupancy status", "Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation","steps_heater","steps_insulation","steps_insulation_with_heater", "Operation type","Year","Value"]) + df_renovations_obligation = pd.DataFrame(columns=["Occupancy status", "Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation","steps_heater","steps_insulation","steps_insulation_with_heater", "Operation type","Year","Value"]) + merged_df_heater = pd.DataFrame(columns=["Occupancy status","Housing type","Heating system final","Heating system","Flow","certificate_before_heater","certificate_after_heater","steps_heater","Year"]) output['Stock (Million)'] = stock.sum() / 10 ** 6 output['Stock existing (Million)'] = self.stock.xs(True, level='Existing').sum() / 10 ** 6 stock_new = 0 @@ -5781,7 +5908,8 @@ def condition_decarbonizing(x): energy_saved_renovation[key] = energy_saved.sum().sum() / replacement_eligible.sum() # subsidies - details: policies amount and number of beneficiaries, by decile - if key in ['mpr_performance', 'mpr_multifamily', 'mpr_multifamily_updated', 'mpr_multifamily_deep']: +# if key in ['mpr_performance', 'mpr_multifamily', 'mpr_multifamily_updated', 'mpr_multifamily_deep']: + if key in ['mpr_performance']: amount_tmp = subsidies_details_renovation[key] subsidies_details_by_decile = amount_tmp.groupby(['Income owner']).sum() amount_by_decile = subsidies_details_by_decile.T.sum() @@ -5997,12 +6125,106 @@ def condition_decarbonizing(x): output.update({'Heater replacement only - {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples.loc[(i,j)] for (i,j) in flow_by_certificate_couples.index}) temp = tmp1 + tmp2 + tmp3 + if temp > 0: output['Total High-performance renovation (Thousand households)'] = temp + + tmp1, tmp2= 0, 0 + + if self.flow_by_certificate_couples_ampleur_insulation is not None: + tmp1 = self.sum_performance_insulation_ampleur / 10 ** 3 + output['Renovation ampleur (Thousand households)'] = tmp1 + flow_by_certificate_couples_ampleur = self.flow_by_certificate_couples_ampleur_insulation / 10 ** 3 + output.update({'Renovation >= 2 operations from {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples_ampleur.loc[(i,j)] for (i,j) in flow_by_certificate_couples_ampleur.index}) + + if self.flow_by_certificate_couples_ampleur_obligation is not None: + tmp2 = self.sum_performance_insulation_ampleur_obligation / 10 ** 3 + output['Obligatory Renovation ampleur (Thousand households)'] = tmp2 + flow_by_certificate_couples_ampleur_obligation = self.flow_by_certificate_couples_ampleur_obligation / 10 ** 3 + output.update({'Obligatory renovation ampleur from {} to '.format(i) + '{} (Thousand households)'.format(j): flow_by_certificate_couples_ampleur_obligation.loc[(i,j)] for (i,j) in flow_by_certificate_couples_ampleur_obligation.index}) + + temp = tmp1 + tmp2 + + if temp > 0: + output['Total Renovation ampleur (Thousand households)'] = temp + + flow_by_operation = self.flow_by_operation_insulation / 10**3 + l5 = self.l5.fillna(0) + if self.flow_by_certificate_couples_obligation is not None: + l5_obligation = self.l5_obligation.fillna(0) + df_renovations_obligation_3 = pd.DataFrame(columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Operation type","Year","Value"]) + for i in l5_obligation.squeeze().index : + + i_columns = i.split('_') + i_columns.append(self.year) + value = l5_obligation.squeeze().loc[(i)] + i_columns.append(value) + + df_renovations_obligation_2 = pd.DataFrame([i_columns], columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Operation type","Year","Value"]) + + df_renovations_obligation_3 = pd.concat([df_renovations_obligation_3, df_renovations_obligation_2], ignore_index=True) + + df_renovations_obligation = pd.concat([df_renovations_obligation, df_renovations_obligation_3], ignore_index=True) + +# output.update({'Renovation {} (Thousand households)'.format(i): flow_by_operation.loc[(i)] for (i) in flow_by_operation.index}) + +# output.update({'Renovation {} (Thousand households)'.format(i): l5.squeeze().loc[(i)] for (i) in l5.squeeze().index}) + + if self.merged_df_heater is not None: + merged_df_heater = self.merged_df_heater + merged_df_heater = merged_df_heater.reset_index() + merged_df_heater.drop(['Wall', 'Floor', 'Roof','Windows','Existing','Income owner'], axis=1, inplace=True) + merged_df_heater = merged_df_heater.groupby(['Occupancy status','Housing type',"Heating system final","Heating system","certificate_before_heater","certificate_after_heater"]).sum() + merged_df_heater.reset_index(inplace=True) + merged_df_heater["epc before heater"] = merged_df_heater["certificate_before_heater"].replace(EPC2INT) + merged_df_heater["epc after heater"] = merged_df_heater["certificate_after_heater"].replace(EPC2INT) + merged_df_heater["steps_heater"] = - (merged_df_heater["epc after heater"] - merged_df_heater["epc before heater"]) + merged_df_heater["Year"] = self.year + merged_df_heater.drop(["epc before heater", "epc after heater"], axis=1, inplace=True) + + + else : + merged_df_heater = pd.DataFrame(columns=["Occupancy status","Housing type","Heating system final","Heating system","Flow","certificate_before_heater","certificate_after_heater","steps_heater","Year"]) + + df_renovations_3 = pd.DataFrame(columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Operation type","Year","Value"]) + for i in l5.squeeze().index : + + i_columns = i.split('_') + i_columns.append(self.year) + value = l5.squeeze().loc[(i)] + i_columns.append(value) + + df_renovations_2 = pd.DataFrame([i_columns], columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Operation type","Year","Value"]) + + df_renovations_3 = pd.concat([df_renovations_3, df_renovations_2], ignore_index=True) + + df_renovations = pd.concat([df_renovations, df_renovations_3], ignore_index=True) + + df_renovations["epc before heater"] = df_renovations["Category before heater"].replace(EPC2INT) + df_renovations["epc before insulation"] = df_renovations["Category before insulation"].replace(EPC2INT) + df_renovations["epc after insulation"] = df_renovations["Category after insulation"].replace(EPC2INT) + df_renovations["steps_heater"] = - (df_renovations["epc before insulation"] - df_renovations["epc before heater"]) + df_renovations["steps_insulation"] = - (df_renovations["epc after insulation"] - df_renovations["epc before insulation"]) + df_renovations["steps_insulation_with_heater"] = - (df_renovations["epc after insulation"] - df_renovations["epc before heater"]) + df_renovations.drop(['epc before heater', 'epc before insulation', 'epc after insulation'], axis=1, inplace=True) + df_renovations["Obligation"] = "No" + + if self.flow_by_certificate_couples_obligation is not None: + df_renovations_obligation["epc before heater"] = df_renovations_obligation["Category before heater"].replace(EPC2INT) + df_renovations_obligation["epc before insulation"] = df_renovations_obligation["Category before insulation"].replace(EPC2INT) + df_renovations_obligation["epc after insulation"] = df_renovations_obligation["Category after insulation"].replace(EPC2INT) + df_renovations_obligation["steps_heater"] = - (df_renovations_obligation["epc before insulation"] - df_renovations_obligation["epc before heater"]) + df_renovations_obligation["steps_insulation"] = - (df_renovations_obligation["epc after insulation"] - df_renovations_obligation["epc before insulation"]) + df_renovations_obligation["steps_insulation_with_heater"] = - (df_renovations_obligation["epc after insulation"] - df_renovations_obligation["epc before heater"]) + df_renovations_obligation.drop(['epc before heater', 'epc before insulation', 'epc after insulation'], axis=1, inplace=True) + df_renovations_obligation["Obligation"] = "Yes" + + df_renovations_total = pd.concat([df_renovations, df_renovations_obligation], ignore_index=True) output = Series(output).rename(self.year) stock = stock.rename(self.year) - return stock, output + + return stock, output, df_renovations_total, merged_df_heater def parse_output_run_cba(self, prices, inputs, step=1, taxes=None, bill_rebate=0): output = dict() diff --git a/project/config/config.json b/project/config/config.json index c960ae5e..b52c6a06 100644 --- a/project/config/config.json +++ b/project/config/config.json @@ -11,7 +11,7 @@ "add_policies": null, "remove_policies": null, "policies_scenarios": null, - "no_natural_replacement": null + "no_natural_replacement": false }, "sensitivity": { "activated": false @@ -32,7 +32,7 @@ "Reference": { "file": "project/config/reference.json", "step": 1, - "end": 2020, + "end": 2025, "policies": { "file": "project/input/policies/policies_2024.json", "restriction_gas": { @@ -44,7 +44,8 @@ } }, "simple": { - "no_policy_heater": false + "no_policy_heater": false, + "quintiles": false } } -} \ No newline at end of file +} diff --git a/project/config/reference.json b/project/config/reference.json index 5979d26c..f522978a 100644 --- a/project/config/reference.json +++ b/project/config/reference.json @@ -2,7 +2,7 @@ "end": 2051, "step": 1, "output": "full", - "start": 2017, + "start": 2022, "building_stock": "project/input/stock/buildingstock_sdes2018_medium_3.csv", "financing_cost": { "activated": true, @@ -103,7 +103,7 @@ "income": "project/input/macro/income.csv", "income_rate": 0.008, "rotation_rate": "project/input/macro/rotation_rate.csv", - "consumption_ini": "project/input/macro/consumption_ini.csv", + "consumption_ini": "project/input/macro/consumption_ini_agg.csv", "present_discount_rate": "project/input/investment/present_discount_rate.csv", "population": null, "surface_built": null, @@ -118,7 +118,7 @@ "health_cost_income": "project/input/policies/health_cost_income.csv", "carbon_value": "project/input/policies/carbon_value.csv", "policies": { - "file": "project/input/policies/policies_2021.json" + "file": "project/policies/policies_2024.json" }, "simple": { "figures": true, @@ -134,12 +134,12 @@ "income_constant": false, "prices_constant": false, "heating_system": { - "Oil fuel-Standard boiler": "Oil fuel-Performance boiler", - "Natural gas-Standard boiler": "Natural gas-Performance boiler", - "Wood fuel-Standard boiler": "Wood fuel-Performance boiler", - "Natural gas-Collective boiler": "Natural gas-Performance boiler", - "Oil fuel-Collective boiler": "Oil fuel-Performance boiler", - "Electricity-Heat pump air": "Electricity-Heat pump water" + "Oil fuel-Standard boiler": "Oil fuel-Performance boiler", + "Natural gas-Standard boiler": "Natural gas-Performance boiler", + "Wood fuel-Standard boiler": "Wood fuel-Performance boiler", + "Natural gas-Collective boiler": "Natural gas-Performance boiler", + "Oil fuel-Collective boiler": "Oil fuel-Performance boiler", + "Electricity-Heat pump air": "Electricity-Heat pump water" }, "insulation": false, "no_heating_switch": false, diff --git a/project/input/energy/energy_prices_wt_ame2024.csv b/project/input/energy/energy_prices_wt_ame2024.csv new file mode 100644 index 00000000..bb99093b --- /dev/null +++ b/project/input/energy/energy_prices_wt_ame2024.csv @@ -0,0 +1,35 @@ +,Oil fuel,Natural gas,Electricity,Wood fuel,Heating +2017,0.0619,0.05937,0.1151,0.056,0.0746 +2018,0.0619,0.05937,0.1151,0.056,0.0746 +2019,0.0619,0.05937,0.1151,0.056,0.0746 +2020, 0.0452,0.048668936,0.115147572,0.0565708,0.0735 +2021,0.0487,0.056204489,0.116998057,0.057266139,0.074403424 +2022,0.0523,0.063740042,0.118848543,0.057961477,0.075306847 +2023,0.0559,0.071275596,0.120699029,0.058656816,0.076210271 +2024,0.0595,0.078811149,0.122549514,0.059352155,0.077113694 +2025,0.0631,0.086346702,0.1244,0.060047493,0.078017118 +2026,0.0642,0.085784187,0.125518041,0.060785566,0.078976063 +2027,0.0653,0.085221672,0.126636083,0.061523638,0.079935009 +2028,0.0664,0.084659157,0.127754124,0.06226171,0.080893954 +2029,0.0675,0.084096642,0.128872166,0.062999783,0.0818529 +2030,0.0687,0.083534127,0.129990207,0.063737855,0.082811846 +2031,0.0698,0.082642417,0.129990207,0.064521288,0.083829726 +2032,0.0709,0.081750708,0.129990207,0.06530472,0.084847605 +2033,0.0720,0.080858998,0.129990207,0.066088152,0.085865485 +2034,0.0731,0.079967288,0.129990207,0.066871585,0.086883365 +2035,0.0742,0.079075578,0.129990207,0.067655017,0.087901245 +2036,0.0745,0.081413004,0.129990207,0.068486597,0.088981681 +2037,0.0748,0.083750431,0.129990207,0.069318177,0.090062117 +2038,0.0751,0.086087858,0.129990207,0.070149757,0.091142553 +2039,0.0754,0.088425284,0.129990207,0.070981337,0.09222299 +2040,0.0757,0.090762711,0.129990207,0.071812917,0.093303426 +2041,0.0768,0.090594567,0.129990207,0.072695604,0.094450263 +2042,0.0778,0.090426423,0.129990207,0.073578291,0.095597099 +2043,0.0789,0.090258279,0.129990207,0.074460978,0.096743936 +2044,0.0799,0.090090134,0.129990207,0.075343665,0.097890773 +2045,0.0810,0.08992199,0.129990207,0.076226351,0.09903761 +2046,0.0828,0.089637982,0.129990207,0.077163286,0.100254929 +2047,0.0847,0.089353973,0.129990207,0.07810022,0.101472247 +2048,0.0865,0.089069964,0.129990207,0.079037155,0.102689566 +2049,0.0884,0.088785956,0.129990207,0.079974089,0.103906884 +2050,0.0903,0.088501947,0.129990207,0.080911024,0.105124203 \ No newline at end of file diff --git a/project/input/macro/consumption_ini_agg.csv b/project/input/macro/consumption_ini_agg.csv new file mode 100644 index 00000000..23194767 --- /dev/null +++ b/project/input/macro/consumption_ini_agg.csv @@ -0,0 +1,6 @@ +Heating energy, +Electricity,50 +Natural gas,91 +Oil fuel,31 +Wood fuel,77 +Heating,12 diff --git a/project/input/macro/energy_taxes_ame2021.csv b/project/input/macro/energy_taxes_ame2021.csv new file mode 100644 index 00000000..f58490c4 --- /dev/null +++ b/project/input/macro/energy_taxes_ame2021.csv @@ -0,0 +1,34 @@ +,Oil fuel,Natural gas,Electricity,Wood fuel,Heating +2018,0.0156,0.0111,0.0358,0,0 +2019,0.0156,0.0111,0.0358,0,0 +2020,0.0156,0.0111,0.0358,0,0 +2021,0.0156,0.0111,0.0358,0,0 +2022,0.0156,0.0111,0.0358,0,0 +2023,0.0156,0.0111,0.0358,0,0 +2024,0.0156,0.0111,0.0358,0,0 +2025,0.0156,0.0111,0.0358,0,0 +2026,0.0156,0.0111,0.0358,0,0 +2027,0.0156,0.0111,0.0358,0,0 +2028,0.0156,0.0111,0.0358,0,0 +2029,0.0156,0.0111,0.0358,0,0 +2030,0.0156,0.0111,0.0358,0,0 +2031,0.0156,0.0111,0.0358,0,0 +2032,0.0156,0.0111,0.0358,0,0 +2033,0.0156,0.0111,0.0358,0,0 +2034,0.0156,0.0111,0.0358,0,0 +2035,0.0156,0.0111,0.0358,0,0 +2036,0.0156,0.0111,0.0358,0,0 +2037,0.0156,0.0111,0.0358,0,0 +2038,0.0156,0.0111,0.0358,0,0 +2039,0.0156,0.0111,0.0358,0,0 +2040,0.0156,0.0111,0.0358,0,0 +2041,0.0156,0.0111,0.0358,0,0 +2042,0.0156,0.0111,0.0358,0,0 +2043,0.0156,0.0111,0.0358,0,0 +2044,0.0156,0.0111,0.0358,0,0 +2045,0.0156,0.0111,0.0358,0,0 +2046,0.0156,0.0111,0.0358,0,0 +2047,0.0156,0.0111,0.0358,0,0 +2048,0.0156,0.0111,0.0358,0,0 +2049,0.0156,0.0111,0.0358,0,0 +2050,0.0156,0.0111,0.0358,0,0 \ No newline at end of file diff --git a/project/input/macro/energy_taxes_ams_run3.csv b/project/input/macro/energy_taxes_ams_run3.csv new file mode 100644 index 00000000..235677fe --- /dev/null +++ b/project/input/macro/energy_taxes_ams_run3.csv @@ -0,0 +1,34 @@ +,Oil fuel,Natural gas,Electricity,Wood fuel,Heating +2018,0.0156,0.011,0.038,0,0 +2019,0.011,0.014,017,0,0 +2020,0.009,0.014,0.018,0,0 +2021,0.011,0.013,0.019,0,0 +2022,0.017,0.017,0.20,0 +2023,0.015,0.020,0.023,0,0 +2024,0.016,0.019,0.026,0,0 +2025,0.016,0.019,0.037,0,0 +2026,0.016,0.019,0.037,0,0 +2027,0.016,0.019,0.037,0,0 +2028,0.016,0.019,0.037,0,0 +2029,0.016,0.019,0.037,0,0 +2030,0.016,0.019,0.037,0,0 +2031,0.016,0.019,0.037,0,0 +2032,0.016,0.019,0.037,0,0 +2033,0.016,0.019,0.037,0,0 +2034,0.016,0.019,0.037,0,0 +2035,0.016,0.019,0.037,0,0 +2036,0.016,0.019,0.037,0,0 +2037,0.016,0.019,0.038,0,0 +2038,0.016,0.019,0.038,0,0 +2039,0.016,0.019,0.038,0,0 +2040,0.016,0.019,0.038,0,0 +2041,0.016,0.019,0.038,0,0 +2042,0.016,0.019,0.038,0,0 +2043,0.016,0.019,0.038,0,0 +2044,0.016,0.019,0.038,0,0 +2045,0.016,0.019,0.038,0,0 +2046,0.016,0.019,0.038,0,0 +2047,0.016,0.019,0.038,0,0 +2048,0.016,0.019,0.038,0,0 +2049,0.016,0.019,0.038,0,0 +2050,0.016,0.019,0.038,0,0 \ No newline at end of file diff --git a/project/input/macro/energy_vta.csv b/project/input/macro/energy_vta.csv new file mode 100644 index 00000000..7c314463 --- /dev/null +++ b/project/input/macro/energy_vta.csv @@ -0,0 +1,5 @@ +Oil fuel,0.2 +Natural gas,0.15 +Electricity,0.15 +Wood fuel,0.1 +Heating,0 \ No newline at end of file diff --git a/project/input/macro/energy_vta_ams_run3.csv b/project/input/macro/energy_vta_ams_run3.csv new file mode 100644 index 00000000..7c314463 --- /dev/null +++ b/project/input/macro/energy_vta_ams_run3.csv @@ -0,0 +1,5 @@ +Oil fuel,0.2 +Natural gas,0.15 +Electricity,0.15 +Wood fuel,0.1 +Heating,0 \ No newline at end of file diff --git a/project/input/policies/current/mpr_insulation.csv b/project/input/policies/current/mpr_insulation.csv index 22cd0e5b..e82c3dce 100644 --- a/project/input/policies/current/mpr_insulation.csv +++ b/project/input/policies/current/mpr_insulation.csv @@ -5,7 +5,7 @@ Single-family,D3,60,20,0,80 Single-family,D4,60,20,0,80 Single-family,D5,40,15,0,40 Single-family,D6,40,15,0,40 -Single-family,D7,40,15,0,40 -Single-family,D8,15,7,0,0 -Single-family,D9,15,7,0,0 -Single-family,D10,15,7,0,0 \ No newline at end of file +Single-family,D7,0,0,0,0 +Single-family,D8,0,0,0,0 +Single-family,D9,0,0,0,0 +Single-family,D10,0,7,0,0 \ No newline at end of file diff --git a/project/input/policies/policies_2024.json b/project/input/policies/policies_2024.json index 025a9f6a..edd3f118 100644 --- a/project/input/policies/policies_2024.json +++ b/project/input/policies/policies_2024.json @@ -140,6 +140,7 @@ "mpr_serenite", "mpr_performance", "mpr_multifamily", + "mpr_multifamily_updated", "mpr_multifamily_deep", "mpr", "mpr_efficacite", diff --git a/project/model.py b/project/model.py index 6951cb79..2623e3ef 100644 --- a/project/model.py +++ b/project/model.py @@ -473,7 +473,7 @@ def stock_turnover(buildings, prices, taxes, cost_heater, cost_insulation, lifet buildings.logger.info('Writing output') if output_options == 'full': buildings.logger.debug('Full output') - stock, output = buildings.parse_output_run(prices, post_inputs, climate=climate, step=step, taxes=taxes, + stock, output, df_renovations, merged_df_heater = buildings.parse_output_run(prices, post_inputs, climate=climate, step=step, taxes=taxes, bill_rebate=bill_rebate) elif output_options == 'cost_benefit': buildings.logger.debug('Cost-benefit output') @@ -519,7 +519,7 @@ def stock_turnover(buildings, prices, taxes, cost_heater, cost_insulation, lifet heating = buildings.stock.groupby('Heating system').sum() temp = pd.concat((heating, buildings.heater_vintage.sum(axis=1)), axis=1) - return buildings, stock, output + return buildings, stock, output, df_renovations, merged_df_heater def res_irf(config, path, level_logger='DEBUG'): @@ -582,7 +582,7 @@ def res_irf(config, path, level_logger='DEBUG'): inputs_dynamics['health_cost_income'], inputs_dynamics['health_cost_dpe']) - s, o = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs'], + s, o, df_renovations, merged_df_heater = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs'], taxes=taxes) stock = pd.concat((stock, s), axis=1) output = pd.concat((output, o), axis=1) @@ -607,6 +607,8 @@ def res_irf(config, path, level_logger='DEBUG'): if p.variable: p.end = config['start'] + 2 + df_renovations_final = pd.DataFrame(columns=["Occupancy status", "Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation","steps_heater","steps_insulation","steps_insulation_with_heater", "Operation type","Year","Value","Obligation"]) + merged_df_heater_final = pd.DataFrame(columns=["Occupancy status","Housing type","Heating system final","Heating system","Flow","certificate_before_heater","certificate_after_heater","steps_heater","Year"]) for k, year in enumerate(years): start = time() @@ -647,7 +649,7 @@ def res_irf(config, path, level_logger='DEBUG'): heat_pump = [i for i in resources_data['index']['Heat pumps'] if i in inputs_dynamics['cost_heater'].index] inputs_dynamics['cost_heater'].loc[heat_pump] *= (1 + technical_progress['heater'].loc[year])**step - buildings, s, o = stock_turnover(buildings, prices, taxes, + buildings, s, o, df_renovations, merged_df_heater = stock_turnover(buildings, prices, taxes, inputs_dynamics['cost_heater'], inputs_dynamics['cost_insulation'], inputs_dynamics['lifetime_insulation'], @@ -667,7 +669,9 @@ def res_irf(config, path, level_logger='DEBUG'): carbon_content=carbon_content, carbon_content_before=carbon_content_before, step=step) - + + df_renovations_final = pd.concat([df_renovations_final, df_renovations], ignore_index=True) + merged_df_heater_final = pd.concat([merged_df_heater_final, merged_df_heater], ignore_index=True) stock = pd.concat((stock, s), axis=1) stock.index.names = s.index.names output = pd.concat((output, o), axis=1) @@ -698,6 +702,8 @@ def res_irf(config, path, level_logger='DEBUG'): pd.DataFrame(buildings.memory).to_csv(os.path.join(path, 'memory.csv')) output.round(3).to_csv(os.path.join(path, 'output.csv')) + df_renovations_final.round(3).to_csv(os.path.join(path, 'df_renovations_final.csv')) + merged_df_heater_final.round(3).to_csv(os.path.join(path, 'df_heaters_final.csv')) buildings.logger.info('Dumping output in {}'.format(os.path.join(path, 'output.csv'))) if config['output'] == 'full': @@ -744,7 +750,7 @@ def calibration_res_irf(path, config=None, level_logger='DEBUG'): ) output = pd.DataFrame() - _, o = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs']) + _, o, df_renovations, merged_df_heater = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs']) output = pd.concat((output, o), axis=1) year = buildings.first_year + 1 @@ -757,7 +763,7 @@ def calibration_res_irf(path, config=None, level_logger='DEBUG'): flow_district_heating = inputs_dynamics['flow_district_heating'].loc[year] carbon_content = inputs_dynamics['post_inputs']['carbon_emission'].loc[year, :] - buildings, s, o = stock_turnover(buildings, prices, taxes, + buildings, s, o, df_renovations, merged_df_heater = stock_turnover(buildings, prices, taxes, inputs_dynamics['cost_heater'], inputs_dynamics['cost_insulation'], inputs_dynamics['lifetime_insulation'], p_heater, p_insulation, f_built, year, inputs_dynamics['post_inputs'], From 3512cf4c27b202dfb1e646f3937ffe2a34e2bb17 Mon Sep 17 00:00:00 2001 From: remydoudard Date: Mon, 9 Jun 2025 22:21:14 +0200 Subject: [PATCH 14/31] adding_consumption_standard_real Adding consumption standard and real by : epc, heating system, energy, year --- project/building.py | 194 +++++++++++++++++++++++++++++++++++++++++--- project/model.py | 15 ++-- 2 files changed, 192 insertions(+), 17 deletions(-) diff --git a/project/building.py b/project/building.py index 46e662bd..31b8da74 100644 --- a/project/building.py +++ b/project/building.py @@ -4001,6 +4001,22 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert None """ + # mapping_operations = {"False False False True" : "Wi", + # "False False True False" : "other", + # "False False True True": "other", + # "False True False False": "other", + # "False True False True": "other", + # "False True True False": "other", + # "False True True True": "other", + # "True False False False": "other", + # "True False False True": "other", + # "True False True False": "other", + # "True False True True": "other", + # "True True False False": "other", + # "True True False True": "other", + # "True True True False": "other", + # "True True True True": "other"} + mapping_operations = {"False False False True" : "Wi", "False False True False" : "R", "False False True True": "RWi", @@ -4017,6 +4033,38 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert "True True True False": "WaFR", "True True True True": "WaFRWi"} + # mapping_operations_2 = {0 : "Wi", + # 1 : "R", + # 2 : "RWi", + # 3 : "F", + # 4 : "FWi", + # 5 : "FR", + # 6 : "FRWi", + # 7 : "Wa", + # 8 : "WaWi", + # 9 : "WR", + # 10 : "WaRWi", + # 11: "WaF", + # 12: "WaFWi", + # 13: "WaFR", + # 14: "WaFRWi"} + + # mapping_operations_2 = {0 : "Wi", + # 1 : "other", + # 2 : "other", + # 3 : "other", + # 4 : "other", + # 5 : "other", + # 6 : "other", + # 7 : "other", + # 8 : "other", + # 9 : "other", + # 10 : "other", + # 11: "other", + # 12: "other", + # 13: "other", + # 14: "other"} + mapping_operations_2 = {0 : "Wi", 1 : "R", 2 : "RWi", @@ -4033,6 +4081,7 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert 13: "WaFR", 14: "WaFRWi"} + # In obligation_flow everyone in the replaced_by df renovates if call_from_obligation: renovation_rate.values.fill(1) @@ -4079,13 +4128,14 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert merged_df_2 = merged_df.reset_index(level=['Heater replacement']) merged_df_3 = merged_df_2.reset_index(level=['Housing type']) merged_df_4 = merged_df_3.reset_index(level=['Occupancy status']) + merged_df_5 = merged_df_4.reset_index(level=['Income owner']) # merged_df_4 = merged_df_3.reset_index(level=['Heating system']) # merged_df_5 = merged_df_4.reset_index(level=['Heating system final']) # merged_df_6 = merged_df_5.reset_index(level=['Occupancy status']) - # merged_df_7 = merged_df_6.reset_index(level=['Income owner']) - merged_df_4[flow_choice_diff] = merged_df_4['Occupancy status'] + "_" + merged_df_4['Housing type'] + "_" + merged_df_4['Heater replacement'].astype(str) + "_" + merged_df_4['Certificate_before_heater'] + "_" + merged_df_4['Certificate_before'] + "_" + merged_df_4[category_after_col] - certificate_diffs.append(merged_df_4.groupby(flow_choice_diff)[flow_choice_col].sum()) + + merged_df_5[flow_choice_diff] = merged_df_5['Occupancy status'] + "_" + merged_df_5['Housing type'] + "_" + merged_df_5['Heater replacement'].astype(str) + "_" + merged_df_5['Certificate_before_heater'] + "_" + merged_df_5['Certificate_before'] + "_" + merged_df_5[category_after_col] + "_" + merged_df_5['Income owner'] + certificate_diffs.append(merged_df_5.groupby(flow_choice_diff)[flow_choice_col].sum()) for category_before, category_after, flow_choice in zip(merged_df['Certificate_before_heater'], merged_df[category_after_col], merged_df[flow_choice_col]): category_change = (category_before, category_after) @@ -5041,10 +5091,99 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, temp.index = temp.index.map(lambda x: 'Stock {} (Million)'.format(x)) output.update(temp.T / 10 ** 6) + + + consumption_std = self.consumption_heating(freq="year", climate=None) + consumption_std_2 = reindex_mi(consumption_std, self.stock.index) * self.surface * self.stock + consumption_std_3 = consumption_std_2.reset_index() + consumption_std_3.rename(columns={0: "consumption_standard"}) + + + consumption_real = self.consumption_heating(freq="year", climate=climate, temp_sink=self._temp_sink) + consumption_real_2 = reindex_mi(consumption_real, self.stock.index) * self.surface + consumption_real_3 = self.consumption_actual(prices, consumption=consumption_real_2, bill_rebate=bill_rebate) * self.stock + consumption_real_4 = consumption_real_3.reset_index() + consumption_real_4.rename(columns={0: "consumption_real"}, inplace=True) + + stock_output = self.stock.reset_index() + + certificate_output = self.certificate.reset_index() + certificate_output.rename(columns={0: "epc"}, inplace=True) + + # 1. Fusionner les DataFrames sur les colonnes communes (par exemple, tous les niveaux descriptifs) + df = stock_output.merge( + certificate_output[["epc"]], left_index=True, right_index=True, how="left" + ) + df = df.merge( + consumption_std_3.rename(columns={0: "consumption_standard"}), + left_index=True, right_index=True, how="left" + ) + df = df.merge( + consumption_real_4.rename(columns={0: "consumption_real"}), + left_index=True, right_index=True, how="left" + ) + + if 0 in df.columns: + df.rename(columns={0: "Stock buildings"}, inplace=True) + + # 2. Garder uniquement les colonnes d'intérêt + df_final = df[["epc", "Heating system", "Stock buildings", "consumption_standard", "consumption_real"]] + + # 3. Supprimer les lignes sans valeurs de consommation si besoin + df_final = df_final.dropna(subset=["consumption_standard", "consumption_real"]) + + df_final["consumption_real"] *= self.coefficient_global + + coefficient_heater_2 = coefficient_heater.reset_index() + coefficient_heater_2 = coefficient_heater_2[["Heating system",0]] + coefficient_heater_2 = coefficient_heater_2.groupby("Heating system").mean() + + df_final = df_final.merge( + coefficient_heater_2.reset_index(), + left_on="Heating system", + right_on="Heating system", + how="left") + + df_final.rename(columns={0: "coefficient_heater"}, inplace = True) + + df_final = df_final[["epc", "Heating system", "Stock buildings", "consumption_standard", "consumption_real", "coefficient_heater"]] + output['Stock efficient (Million)'] = output.get('Stock A (Million)', 0) + output.get('Stock B (Million)', 0) output['Stock low-efficient (Million)'] = output.get('Stock G (Million)', 0) + output.get('Stock F (Million)', 0) output['Stock to renovate (Million)'] = output['Stock low-efficient (Million)'] + output.get('Stock E (Million)', 0) + \ output.get('Stock D (Million)', 0) + + mapping_system_energy = {"Electricity-Heat pump water" : "Electricity", + "Heating-District heating" : "District heating", + "Natural gas-Performance boiler" : "Gas" , + "Electricity-Performance boiler" : "Electricity", + "Wood fuel-Performance boiler": "Wood", + "Electricity-Heat pump": "Electricity", + "Oil fuel-Performance boiler": "Oil", + "Oil fuel-Standard boiler": "Oil", + "Oil fuel-Collective boiler": "Oil", + "Natural gas-Standard boiler": "Gas", + "Natural gas-Collective boiler": "Gas", + "Wood fuel-Standard boiler": "Wood", + "Electricity-Heat pump air": "Electricity"} + + # Ajout de la colonne "Energy" à df_final en utilisant mapping_system_energy + df_final["Energy"] = df_final["Heating system"].map(mapping_system_energy) + + df_final.drop('coefficient_heater', axis=1, inplace=True) + + df_final["year"] = self.year + + df_final_grouped = df_final.groupby(["epc", "Heating system", "Energy", "year"]).sum(numeric_only=True) + + df_final_grouped = df_final_grouped.loc[(df_final_grouped!=0).any(axis=1)] + + df_final_grouped["consumption_standard"] = df_final_grouped["consumption_standard"] / 10 ** 9 + df_final_grouped["consumption_real"] = df_final_grouped["consumption_real"] / 10 ** 9 + + df_final_grouped.rename(columns={"consumption_standard": "Consumption standard (TWh)","consumption_real": "Consumption real (TWh)" }, inplace = True) + + df_final_grouped = df_final_grouped.reset_index() temp = self.stock.groupby('Heating system').sum() @@ -6152,7 +6291,7 @@ def condition_decarbonizing(x): l5 = self.l5.fillna(0) if self.flow_by_certificate_couples_obligation is not None: l5_obligation = self.l5_obligation.fillna(0) - df_renovations_obligation_3 = pd.DataFrame(columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Operation type","Year","Value"]) + df_renovations_obligation_3 = pd.DataFrame(columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Income", "Operation type","Year","Value"]) for i in l5_obligation.squeeze().index : i_columns = i.split('_') @@ -6160,7 +6299,7 @@ def condition_decarbonizing(x): value = l5_obligation.squeeze().loc[(i)] i_columns.append(value) - df_renovations_obligation_2 = pd.DataFrame([i_columns], columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Operation type","Year","Value"]) + df_renovations_obligation_2 = pd.DataFrame([i_columns], columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation","Income", "Operation type","Year","Value"]) df_renovations_obligation_3 = pd.concat([df_renovations_obligation_3, df_renovations_obligation_2], ignore_index=True) @@ -6186,15 +6325,46 @@ def condition_decarbonizing(x): else : merged_df_heater = pd.DataFrame(columns=["Occupancy status","Housing type","Heating system final","Heating system","Flow","certificate_before_heater","certificate_after_heater","steps_heater","Year"]) - df_renovations_3 = pd.DataFrame(columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Operation type","Year","Value"]) - for i in l5.squeeze().index : + df_renovations_3 = pd.DataFrame(columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Income", "Operation type","Year","Value"]) + + mapping_final = {"Wi" : "Wi", + "R" : "other", + "RWi" : "other" , + "F" : "other", + "FWi": "other", + "FR": "other", + "FRWi": "other", + "Wa": "other", + "WaWi": "other", + "WR": "other", + "WaRWi": "other", + "WaF": "other", + "WaFWi": "other", + "WaFR": "other", + "WaFRWi": "other"} + + l5_bis = l5.reset_index() + l5_bis["operation_details_2"] = l5_bis["operation_details"] + l5_bis[['1','2','3','4','5','6','7','8']] = l5_bis['operation_details_2'].str.split('_',expand=True) + + l5_bis['category'] = l5_bis['8'].map(mapping_final) + + l6_bis = l5_bis.drop(['operation_details','operation_details_2', '8'], axis=1) + l7_bis = l6_bis.groupby(['1','2','3','4','5','6','7','category']).agg( + Flow_Choice_=('Flow_Choice_', 'sum')) + l7_bis = l7_bis.reset_index() + l7_bis["operation_details"] = l7_bis['1'] + "_" + l7_bis['2'] + "_" + l7_bis['3'] + "_" + l7_bis['4'] + "_" + l7_bis['5'] + "_" + l7_bis['6'] + "_" + l7_bis['7'] + "_" + l7_bis['category'] + l8_bis = l7_bis.set_index('operation_details') + l8_bis = l8_bis.drop(['1','2','3','4','5','6','7','category'], axis=1) + + for i,val in enumerate(l8_bis.squeeze().index) : - i_columns = i.split('_') + i_columns = val.split('_') i_columns.append(self.year) - value = l5.squeeze().loc[(i)] + value = l8_bis.squeeze().loc[(val)] i_columns.append(value) - df_renovations_2 = pd.DataFrame([i_columns], columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Operation type","Year","Value"]) + df_renovations_2 = pd.DataFrame([i_columns], columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Income", "Operation type","Year","Value"]) df_renovations_3 = pd.concat([df_renovations_3, df_renovations_2], ignore_index=True) @@ -6221,10 +6391,12 @@ def condition_decarbonizing(x): df_renovations_total = pd.concat([df_renovations, df_renovations_obligation], ignore_index=True) + + output = Series(output).rename(self.year) stock = stock.rename(self.year) - return stock, output, df_renovations_total, merged_df_heater + return stock, output, df_renovations_total, merged_df_heater, df_final_grouped def parse_output_run_cba(self, prices, inputs, step=1, taxes=None, bill_rebate=0): output = dict() diff --git a/project/model.py b/project/model.py index 2623e3ef..e8eb5bb7 100644 --- a/project/model.py +++ b/project/model.py @@ -473,7 +473,7 @@ def stock_turnover(buildings, prices, taxes, cost_heater, cost_insulation, lifet buildings.logger.info('Writing output') if output_options == 'full': buildings.logger.debug('Full output') - stock, output, df_renovations, merged_df_heater = buildings.parse_output_run(prices, post_inputs, climate=climate, step=step, taxes=taxes, + stock, output, df_renovations, merged_df_heater, df_final_grouped = buildings.parse_output_run(prices, post_inputs, climate=climate, step=step, taxes=taxes, bill_rebate=bill_rebate) elif output_options == 'cost_benefit': buildings.logger.debug('Cost-benefit output') @@ -519,7 +519,7 @@ def stock_turnover(buildings, prices, taxes, cost_heater, cost_insulation, lifet heating = buildings.stock.groupby('Heating system').sum() temp = pd.concat((heating, buildings.heater_vintage.sum(axis=1)), axis=1) - return buildings, stock, output, df_renovations, merged_df_heater + return buildings, stock, output, df_renovations, merged_df_heater, df_final_grouped def res_irf(config, path, level_logger='DEBUG'): @@ -582,7 +582,7 @@ def res_irf(config, path, level_logger='DEBUG'): inputs_dynamics['health_cost_income'], inputs_dynamics['health_cost_dpe']) - s, o, df_renovations, merged_df_heater = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs'], + s, o, df_renovations, merged_df_heater, df_final_grouped = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs'], taxes=taxes) stock = pd.concat((stock, s), axis=1) output = pd.concat((output, o), axis=1) @@ -607,8 +607,9 @@ def res_irf(config, path, level_logger='DEBUG'): if p.variable: p.end = config['start'] + 2 - df_renovations_final = pd.DataFrame(columns=["Occupancy status", "Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation","steps_heater","steps_insulation","steps_insulation_with_heater", "Operation type","Year","Value","Obligation"]) + df_renovations_final = pd.DataFrame(columns=["Occupancy status", "Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Income", "steps_heater","steps_insulation","steps_insulation_with_heater", "Operation type","Year","Value","Obligation"]) merged_df_heater_final = pd.DataFrame(columns=["Occupancy status","Housing type","Heating system final","Heating system","Flow","certificate_before_heater","certificate_after_heater","steps_heater","Year"]) + df_final_grouped_final = pd.DataFrame(columns=["epc", "Heating system", "Energy", "year", "Stock buildings", "Consumption standard (TWh)", "Consumption real (TWh)"]) for k, year in enumerate(years): start = time() @@ -649,7 +650,7 @@ def res_irf(config, path, level_logger='DEBUG'): heat_pump = [i for i in resources_data['index']['Heat pumps'] if i in inputs_dynamics['cost_heater'].index] inputs_dynamics['cost_heater'].loc[heat_pump] *= (1 + technical_progress['heater'].loc[year])**step - buildings, s, o, df_renovations, merged_df_heater = stock_turnover(buildings, prices, taxes, + buildings, s, o, df_renovations, merged_df_heater, df_final_grouped = stock_turnover(buildings, prices, taxes, inputs_dynamics['cost_heater'], inputs_dynamics['cost_insulation'], inputs_dynamics['lifetime_insulation'], @@ -672,6 +673,7 @@ def res_irf(config, path, level_logger='DEBUG'): df_renovations_final = pd.concat([df_renovations_final, df_renovations], ignore_index=True) merged_df_heater_final = pd.concat([merged_df_heater_final, merged_df_heater], ignore_index=True) + df_final_grouped_final = pd.concat([df_final_grouped_final, df_final_grouped], ignore_index=True) stock = pd.concat((stock, s), axis=1) stock.index.names = s.index.names output = pd.concat((output, o), axis=1) @@ -704,6 +706,7 @@ def res_irf(config, path, level_logger='DEBUG'): output.round(3).to_csv(os.path.join(path, 'output.csv')) df_renovations_final.round(3).to_csv(os.path.join(path, 'df_renovations_final.csv')) merged_df_heater_final.round(3).to_csv(os.path.join(path, 'df_heaters_final.csv')) + df_final_grouped_final.round(3).to_csv(os.path.join(path, 'conso_dpe_reel.csv')) buildings.logger.info('Dumping output in {}'.format(os.path.join(path, 'output.csv'))) if config['output'] == 'full': @@ -763,7 +766,7 @@ def calibration_res_irf(path, config=None, level_logger='DEBUG'): flow_district_heating = inputs_dynamics['flow_district_heating'].loc[year] carbon_content = inputs_dynamics['post_inputs']['carbon_emission'].loc[year, :] - buildings, s, o, df_renovations, merged_df_heater = stock_turnover(buildings, prices, taxes, + buildings, s, o, df_renovations, merged_df_heater, df_final_grouped = stock_turnover(buildings, prices, taxes, inputs_dynamics['cost_heater'], inputs_dynamics['cost_insulation'], inputs_dynamics['lifetime_insulation'], p_heater, p_insulation, f_built, year, inputs_dynamics['post_inputs'], From c643bc217a9db67c7e22d8e3a840d71421cb74f6 Mon Sep 17 00:00:00 2001 From: remydoudard Date: Wed, 11 Jun 2025 11:34:30 +0200 Subject: [PATCH 15/31] update gitignore : ignore stock --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7bba0adb..ce902749 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ /project/input/investment/_old/ /project/input/sfec/ /project/config/sfec/ +/project/input/stock/ # Byte-compiled / optimized / DLL files From c4017c6519d4b168d11ee1bde06dbb3659015aaa Mon Sep 17 00:00:00 2001 From: remydoudard Date: Thu, 12 Jun 2025 17:59:56 +0200 Subject: [PATCH 16/31] update_output_renovations_details renovation_details by quintile new insulations type mapping --- project/building.py | 91 +++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/project/building.py b/project/building.py index 31b8da74..26e4f48d 100644 --- a/project/building.py +++ b/project/building.py @@ -4120,6 +4120,21 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert certificate_diffs=[] certificate_diffs_results = pd.DataFrame() + ########## Adding df renovations ################################################################### + + + mapping_deciles = {"D1" : "Q1", + "D2" : "Q1", + "D3" : "Q2", + "D4" : "Q2", + "D5": "Q3", + "D6": "Q3", + "D7": "Q4", + "D8": "Q4", + "D9": "Q5", + "D10": "Q5"} + + for i in range(len(market_flow.columns)): category_after_col = f'Certif_after_Choice_{i}' flow_choice_col = f'Flow_Choice_{i}' @@ -4132,17 +4147,20 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert # merged_df_4 = merged_df_3.reset_index(level=['Heating system']) # merged_df_5 = merged_df_4.reset_index(level=['Heating system final']) # merged_df_6 = merged_df_5.reset_index(level=['Occupancy status']) - - + + merged_df_5.rename(columns={'Income owner': 'Income_owner_D'}, inplace=True) + merged_df_5['Income owner'] = merged_df_5['Income_owner_D'].map(mapping_deciles) + merged_df_5.drop('Income_owner_D', axis=1, inplace=True) + merged_df_5[flow_choice_diff] = merged_df_5['Occupancy status'] + "_" + merged_df_5['Housing type'] + "_" + merged_df_5['Heater replacement'].astype(str) + "_" + merged_df_5['Certificate_before_heater'] + "_" + merged_df_5['Certificate_before'] + "_" + merged_df_5[category_after_col] + "_" + merged_df_5['Income owner'] certificate_diffs.append(merged_df_5.groupby(flow_choice_diff)[flow_choice_col].sum()) for category_before, category_after, flow_choice in zip(merged_df['Certificate_before_heater'], merged_df[category_after_col], merged_df[flow_choice_col]): category_change = (category_before, category_after) flow_by_certificate_couples[category_change] = flow_by_certificate_couples.get(category_change, 0) + flow_choice - + certificate_diffs_results = pd.concat(certificate_diffs, axis=1) - + certificate_diffs_results = certificate_diffs_results.reset_index() l = pd.wide_to_long(certificate_diffs_results, stubnames='Flow_Choice_', i= 'index', j='operation') l2 = l.reset_index(level=['operation']) @@ -4151,6 +4169,10 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert l3['operation_details'] = l3['index'] + "_" + l3['operation_map'] l4 = l3.drop(['operation', 'index','operation_map'], axis=1) l5 = l4.set_index(['operation_details']) + + ########## End adding df renovations ################################################################### + + # Check that the sum of the flows for each possible pair of certificates equals the sum of renovation_flow. sum_check = 0 for key in flow_by_certificate_couples: @@ -5091,14 +5113,15 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, temp.index = temp.index.map(lambda x: 'Stock {} (Million)'.format(x)) output.update(temp.T / 10 ** 6) - + ############################################################################################################### + ####################### Adding consumption standard real ###################################################### + ############################################################################################################### consumption_std = self.consumption_heating(freq="year", climate=None) consumption_std_2 = reindex_mi(consumption_std, self.stock.index) * self.surface * self.stock consumption_std_3 = consumption_std_2.reset_index() consumption_std_3.rename(columns={0: "consumption_standard"}) - consumption_real = self.consumption_heating(freq="year", climate=climate, temp_sink=self._temp_sink) consumption_real_2 = reindex_mi(consumption_real, self.stock.index) * self.surface consumption_real_3 = self.consumption_actual(prices, consumption=consumption_real_2, bill_rebate=bill_rebate) * self.stock @@ -5148,11 +5171,6 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, df_final = df_final[["epc", "Heating system", "Stock buildings", "consumption_standard", "consumption_real", "coefficient_heater"]] - output['Stock efficient (Million)'] = output.get('Stock A (Million)', 0) + output.get('Stock B (Million)', 0) - output['Stock low-efficient (Million)'] = output.get('Stock G (Million)', 0) + output.get('Stock F (Million)', 0) - output['Stock to renovate (Million)'] = output['Stock low-efficient (Million)'] + output.get('Stock E (Million)', 0) + \ - output.get('Stock D (Million)', 0) - mapping_system_energy = {"Electricity-Heat pump water" : "Electricity", "Heating-District heating" : "District heating", "Natural gas-Performance boiler" : "Gas" , @@ -5185,6 +5203,17 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, df_final_grouped = df_final_grouped.reset_index() + ############################################################################################################### + ####################### End adding consumption standard_real ################################################### + ############################################################################################################### + + + output['Stock efficient (Million)'] = output.get('Stock A (Million)', 0) + output.get('Stock B (Million)', 0) + output['Stock low-efficient (Million)'] = output.get('Stock G (Million)', 0) + output.get('Stock F (Million)', 0) + output['Stock to renovate (Million)'] = output['Stock low-efficient (Million)'] + output.get('Stock E (Million)', 0) + \ + output.get('Stock D (Million)', 0) + + temp = self.stock.groupby('Heating system').sum() output['Stock Direct electric (Million)'] = temp['Electricity-Performance boiler'] / 10 ** 6 @@ -6288,6 +6317,11 @@ def condition_decarbonizing(x): output['Total Renovation ampleur (Thousand households)'] = temp flow_by_operation = self.flow_by_operation_insulation / 10**3 + + ############################################################################################################### + ####################### Adding df_renovations ################################################################# + ############################################################################################################### + l5 = self.l5.fillna(0) if self.flow_by_certificate_couples_obligation is not None: l5_obligation = self.l5_obligation.fillna(0) @@ -6321,27 +6355,26 @@ def condition_decarbonizing(x): merged_df_heater["Year"] = self.year merged_df_heater.drop(["epc before heater", "epc after heater"], axis=1, inplace=True) - else : merged_df_heater = pd.DataFrame(columns=["Occupancy status","Housing type","Heating system final","Heating system","Flow","certificate_before_heater","certificate_after_heater","steps_heater","Year"]) df_renovations_3 = pd.DataFrame(columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Income", "Operation type","Year","Value"]) - mapping_final = {"Wi" : "Wi", - "R" : "other", - "RWi" : "other" , - "F" : "other", - "FWi": "other", - "FR": "other", - "FRWi": "other", - "Wa": "other", - "WaWi": "other", - "WR": "other", - "WaRWi": "other", - "WaF": "other", - "WaFWi": "other", - "WaFR": "other", - "WaFRWi": "other"} + mapping_final = {"Wi" : "Windows", + "R" : "othersingleinsulation", + "RWi" : "2insulations", + "F" : "othersingleinsulation", + "FWi": "2insulations", + "FR": "2insulations", + "FRWi": "3insulations", + "Wa": "othersingleinsulation", + "WaWi": "2insulations", + "WR": "2insulations", + "WaRWi":"3insulations", + "WaF": "2insulations", + "WaFWi": "3insulations", + "WaFR": "3insulations", + "WaFRWi": "4insulations"} l5_bis = l5.reset_index() l5_bis["operation_details_2"] = l5_bis["operation_details"] @@ -6391,7 +6424,9 @@ def condition_decarbonizing(x): df_renovations_total = pd.concat([df_renovations, df_renovations_obligation], ignore_index=True) - + ############################################################################################################### + ####################### End adding_df_renovations ################################################################# + ############################################################################################################### output = Series(output).rename(self.year) stock = stock.rename(self.year) From e43226f0676ccc6b1350742c62d31c9c33c26e42 Mon Sep 17 00:00:00 2001 From: remydoudard Date: Thu, 12 Jun 2025 18:25:37 +0200 Subject: [PATCH 17/31] refactoring_df_renovations --- project/building.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/project/building.py b/project/building.py index 26e4f48d..d96467b7 100644 --- a/project/building.py +++ b/project/building.py @@ -4140,20 +4140,14 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert flow_choice_col = f'Flow_Choice_{i}' flow_choice_diff = f'Flow_Choice_diff{i}' - merged_df_2 = merged_df.reset_index(level=['Heater replacement']) - merged_df_3 = merged_df_2.reset_index(level=['Housing type']) - merged_df_4 = merged_df_3.reset_index(level=['Occupancy status']) - merged_df_5 = merged_df_4.reset_index(level=['Income owner']) - # merged_df_4 = merged_df_3.reset_index(level=['Heating system']) - # merged_df_5 = merged_df_4.reset_index(level=['Heating system final']) - # merged_df_6 = merged_df_5.reset_index(level=['Occupancy status']) + merged_df_reset = merged_df.reset_index(level=['Heater replacement', 'Housing type', 'Occupancy status', 'Income owner']) - merged_df_5.rename(columns={'Income owner': 'Income_owner_D'}, inplace=True) - merged_df_5['Income owner'] = merged_df_5['Income_owner_D'].map(mapping_deciles) - merged_df_5.drop('Income_owner_D', axis=1, inplace=True) + merged_df_reset = merged_df_reset.rename(columns={'Income owner': 'Income_owner_D'}) + merged_df_reset['Income owner'] = merged_df_reset['Income_owner_D'].map(mapping_deciles) + merged_df_reset = merged_df_reset.drop('Income_owner_D', axis=1) - merged_df_5[flow_choice_diff] = merged_df_5['Occupancy status'] + "_" + merged_df_5['Housing type'] + "_" + merged_df_5['Heater replacement'].astype(str) + "_" + merged_df_5['Certificate_before_heater'] + "_" + merged_df_5['Certificate_before'] + "_" + merged_df_5[category_after_col] + "_" + merged_df_5['Income owner'] - certificate_diffs.append(merged_df_5.groupby(flow_choice_diff)[flow_choice_col].sum()) + merged_df_reset[flow_choice_diff] = merged_df_reset['Occupancy status'] + "_" + merged_df_reset['Housing type'] + "_" + merged_df_reset['Heater replacement'].astype(str) + "_" + merged_df_reset['Certificate_before_heater'] + "_" + merged_df_reset['Certificate_before'] + "_" + merged_df_reset[category_after_col] + "_" + merged_df_reset['Income owner'] + certificate_diffs.append(merged_df_reset.groupby(flow_choice_diff)[flow_choice_col].sum()) for category_before, category_after, flow_choice in zip(merged_df['Certificate_before_heater'], merged_df[category_after_col], merged_df[flow_choice_col]): category_change = (category_before, category_after) From 03d516f33f297bd4e8331a3e6cfd1a2205b4d864 Mon Sep 17 00:00:00 2001 From: remydoudard Date: Fri, 13 Jun 2025 11:53:20 +0200 Subject: [PATCH 18/31] update refactoring renovation details --- project/building.py | 66 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/project/building.py b/project/building.py index d96467b7..5b4dc2a7 100644 --- a/project/building.py +++ b/project/building.py @@ -4122,7 +4122,7 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert ########## Adding df renovations ################################################################### - + # dictionary, `mapping_deciles`, which groups income deciles (D1–D10) into broader quintile categories (Q1–Q5). mapping_deciles = {"D1" : "Q1", "D2" : "Q1", "D3" : "Q2", @@ -4134,35 +4134,71 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert "D9": "Q5", "D10": "Q5"} - - for i in range(len(market_flow.columns)): - category_after_col = f'Certif_after_Choice_{i}' - flow_choice_col = f'Flow_Choice_{i}' - flow_choice_diff = f'Flow_Choice_diff{i}' + # Iteration over the columns numbers of a DataFrame called `market_flow`(each insulation combination) + for index, value in enumerate(market_flow.columns): + category_after_col = f'Certif_after_Choice_{index}' + flow_choice_col = f'Flow_Choice_{index}' + flow_choice_diff = f'Flow_Choice_diff{index}' merged_df_reset = merged_df.reset_index(level=['Heater replacement', 'Housing type', 'Occupancy status', 'Income owner']) - + + # mapping deciles with quintiles, removing deciles columns merged_df_reset = merged_df_reset.rename(columns={'Income owner': 'Income_owner_D'}) merged_df_reset['Income owner'] = merged_df_reset['Income_owner_D'].map(mapping_deciles) merged_df_reset = merged_df_reset.drop('Income_owner_D', axis=1) + # creating a description of all renovations with characteristics merged_df_reset[flow_choice_diff] = merged_df_reset['Occupancy status'] + "_" + merged_df_reset['Housing type'] + "_" + merged_df_reset['Heater replacement'].astype(str) + "_" + merged_df_reset['Certificate_before_heater'] + "_" + merged_df_reset['Certificate_before'] + "_" + merged_df_reset[category_after_col] + "_" + merged_df_reset['Income owner'] + + certificate_diffs.append(merged_df_reset.groupby(flow_choice_diff)[flow_choice_col].sum()) for category_before, category_after, flow_choice in zip(merged_df['Certificate_before_heater'], merged_df[category_after_col], merged_df[flow_choice_col]): category_change = (category_before, category_after) flow_by_certificate_couples[category_change] = flow_by_certificate_couples.get(category_change, 0) + flow_choice + # renovations by option (0 to 14) and characteristics (socio-economic, certificate before heating, after heating, after insulation) certificate_diffs_results = pd.concat(certificate_diffs, axis=1) - certificate_diffs_results = certificate_diffs_results.reset_index() - l = pd.wide_to_long(certificate_diffs_results, stubnames='Flow_Choice_', i= 'index', j='operation') - l2 = l.reset_index(level=['operation']) - l2['operation_map'] = l2['operation'].map(mapping_operations_2) - l3 = l2.reset_index() - l3['operation_details'] = l3['index'] + "_" + l3['operation_map'] - l4 = l3.drop(['operation', 'index','operation_map'], axis=1) - l5 = l4.set_index(['operation_details']) + + def transform_certificate_diffs(certificate_diffs_results, mapping_operations_2): + """ + Transform certificate differences results into long format with operation details. + + Args: + certificate_diffs_results (pd.DataFrame): Input DataFrame with certificate differences + mapping_operations_2 (dict): Mapping dictionary for operations + + Returns: + pd.DataFrame: Transformed DataFrame with operation details + """ + try: + transformed_df = ( + pd.wide_to_long( + certificate_diffs_results, + stubnames='Flow_Choice_', + i='index', + j='operation' + ) + .reset_index(level=['operation'])) + transformed_df['operation_map'] = transformed_df['operation'].map(mapping_operations_2) + transformed_df = transformed_df.reset_index() + transformed_df['operation_details'] = transformed_df['index'] + "_" + transformed_df['operation_map'] + transformed_df = transformed_df.drop(['operation', 'index','operation_map'], axis=1) + transformed_df = transformed_df.set_index(['operation_details']) + + return transformed_df + + except KeyError as e: + raise KeyError(f"Missing mapping for operation: {e}") + except Exception as e: + raise RuntimeError(f"Error transforming certificate diffs: {e}") + + # Reshape certificate_diffs_results to long format and add operation details + try: + l5 = transform_certificate_diffs(certificate_diffs_results, mapping_operations_2) + except Exception as e: + raise Exception(f"Failed to transform certificate differences: {e}") ########## End adding df renovations ################################################################### From 54d481180cb6400d591d6f1012799fc3eeb8deb5 Mon Sep 17 00:00:00 2001 From: remydoudard Date: Fri, 13 Jun 2025 12:12:00 +0200 Subject: [PATCH 19/31] update "l5" to "renovation_details_long" --- project/building.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/project/building.py b/project/building.py index 5b4dc2a7..2a2b1f70 100644 --- a/project/building.py +++ b/project/building.py @@ -1071,8 +1071,8 @@ def __init__(self, stock, surface, ratio_surface, efficiency, income, preference self.flow_by_certificate_couples_ampleur_insulation = None self.flow_by_certificate_couples_ampleur_obligation = None self.flow_by_operation_insulation = None - self.l5 = None - self.l5_obligation = None + self.renovation_details_long = None + self.renovation_details_long_obligation = None self.merged_df_heater = None self.flow_by_certificate_couples_heater = None self.sum_performance_changes_heater = None @@ -4196,7 +4196,7 @@ def transform_certificate_diffs(certificate_diffs_results, mapping_operations_2) # Reshape certificate_diffs_results to long format and add operation details try: - l5 = transform_certificate_diffs(certificate_diffs_results, mapping_operations_2) + renovation_details_long = transform_certificate_diffs(certificate_diffs_results, mapping_operations_2) except Exception as e: raise Exception(f"Failed to transform certificate differences: {e}") @@ -4267,11 +4267,11 @@ def transform_certificate_diffs(certificate_diffs_results, mapping_operations_2) self.flow_by_certificate_couples_ampleur_insulation = flow_by_certificate_couples_ampleur self.sum_performance_insulation_ampleur = sum_performance_insulation_ampleur self.flow_by_operation_insulation = flow_by_operation - self.l5 = l5 + self.renovation_details_long = renovation_details_long else: self.flow_by_certificate_couples_ampleur_obligation = flow_by_certificate_couples_ampleur self.sum_performance_insulation_ampleur_obligation = sum_performance_insulation_ampleur - self.l5_obligation = l5 + self.renovation_details_long_obligation = renovation_details_long return None @@ -6352,15 +6352,15 @@ def condition_decarbonizing(x): ####################### Adding df_renovations ################################################################# ############################################################################################################### - l5 = self.l5.fillna(0) + renovation_details_long = self.renovation_details_long.fillna(0) if self.flow_by_certificate_couples_obligation is not None: - l5_obligation = self.l5_obligation.fillna(0) + renovation_details_long_obligation = self.renovation_details_long_obligation.fillna(0) df_renovations_obligation_3 = pd.DataFrame(columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Income", "Operation type","Year","Value"]) - for i in l5_obligation.squeeze().index : + for i in renovation_details_long_obligation.squeeze().index : i_columns = i.split('_') i_columns.append(self.year) - value = l5_obligation.squeeze().loc[(i)] + value = renovation_details_long_obligation.squeeze().loc[(i)] i_columns.append(value) df_renovations_obligation_2 = pd.DataFrame([i_columns], columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation","Income", "Operation type","Year","Value"]) @@ -6371,7 +6371,7 @@ def condition_decarbonizing(x): # output.update({'Renovation {} (Thousand households)'.format(i): flow_by_operation.loc[(i)] for (i) in flow_by_operation.index}) -# output.update({'Renovation {} (Thousand households)'.format(i): l5.squeeze().loc[(i)] for (i) in l5.squeeze().index}) +# output.update({'Renovation {} (Thousand households)'.format(i): renovation_details_long.squeeze().loc[(i)] for (i) in renovation_details_long.squeeze().index}) if self.merged_df_heater is not None: merged_df_heater = self.merged_df_heater @@ -6406,13 +6406,12 @@ def condition_decarbonizing(x): "WaFR": "3insulations", "WaFRWi": "4insulations"} - l5_bis = l5.reset_index() - l5_bis["operation_details_2"] = l5_bis["operation_details"] - l5_bis[['1','2','3','4','5','6','7','8']] = l5_bis['operation_details_2'].str.split('_',expand=True) + renovation_details_long_bis = renovation_details_long.reset_index() + renovation_details_long_bis["operation_details_2"] = renovation_details_long_bis["operation_details"] + renovation_details_long_bis[['1','2','3','4','5','6','7','8']] = renovation_details_long_bis['operation_details_2'].str.split('_',expand=True) + renovation_details_long_bis['category'] = renovation_details_long_bis['8'].map(mapping_final) - l5_bis['category'] = l5_bis['8'].map(mapping_final) - - l6_bis = l5_bis.drop(['operation_details','operation_details_2', '8'], axis=1) + l6_bis = renovation_details_long_bis.drop(['operation_details','operation_details_2', '8'], axis=1) l7_bis = l6_bis.groupby(['1','2','3','4','5','6','7','category']).agg( Flow_Choice_=('Flow_Choice_', 'sum')) l7_bis = l7_bis.reset_index() From 3ebf7f5142971614dcd4ae517885d8f20bb13782 Mon Sep 17 00:00:00 2001 From: remydoudard Date: Fri, 13 Jun 2025 12:23:54 +0200 Subject: [PATCH 20/31] update df_renovations_details refactoring --- project/building.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/project/building.py b/project/building.py index 2a2b1f70..3b154e2e 100644 --- a/project/building.py +++ b/project/building.py @@ -6408,22 +6408,23 @@ def condition_decarbonizing(x): renovation_details_long_bis = renovation_details_long.reset_index() renovation_details_long_bis["operation_details_2"] = renovation_details_long_bis["operation_details"] - renovation_details_long_bis[['1','2','3','4','5','6','7','8']] = renovation_details_long_bis['operation_details_2'].str.split('_',expand=True) - renovation_details_long_bis['category'] = renovation_details_long_bis['8'].map(mapping_final) + renovation_details_long_bis[['1','2','3','4','5','6','7','8']] = renovation_details_long_bis['operation_details_2'].str.split('_', n=7, expand=True) + renovation_details_long_bis['category'] = renovation_details_long_bis['8'].apply(lambda x: mapping_final.get(x, 'Unknown')) - l6_bis = renovation_details_long_bis.drop(['operation_details','operation_details_2', '8'], axis=1) - l7_bis = l6_bis.groupby(['1','2','3','4','5','6','7','category']).agg( + renovation_details_cleaned = renovation_details_long_bis.drop(['operation_details','operation_details_2', '8'], axis=1) + grouped_renovation_details = renovation_details_cleaned.groupby(['1','2','3','4','5','6','7','category']).agg( Flow_Choice_=('Flow_Choice_', 'sum')) - l7_bis = l7_bis.reset_index() - l7_bis["operation_details"] = l7_bis['1'] + "_" + l7_bis['2'] + "_" + l7_bis['3'] + "_" + l7_bis['4'] + "_" + l7_bis['5'] + "_" + l7_bis['6'] + "_" + l7_bis['7'] + "_" + l7_bis['category'] - l8_bis = l7_bis.set_index('operation_details') - l8_bis = l8_bis.drop(['1','2','3','4','5','6','7','category'], axis=1) + grouped_renovation_details = grouped_renovation_details.reset_index() + grouped_renovation_details["operation_details"] = grouped_renovation_details.apply(lambda row: "_".join([str(row[str(i)]) for i in range(1, 8)] + [str(row['category'])]), axis=1) + grouped_renovation_details = grouped_renovation_details.set_index('operation_details') + cols_to_keep = [col for col in grouped_renovation_details.columns if col not in ['1','2','3','4','5','6','7','category']] + grouped_renovation_details = grouped_renovation_details[cols_to_keep] - for i,val in enumerate(l8_bis.squeeze().index) : + for i,val in enumerate(grouped_renovation_details.squeeze().index) : i_columns = val.split('_') i_columns.append(self.year) - value = l8_bis.squeeze().loc[(val)] + value = grouped_renovation_details.squeeze().loc[(val)] i_columns.append(value) df_renovations_2 = pd.DataFrame([i_columns], columns=["Occupancy status","Housing type","Heater replacement","Category before heater","Category before insulation", "Category after insulation", "Income", "Operation type","Year","Value"]) From 73a88b004f98ab7664845518c83e71763c6d884b Mon Sep 17 00:00:00 2001 From: remydoudard Date: Mon, 16 Jun 2025 17:43:53 +0200 Subject: [PATCH 21/31] df_renovation_details_corrections --- project/building.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/project/building.py b/project/building.py index 3b154e2e..b0cb9a6f 100644 --- a/project/building.py +++ b/project/building.py @@ -4017,21 +4017,21 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert # "True True True False": "other", # "True True True True": "other"} - mapping_operations = {"False False False True" : "Wi", - "False False True False" : "R", - "False False True True": "RWi", - "False True False False": "F", - "False True False True": "FWi", - "False True True False": "FR", - "False True True True": "FRWi", - "True False False False": "Wa", - "True False False True": "WaWi", - "True False True False": "WR", - "True False True True": "WaRWi", - "True True False False": "WaF", - "True True False True": "WaFWi", - "True True True False": "WaFR", - "True True True True": "WaFRWi"} + mapping_operations = {"Wi" : "Windows", + "R" : "othersingleinsulation", + "RWi" : "2insulations", + "F" : "othersingleinsulation", + "FWi": "2insulations", + "FR": "2insulations", + "FRWi": "3insulations", + "Wa": "othersingleinsulation", + "WaWi": "2insulations", + "WR": "2insulations", + "WaRWi":"3insulations", + "WaF": "2insulations", + "WaFWi": "3insulations", + "WaFR": "3insulations", + "WaFRWi": "4insulations"} # mapping_operations_2 = {0 : "Wi", # 1 : "R", @@ -6451,6 +6451,7 @@ def condition_decarbonizing(x): df_renovations_obligation["steps_insulation_with_heater"] = - (df_renovations_obligation["epc after insulation"] - df_renovations_obligation["epc before heater"]) df_renovations_obligation.drop(['epc before heater', 'epc before insulation', 'epc after insulation'], axis=1, inplace=True) df_renovations_obligation["Obligation"] = "Yes" + df_renovations_obligation['Operation type'] = df_renovations_obligation['Operation type'].apply(lambda x: mapping_final.get(x, 'Unknown')) df_renovations_total = pd.concat([df_renovations, df_renovations_obligation], ignore_index=True) From 7d9d3fa2d00283ad5654cfdc7b3eaec11b17f9e8 Mon Sep 17 00:00:00 2001 From: remydoudard Date: Fri, 18 Jul 2025 11:51:35 +0200 Subject: [PATCH 22/31] correction_mapping_deciles_renovations_details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mapping des déciles en quintiles uniquement si les données contiennent des déciles --- project/building.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/project/building.py b/project/building.py index b0cb9a6f..3109f271 100644 --- a/project/building.py +++ b/project/building.py @@ -4144,8 +4144,12 @@ def certificate_flow_insulation(self, stock, renovation_rate, market_share, cert # mapping deciles with quintiles, removing deciles columns merged_df_reset = merged_df_reset.rename(columns={'Income owner': 'Income_owner_D'}) - merged_df_reset['Income owner'] = merged_df_reset['Income_owner_D'].map(mapping_deciles) - merged_df_reset = merged_df_reset.drop('Income_owner_D', axis=1) + # merged_df_reset['Income owner'] = merged_df_reset['Income_owner_D'].map(mapping_deciles) + # merged_df_reset = merged_df_reset.drop('Income_owner_D', axis=1) + + mask_decile = merged_df_reset['Income_owner_D'].str.startswith("D") + merged_df_reset['Income owner'] = merged_df_reset['Income_owner_D'] # copie par défaut + merged_df_reset.loc[mask_decile, 'Income owner'] = merged_df_reset.loc[mask_decile, 'Income_owner_D'].map(mapping_deciles) # creating a description of all renovations with characteristics merged_df_reset[flow_choice_diff] = merged_df_reset['Occupancy status'] + "_" + merged_df_reset['Housing type'] + "_" + merged_df_reset['Heater replacement'].astype(str) + "_" + merged_df_reset['Certificate_before_heater'] + "_" + merged_df_reset['Certificate_before'] + "_" + merged_df_reset[category_after_col] + "_" + merged_df_reset['Income owner'] From 37fe05cf2e9895bae0b40874c77b8d0af9965e8d Mon Sep 17 00:00:00 2001 From: VictorMannoni Date: Thu, 11 Sep 2025 17:34:03 +0200 Subject: [PATCH 23/31] refactor(thermal): replace hardcoded CONVERSION with pef_elec argument Remove hardcoded CONVERSION. Add pef_elec=None argument to functions. Raise error if pef_elec is None. --- project/thermal.py | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/project/thermal.py b/project/thermal.py index 8d9b8bbd..9c4d68cb 100644 --- a/project/thermal.py +++ b/project/thermal.py @@ -25,7 +25,7 @@ """LOGISTIC_COEFFICIENT = pd.read_csv('project/input/logistic_regression_coefficient_epc.csv', index_col=[0]) LOGISTIC_COEFFICIENT.columns = ['Intercept', 'Proxy_conso_square'] LOGISTIC_COEFFICIENT.index.names = ['Performance']""" -CONVERSION = 2.3 + HDD = 55706 CERTIFICATE_3USES_BOUNDARIES = { 'A': [0, 50], @@ -503,7 +503,8 @@ def conventional_dhw_final(index): def conventional_energy_3uses(u_wall, u_floor, u_roof, u_windows, ratio_surface, efficiency, index, th_bridging='Medium', vent_types='Ventilation naturelle', infiltration='Medium', - air_rate=None, unobserved=None, method='3uses' + air_rate=None, unobserved=None, method='3uses', + pef_elec=None ): """Space heating conventional, and energy performance certificate. @@ -531,6 +532,9 @@ def conventional_energy_3uses(u_wall, u_floor, u_roof, u_windows, ratio_surface, """ + if pef_elec is None: + raise ValueError("The pef_elec factor is required but has not been provided.") + heating_final = conventional_heating_final(u_wall, u_floor, u_roof, u_windows, ratio_surface, efficiency, th_bridging=th_bridging, vent_types=vent_types, infiltration=infiltration, air_rate=air_rate, unobserved=unobserved @@ -543,10 +547,10 @@ def conventional_energy_3uses(u_wall, u_floor, u_roof, u_windows, ratio_surface, else: energy_carrier = index.get_level_values('Energy') - energy_primary = final2primary(energy_final, energy_carrier) + energy_primary = final2primary(energy_final, energy_carrier, pef_elec=pef_elec) other_consumptions = None if method == '5uses': - other_consumptions = (CONSUMPTION_LIGHT + AUXILIARY_CONSUMPTION) * CONVERSION + other_consumptions = (CONSUMPTION_LIGHT + AUXILIARY_CONSUMPTION) * pef_elec performance = find_certificate(energy_primary, method=method, other_consumptions=other_consumptions) @@ -616,15 +620,19 @@ def find_certificate(primary_consumption, other_consumptions=None, method='3uses raise NotImplementedError -def final2primary(heat_consumption, energy, conversion=CONVERSION): +def final2primary(heat_consumption, energy, pef_elec=None): + + if pef_elec is None: + raise ValueError("The pef_elec factor is required but has not been provided.") + if isinstance(heat_consumption, pd.Series): primary_heat_consumption = heat_consumption.copy() - primary_heat_consumption[energy == 'Electricity'] = primary_heat_consumption * conversion + primary_heat_consumption[energy == 'Electricity'] = primary_heat_consumption * pef_elec return primary_heat_consumption elif isinstance(heat_consumption, float): if energy == 'Electricity': - return heat_consumption * conversion + return heat_consumption * pef_elec else: return heat_consumption @@ -632,12 +640,12 @@ def final2primary(heat_consumption, energy, conversion=CONVERSION): # index if energy.index.equals(heat_consumption.index): primary_heat_consumption = heat_consumption.copy() - primary_heat_consumption.loc[energy == 'Electricity', :] = primary_heat_consumption * conversion + primary_heat_consumption.loc[energy == 'Electricity', :] = primary_heat_consumption * pef_elec return primary_heat_consumption # columns elif energy.index.equals(heat_consumption.columns): primary_heat_consumption = heat_consumption.copy() - primary_heat_consumption.loc[:, energy == 'Electricity'] = primary_heat_consumption * conversion + primary_heat_consumption.loc[:, energy == 'Electricity'] = primary_heat_consumption * pef_elec return primary_heat_consumption else: raise 'Energy DataFrame do not match indexes and columns' @@ -646,7 +654,7 @@ def final2primary(heat_consumption, energy, conversion=CONVERSION): def primary_heating_consumption(u_wall, u_floor, u_roof, u_windows, efficiency, energy, ratio_surface, hdd, - conversion=CONVERSION): + pef_elec=None): """Convert final to primary heating consumption. Parameters @@ -659,14 +667,18 @@ def primary_heating_consumption(u_wall, u_floor, u_roof, u_windows, efficiency, efficiency energy ratio_surface - conversion + pef_elec Returns ------- """ + + if pef_elec is None: + raise ValueError("The pef_elec factor is required but has not been provided.") + # data = pd.concat([u_wall, u_floor, u_roof, u_windows], axis=1, keys=['Wall', 'Floor', 'Roof', 'Windows']) heat_consumption = stat_heating_consumption(u_wall, u_floor, u_roof, u_windows, efficiency, ratio_surface, hdd) - return final2primary(heat_consumption, energy, conversion=conversion) + return final2primary(heat_consumption, energy, pef_elec) def heat_intensity(budget, method='v4'): @@ -767,7 +779,7 @@ def stat_certificate(df): if (primary_consumption > item[0]) & (primary_consumption <= item[1]): return key -def certificate_buildings(u_wall, u_floor, u_roof, u_windows, hdd, efficiency, energy, ratio_surface): +def certificate_buildings(u_wall, u_floor, u_roof, u_windows, hdd, efficiency, energy, ratio_surface, pef_elec=None): """Returns energy performance certificate. Parameters @@ -789,8 +801,12 @@ def certificate_buildings(u_wall, u_floor, u_roof, u_windows, hdd, efficiency, e Certificates for all buildings in the stock. """ + + if pef_elec is None: + raise ValueError("The pef_elec factor is required but has not been provided.") + primary_heat_consumption = primary_heating_consumption(u_wall, u_floor, u_roof, u_windows, hdd, efficiency, - energy, ratio_surface, conversion=CONVERSION) + energy, ratio_surface, pef_elec=pef_elec) return primary_heat_consumption, stat_certificate(primary_heat_consumption) From 00531ba868df259de4a10f9535a3df620a39c4e4 Mon Sep 17 00:00:00 2001 From: VictorMannoni Date: Thu, 11 Sep 2025 17:34:41 +0200 Subject: [PATCH 24/31] feat(inputs): add support for pef_elec parameter Read config['energy']['pef_elec'] in read_inputs. Add fill_missing_years function (fills missing years with previous values). Apply fill_missing_years(inputs['pef_elec'], config['start'], config['end']). --- project/read_input.py | 1326 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1325 insertions(+), 1 deletion(-) diff --git a/project/read_input.py b/project/read_input.py index c9a98f76..ffeb566e 100644 --- a/project/read_input.py +++ b/project/read_input.py @@ -1 +1,1325 @@ -# Copyright 2020-2021 Ecole Nationale des Ponts et Chaussées # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Original author Lucas Vivier import copy import os import pandas as pd from pandas import Series, DataFrame, concat, MultiIndex, Index from numpy.random import normal from numpy.testing import assert_almost_equal from project.utils import reindex_mi, get_pandas, make_plot, get_series, reverse_dict, select from project.dynamic import stock_need, share_multi_family, share_type_built from project.input.param import generic_input from project.input.resources import resources_data class PublicPolicy: """Public policy parent class. Attributes ---------- name : str Name of the policy. start : int Year policy starts. end : int Year policy ends. value: float policy : {'energy_taxes', 'subsidies'} """ def __init__(self, name, start, end, value, policy, gest=None, cap=None, target=None, cost_min=None, cost_max=None, new=None, by='index', non_cumulative=None, frequency=None, intensive=None, min_performance=None, bonus=False, social_housing=True, duration=None, recycling=None, proportional=None, recycling_ini=None, year_stop=None, years_stop=None, public_cost=None, variable=True): self.name = name self.start = start self.end = end self.value = value self.policy = policy self.gest = gest self.cap = cap self.target = target self.cost_max = cost_max self.cost_min = cost_min self.new = new self.by = by self.non_cumulative = non_cumulative self.frequency = frequency self.intensive = intensive self.min_performance = min_performance self.bonus = bonus self.social_housing = social_housing self.duration = duration self.recycling = recycling self.recycling_ini = recycling_ini self.proportional = proportional self.public_cost = public_cost self.year_stop = year_stop self.years_stop = years_stop self.variable = variable self.incentive = False if policy in ['subsidy_ad_valorem', 'subsidy_target', 'subsidy_proportional', 'bonus', 'reduced_vat', 'zero_interest_loan']: self.incentive = True if (year_stop or years_stop) and self.incentive: if not isinstance(value, dict): self.value = {k: value for k in range(self.start, self.end)} if year_stop: if self.incentive: if year_stop < end: self.apply_year_stop(year_stop, self.value) else: self.end = min(year_stop, end) if years_stop: if self.incentive: for y in years_stop: if y < end: self.apply_year_stop(y, self.value) else: self.end = min(years_stop[0], end) def __repr__(self): return self.name def __str__(self): return self.name def cost_targeted(self, cost_insulation, target_subsidies=None): """ Gives the amount of the cost of a gesture for a segment over which the subvention applies. If self.new, cost global is the amount loaned for gestures which are considered as 'global renovations', and thus caped by the proper maximum zil amount taking the heater replacement into account. Also, cost_no_global are the amount loaned for unique or bunch renovations actions. Parameters ---------- cost_insulation: pd.DataFrame Cost of an insulation gesture target_subsidies: pd.DataFrame Boolean values. If self.new it corresponds to the global renovations Returns ------- cost: pd.DataFrame Each cell of the DataFrame corresponds to the cost after subventions of a specific gesture and segment """ cost = cost_insulation.copy() if self.target is not None and target_subsidies is not None: if isinstance(target_subsidies, Series): target_subsidies = pd.concat([target_subsidies] * cost.shape[1], axis=1, keys=cost.columns) cost = cost[target_subsidies.astype(bool)].fillna(0) if self.cost_max is not None: cost_max = reindex_mi(self.cost_max, cost.index) cost_max = pd.concat([cost_max] * cost.shape[1], axis=1).set_axis(cost.columns, axis=1) cost[cost > cost_max] = cost_max if self.cost_min is not None: cost_min = reindex_mi(self.cost_min, cost.index) cost_min = pd.concat([cost_min] * cost.shape[1], axis=1).set_axis( cost.columns, axis=1) cost[cost < cost_min] = 0 return cost def apply_year_stop(self, year_stop, value): """Put values of an incentive to 1e-4 to assess the impact of removing the incentive. Rationale is to keep calculating the number of eligible households that renovate without the incentive. Parameters ---------- year_stop value Returns ------- """ if self.policy == 'zero_interest_loan': if not isinstance(self.cost_max, dict): self.cost_max = {k: self.cost_max for k in range(self.start, self.end)} self.cost_max[year_stop] = 1e-4 else: if self.policy == 'reduced_vat': val = 0.1 - 1e-4 else: val = 1e-4 if isinstance(value[year_stop], (pd.Series, pd.DataFrame)): temp = value[year_stop].mask(value[year_stop] > 0, val) else: temp = val value[year_stop] = temp self.value = value def read_stock(config): """Read initial building stock. Parameters ---------- config: dict Returns ------- pd.Series MultiIndex Series with building stock attributes as levels. """ stock = get_pandas(config['building_stock'], lambda x: pd.read_csv(x, index_col=[0, 1, 2, 3, 4, 5, 6, 7, 8]).squeeze()).rename('Stock buildings') stock_sum = stock.sum() stock = stock.reset_index('Heating system') # replace wood fuel multi-family by natural gas in building stock idx = (stock['Heating system'] == 'Wood fuel-Standard boiler') & (stock.index.get_level_values('Housing type') == 'Multi-family') stock.loc[idx, 'Heating system'] = 'Natural gas-Standard boiler' idx = (stock['Heating system'] == 'Wood fuel-Performance boiler') & (stock.index.get_level_values('Housing type') == 'Multi-family') stock.loc[idx, 'Heating system'] = 'Natural gas-Performance boiler' assert_almost_equal(stock['Stock buildings'].sum(), stock_sum) # specify heat-pump repartition = 0.8 idx = stock['Heating system'] == 'Electricity-Heat pump' hp_water = stock.loc[idx, :].copy() hp_water['Stock buildings'] *= repartition hp_water['Heating system'] = hp_water['Heating system'].str.replace('Electricity-Heat pump', 'Electricity-Heat pump water') hp_air = stock.loc[idx, :].copy() hp_air['Stock buildings'] *= (1 - repartition) hp_air['Heating system'] = hp_air['Heating system'].str.replace('Electricity-Heat pump', 'Electricity-Heat pump air') stock = stock.loc[~idx, :] stock = pd.concat((stock, hp_water, hp_air), axis=0) if config['simple'].get('collective_boiler'): multi_family = stock.index.get_level_values('Housing type') == 'Multi-family' oil_fuel = stock['Heating system'].isin(['Oil fuel-Performance boiler', 'Oil fuel-Standard boiler']) stock.loc[multi_family & oil_fuel, 'Heating system'] = 'Oil fuel-Collective boiler' repartition = 0.5 idx_gas = stock['Heating system'].isin(['Natural gas-Performance boiler', 'Natural gas-Standard boiler']) & multi_family collective_gas = stock.loc[idx_gas, :].copy() collective_gas['Stock buildings'] *= repartition collective_gas['Heating system'] = collective_gas['Heating system'].str.replace('Natural gas-Performance boiler', 'Natural gas-Collective boiler') collective_gas['Heating system'] = collective_gas['Heating system'].str.replace('Natural gas-Standard boiler', 'Natural gas-Collective boiler') individual_gas = stock.loc[idx_gas, :].copy() individual_gas['Stock buildings'] *= (1 - repartition) stock = stock.loc[~idx_gas, :] stock = pd.concat((stock, collective_gas, individual_gas), axis=0) stock = stock.set_index('Heating system', append=True).squeeze() stock = stock.groupby(stock.index.names).sum() stock = pd.concat([stock], keys=[True], names=['Existing']) idx_names = ['Existing', 'Occupancy status', 'Income owner', 'Income tenant', 'Housing type', 'Heating system', 'Wall', 'Floor', 'Roof', 'Windows'] stock = stock.reorder_levels(idx_names) assert_almost_equal(stock.sum(), stock_sum) return stock def read_policies(config): # TODO: replace names to clarify the definition of policies (index = households, columns = technologies). # TODO: target should be a list with combination of simple condition. def read_mpr(data): l = list() heater = get_pandas(data['heater'], lambda x: pd.read_csv(x, index_col=[0, 1]).squeeze().unstack('Heating system final')) if data.get('growth_heater'): growth_heater = get_pandas(data['growth_heater'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) heater = {k: i * heater for k, i in growth_heater.items()} insulation = get_pandas(data['insulation'], lambda x: pd.read_csv(x, index_col=[0, 1])) if data.get('growth_insulation'): growth_insulation = get_pandas(data['growth_insulation'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) insulation = {k: i * insulation for k, i in growth_insulation.items()} if data.get('deep_renovation'): deep_renovation = get_pandas(data['deep_renovation'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()) l.append(PublicPolicy(data['name'], data['start'], data['end'], deep_renovation, 'subsidy_target', gest='insulation', target='deep_renovation', year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) if data['bonus']: bonus_best = get_pandas(data['bonus'], lambda x: pd.read_csv(x, index_col=[0, 1]).squeeze()) bonus_worst = get_pandas(data['bonus'], lambda x: pd.read_csv(x, index_col=[0, 1]).squeeze()) l.append(PublicPolicy(data['name'], data['start'], data['end'], bonus_best, 'bonus', gest='insulation', target='reach_best', year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) l.append(PublicPolicy(data['name'], data['start'], data['end'], bonus_worst, 'bonus', gest='insulation', target='out_worst', year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) l.append(PublicPolicy(data['name'], data['start'], data['end'], heater, 'subsidy_target', gest='heater', year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) l.append(PublicPolicy(data['name'], data['start'], data['end'], insulation, 'subsidy_target', gest='insulation', target=data.get('target'), year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) return l def read_mpr_serenite(data): """Create MPR Serenite PublicPolicy instance. MaPrimeRénov' Sérénité (formerly Habiter Mieux Sérénité) for major energy renovation work in your home. To do so, your work must result in an energy gain of at least 35%. The amount of the bonus varies according to the amount of your resources. Parameters ---------- data Returns ------- list """ l = list() value = get_series(data['insulation']) cap = None if data.get('cap') is not None: cap = get_series(data['cap']) if data.get('growth_insulation'): growth_insulation = get_series(data['growth_insulation']) value = {k: i * value for k, i in growth_insulation.items()} cap = {k: i * cap for k, i in growth_insulation.items()} l.append(PublicPolicy(data['name'], data['start'], data['end'], value, data['policy'], target=data.get('target'), gest='insulation', non_cumulative=data.get('non_cumulative'), cap=cap, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) if data.get('bonus') is not None: bonus = get_series(data['bonus']) l.append(PublicPolicy(data['name'], data['start'], data['end'], bonus, 'bonus', gest='insulation', year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) return l def read_cee(data): l = list() if isinstance(data['value'], str): cee_value = get_series(data['value']) elif isinstance(data['value'], (int, float)): cee_value = pd.Series(data['value'], index=range(data['start'], data['end'] + 1)) else: raise NotImplemented cumac_heater = get_series(data['cumac_heater']) cee_heater = cumac_heater * cee_value / 1000 cee_heater = cee_heater.unstack('Heating system final') cee_heater = {y: cee_heater.loc[cee_heater.index.get_level_values('Year') == y, :].droplevel('Year') for y in cee_heater.index.get_level_values('Year').unique()} cumac_insulation = get_series(data['cumac_insulation']) cee_insulation = cumac_insulation * cee_value / 1000 cee_insulation = cee_insulation.unstack('Insulation').rename_axis(None, axis=1) cee_insulation = {y: cee_insulation.loc[cee_insulation.index.get_level_values('Year') == y, :].squeeze() for y in cee_insulation.index.get_level_values('Year').unique()} bonus_heater = get_series(data['bonus_heater']['value']).unstack('Heating system final') bonus_heater = bonus_heater.reindex(cee_heater[data['start']].columns, axis=1).fillna(0) end = min(data['bonus_heater']['end'], data['end']) l.append(PublicPolicy('cee', data['bonus_heater']['start'], end, bonus_heater, 'bonus', gest='heater', social_housing=True, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) bonus_insulation = get_pandas(data['bonus_insulation']['value'], lambda x: pd.read_csv(x, index_col=[0])) end = min(data['bonus_heater']['end'], data['end']) l.append(PublicPolicy('cee', data['bonus_insulation']['start'], end, bonus_insulation, 'bonus', gest='insulation', social_housing=True, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) l.append(PublicPolicy('cee', data['start'], data['end'], cee_heater, 'subsidy_target', gest='heater', social_housing=True, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) l.append(PublicPolicy('cee', data['start'], data['end'], cee_insulation, 'subsidy_target', gest='insulation', social_housing=True, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) coefficient_obligation = get_pandas(data['coefficient_obligation'], lambda x: pd.read_csv(x, index_col=[0])).rename_axis('Energy', axis=1) cee_tax = (coefficient_obligation.T * cee_value).T / 1000 end = data['end'] if data.get('year_stop') is not None: end = data['year_stop'] cee_tax = cee_tax.loc[data['start']:end - 1, :] if data.get('years_stop') is not None: if data['years_stop']: cee_tax.loc[data['years_stop'], :] = 0 l.append(PublicPolicy('cee', data['start'], end, cee_tax, 'tax')) return l def read_cap(data): l = list() if 'insulation' in data.keys(): l.append(PublicPolicy('subsidies_cap', data['start'], data['end'], get_series(data['insulation']), 'subsidies_cap', gest='insulation', target=data.get('target_insulation'))) if 'heater' in data.keys(): l.append(PublicPolicy('subsidies_cap', data['start'], data['end'], get_series(data['heater']), 'subsidies_cap', gest='heater', target=data.get('target_heater'))) return l def read_carbon_tax(data): tax = get_series(data['tax'], header=None) emission = get_pandas(data['emission'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()) tax = (tax * emission.T).T.fillna(0) / 10 ** 6 tax = tax.loc[(tax != 0).any(axis=1)] recycling = None if data.get('recycling') is not None: if isinstance(data['recycling'], str): recycling = get_series(data['recycling'], header=0) else: recycling = data['recycling'] end = data['end'] if data.get('year_stop') is not None: end = data['year_stop'] tax = tax.loc[data['start']:end - 1, :] if data.get('years_stop') is not None: if data['years_stop']: tax.loc[data['years_stop'], :] = 0 return [PublicPolicy('carbon_tax', data['start'], end, tax, 'tax', recycling=recycling, recycling_ini=data.get('recycling_ini'))] def read_cite(data): """Creates the income tax credit PublicPolicy instance. Oil fuel-Performant Boiler exempted. Parameters ---------- data Returns ------- list """ l = list() if data['heater'] is not None: heater = get_series(data['heater']).unstack('Heating system final') l.append(PublicPolicy('cite', data['start'], data['end'], heater, 'subsidy_ad_valorem', gest='heater', cap=data['cap'], year_stop=data.get('year_stop'), years_stop=data.get('years_stop')) ) if data['insulation'] is not None: insulation = get_pandas(data['insulation'], lambda x: pd.read_csv(x, index_col=[0])) l.append( PublicPolicy('cite', data['start'], data['end'], insulation, 'subsidy_ad_valorem', gest='insulation', cap=data['cap'], target=data.get('target'), year_stop=data.get('year_stop'), years_stop=data.get('years_stop')) ) return l def read_zil(data): l = list() if isinstance(data['gest'], str): data['gest'] = [data['gest']] for gest in data['gest']: l.append(PublicPolicy('zero_interest_loan', data['start'], data['end'], data['value'], 'zero_interest_loan', cost_max=data.get('cost_max'), gest=gest, duration=data['duration'], year_stop=data.get('year_stop'), years_stop=data.get('years_stop'), public_cost=data.get('public_cost'), target=data.get('target'))) return l def read_reduced_vat(data): l = list() l.append(PublicPolicy('reduced_vat', data['start'], data['end'], data['value'], 'reduced_vat', gest='heater', social_housing=True, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) l.append( PublicPolicy('reduced_vat', data['start'], data['end'], data['value'], 'reduced_vat', gest='insulation', social_housing=True, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) return l def read_ad_valorem(data): l = list() value = data['value'] if isinstance(value, str): value = get_series(data['value']) by = 'index' if data.get('index') is not None: mask = get_series(data['index'], header=0) value *= mask if data.get('columns') is not None: mask = get_pandas(data['columns'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()).rename(None) if isinstance(value, (float, int)): value *= mask else: value = value.to_frame().dot(mask.to_frame().T) by = 'columns' if data.get('growth'): growth = get_series(data['growth'], header=None) value = {k: i * value for k, i in growth.items()} name = 'sub_ad_valorem' if data.get('name') is not None: name = data['name'] if isinstance(data['gest'], str): data['gest'] = [data['gest']] for gest in data['gest']: l.append(PublicPolicy(name, data['start'], data['end'], value, 'subsidy_ad_valorem', gest=gest, by=by, target=data.get('target'), cap=data.get('cap'), year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) return l def read_proportional(data): l = list() value = data['value'] if isinstance(value, str): value = get_series(data['value']) by = 'index' if data.get('index') is not None: mask = get_series(data['index'], header=0) value *= mask if data.get('columns') is not None: mask = get_pandas(data['columns'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()).rename(None) if isinstance(value, (float, int)): value *= mask else: value = value.to_frame().dot(mask.to_frame().T) by = 'columns' if data.get('growth'): growth = get_series(data['growth'], header=None) value = {k: i * value for k, i in growth.items()} name = 'sub_proportional' if data.get('name') is not None: name = data['name'] proportional = 'MWh_cumac' # tCO2_cumac if data.get('proportional') is not None: proportional = data['proportional'] if isinstance(data['gest'], str): data['gest'] = [data['gest']] for gest in data['gest']: l.append(PublicPolicy(name, data['start'], data['end'], value, 'subsidy_proportional', gest=gest, by=by, target=data.get('target'), proportional=proportional, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) return l def restriction_energy(data): return [PublicPolicy(data['name'], data['start'], data['end'], data['value'], 'restriction_energy', gest='heater', target=data.get('target'), variable=data.get('variable', True))] def restriction_heater(data): return [PublicPolicy(data['name'], data['start'], data['end'], data['value'], 'restriction_heater', gest='heater', target=data.get('target'), variable=data.get('variable', True))] def premature_heater(data): return [PublicPolicy(data['name'], data['start'], data['end'], data['value'], 'premature_heater', gest='heater', target=data.get('target'))] def read_obligation(data): l = list() banned_performance = get_pandas(data['value'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()).dropna() start = min(banned_performance.index) if data['start'] > start: start = data['start'] frequency = data['frequency'] if frequency is not None: frequency = pd.Series(frequency['value'], index=pd.Index(frequency['index'], name=frequency['name'])) target = None if data.get('target') is not None: target = pd.Series(True, index=pd.Index(data['target']['value'], name=data['target']['name'])) end = data['end'] if data.get('year_stop') is not None: end = data['year_stop'] if data.get('years_stop') is not None: end = data['years_stop'][0] l.append(PublicPolicy(data['name'], start, end, banned_performance, 'obligation', gest='insulation', frequency=frequency, intensive=data['intensive'], min_performance=data['minimum_performance'], target=target)) if data.get('sub_obligation') is not None: value = get_pandas(data['sub_obligation'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()) l.append(PublicPolicy('sub_obligation', start, data['end'], value, 'subsidy_ad_valorem', gest='insulation')) return l def read_regulation(data): return [PublicPolicy(data['name'], data['start'], data['end'], None, 'regulation', gest=data['gest'])] read = {'mpr': read_mpr, 'mpr_variant': read_mpr, 'mpr_efficacite': read_mpr, 'mpr_serenite': read_mpr_serenite, 'mpr_performance': read_mpr_serenite, 'mpr_serenite_variant': read_mpr_serenite, 'mpr_serenite_high_income': read_mpr_serenite, 'mpr_serenite_low_income': read_mpr_serenite, 'mpr_multifamily': read_mpr_serenite, "mpr_multifamily_updated": read_mpr_serenite, 'mpr_multifamily_deep': read_mpr_serenite, 'mpr_serenite_multifamily_variant': read_mpr_serenite, 'cee': read_cee, 'cee_variant': read_cee, 'cap': read_cap, 'cap_updated': read_cap, 'cap_variant': read_cee, 'carbon_tax': read_carbon_tax, 'carbon_tax_variant': read_carbon_tax, 'cite': read_cite, 'cite_insulation': read_cite, 'cite_heater': read_cite, 'reduced_vat': read_reduced_vat, 'reduced_vat_variant': read_reduced_vat, 'zero_interest_loan': read_zil} list_policies = list() for key, item in config['policies'].items(): item['name'] = key if key in read.keys(): list_policies += read[key](item) else: if item.get('policy') == 'subsidy_ad_valorem': list_policies += read_ad_valorem(item) elif item.get('policy') == 'subsidy_proportional': list_policies += read_proportional(item) elif item.get('policy') == 'zero_interest_loan': list_policies += read_zil(item) elif item.get('policy') == 'premature_heater': list_policies += premature_heater(item) elif item.get('policy') == 'restriction_energy': list_policies += restriction_energy(item) elif item.get('policy') == 'restriction_heater': list_policies += restriction_heater(item) elif item.get('policy') == 'obligation': list_policies += read_obligation(item) elif item.get('policy') == 'regulation': list_policies += read_regulation(item) else: print('{} reading function is not implemented'.format(key)) policies_heater = [p for p in list_policies if p.gest == 'heater'] policies_insulation = [p for p in list_policies if p.gest == 'insulation'] taxes = [p for p in list_policies if p.policy == 'tax'] return policies_heater, policies_insulation, taxes def read_inputs(config, other_inputs=generic_input): """Read all inputs in Python object and concatenate in one dict. Parameters ---------- config: dict Configuration dictionary with path to data. other_inputs: dict Other inputs that are manually inserted in param.py Returns ------- dict """ inputs = dict() idx = range(config['start'], config['end']) inputs.update(other_inputs) if isinstance(config['energy']['energy_prices'], str): energy_prices = get_pandas(config['macro']['energy_prices'], lambda x: pd.read_csv(x, index_col=[0]).rename_axis('Year').rename_axis('Energy', axis=1)) elif isinstance(config['energy']['energy_prices'], dict): energy_prices = get_pandas(config['energy']['energy_prices']['ini'], lambda x: pd.read_csv(x, index_col=[0]).rename_axis('Year').rename_axis('Energy', axis=1)) energy_prices = energy_prices.loc[config['start'], :] rate = Series(config['energy']['energy_prices']['rate']).rename_axis('Energy') if config['energy']['energy_prices'].get('factor') is not None: rate *= config['energy']['energy_prices']['factor'] temp = range(config['start'] + 1, 2051) rate = concat([(1 + rate) ** n for n in range(len(temp))], axis=1, keys=temp) rate = concat((pd.Series(1, index=rate.index, name=config['start']), rate), axis=1) energy_prices = rate.T * energy_prices if config['energy']['energy_prices'].get('shock') is not None: temp = config['energy']['energy_prices']['shock'] energy_prices.loc[temp['start']:, :] *= temp['factor'] else: raise NotImplemented inputs.update({'energy_prices': energy_prices}) energy_taxes = get_pandas(config['energy']['energy_taxes'], lambda x: pd.read_csv(x, index_col=[0]).rename_axis('Year').rename_axis('Heating energy', axis=1)) inputs.update({'energy_taxes': energy_taxes}) inputs.update({'energy_vat': get_series(config['energy']['energy_vat'], header=None)}) inputs.update({'cost_heater': get_series(config['technical']['cost_heater'], header=[0])}) inputs.update({'efficiency': get_series(config['technical']['efficiency'], header=[0])}) inputs.update({'temp_sink': get_series(config['technical']['temp_sink'], header=None)}) inputs.update({'lifetime_heater': get_series(config['technical']['lifetime_heater'], header=[0])}) inputs.update({'cost_insulation': get_series(config['technical']['cost_insulation'], header=[0])}) inputs.update({'lifetime_insulation': config['renovation']['lifetime_insulation']}) inputs.update({'performance_insulation_renovation': get_series(config['technical']['performance_insulation_renovation'], header=None).to_dict()}) inputs.update({'performance_insulation_construction': get_series(config['technical']['performance_insulation_construction'], header=None).to_dict()}) """bill_saving_preferences = get_pandas(config['macro']['preferences_saving'], lambda x: pd.read_csv(x, index_col=[0])) preferences_insulation.update({'bill_saved': bill_saving_preferences.loc[:, 'Insulation']}) preferences_heater.update({'bill_saved': bill_saving_preferences.loc[:, 'Heater']})""" preferences_insulation = get_series(config['renovation']['preferences_insulation'], header=None).to_dict() preferences_insulation.update({'present_discount_rate': get_series(config['macro']['present_discount_rate'])}) preferences_heater = get_series(config['switch_heater']['preferences_heater'], header=None).to_dict() preferences_heater.update({'present_discount_rate': get_series(config['macro']['present_discount_rate'])}) inputs.update({'preferences': {'insulation': preferences_insulation, 'heater': preferences_heater}}) consumption_ini = get_series(config['macro']['consumption_ini'], header=[0]) inputs.update({'consumption_ini': consumption_ini}) ms_heater = get_pandas(config['switch_heater']['ms_heater'], lambda x: pd.read_csv(x, index_col=[0, 1])) ms_heater.columns.set_names('Heating system final', inplace=True) calibration_heater = { 'ms_heater': ms_heater, 'scale': config['switch_heater']['scale'] } inputs.update({'calibration_heater': calibration_heater}) if config['switch_heater'].get('district_heating') is not None: district_heating = get_series(config['switch_heater']['district_heating'], header=None) inputs.update({'flow_district_heating': district_heating.diff().fillna(0)}) calibration_renovation = None if config['renovation']['endogenous']: renovation_rate_ini = get_series(config['renovation']['renovation_rate_ini']).round(decimals=3) scale_calibration = config['renovation']['scale'] ms_insulation_ini = get_pandas(config['renovation']['ms_insulation']['ms_insulation_ini'], lambda x: pd.read_csv(x, index_col=[0, 1, 2, 3]).squeeze().rename(None).round(decimals=3)) minimum_performance = config['renovation']['ms_insulation']['minimum_performance'] calibration_renovation = {'renovation_rate_ini': renovation_rate_ini, 'scale': scale_calibration, 'threshold_indicator': config['renovation'].get('threshold'), 'ms_insulation_ini': ms_insulation_ini, 'minimum_performance': minimum_performance} inputs.update({'calibration_renovation': calibration_renovation}) if config['renovation'].get('exogenous_social'): exogenous_social = get_pandas(config['renovation']['exogenous_social'], lambda x: pd.read_csv(x, index_col=[0, 1])) exogenous_social.columns = exogenous_social.columns.astype(int) inputs.update({'exogenous_social': exogenous_social}) temp = None if config['renovation'].get('rational_behavior') is not None: if config['renovation']['rational_behavior']['activated']: temp = {'calibration': config['renovation']['rational_behavior']['calibration'], 'social': config['renovation']['rational_behavior']['social'], } inputs.update({'rational_behavior_insulation': temp}) temp = None if config['switch_heater'].get('rational_behavior') is not None: if config['switch_heater']['rational_behavior']['activated']: temp = { 'social': config['switch_heater']['rational_behavior']['social'], } inputs.update({'rational_behavior_heater': temp}) if config['macro'].get('population') is not None: population = get_pandas(config['macro']['population'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) inputs.update({'population': population.loc[:config['end']]}) if config['macro'].get('stock_ini') is not None: inputs.update({'stock_ini': config['macro']['stock_ini']}) if config['macro'].get('pop_housing') is None: inputs.update({'pop_housing_min': other_inputs['pop_housing_min']}) inputs.update({'factor_pop_housing': other_inputs['factor_pop_housing']}) else: pop_housing = get_pandas(config['macro']['pop_housing'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) inputs.update({'pop_housing': pop_housing.loc[:config['end']]}) if config['macro'].get('share_single_family_construction') is not None: temp = get_pandas(config['macro']['share_single_family_construction'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) inputs.update({'share_single_family_construction': temp}) else: if config['macro'].get('share_multi_family') is None: inputs.update({'factor_multi_family': other_inputs['factor_multi_family']}) else: _share_multi_family = get_pandas(config['macro']['share_multi_family'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) inputs.update({'share_multi_family': _share_multi_family}) if config['macro']['available_income'] is not None: inputs.update({'available_income': config['macro']['available_income']}) inputs.update({'income_rate': config['macro']['income_rate']}) income = get_series(config['macro']['income'], header=[0]) inputs.update({'income': income}) if isinstance(config['macro']['demolition_rate'], (float, int)): demolition_rate = config['macro']['demolition_rate'] elif config['macro'].get('demolition_rate') is not None: demolition_rate = get_series(config['macro']['demolition_rate'], header=None) else: demolition_rate = None inputs.update({'demolition_rate': demolition_rate}) rotation_rate = get_pandas(config['macro']['rotation_rate'], lambda x: pd.read_csv(x, index_col=[0])).squeeze().rename(None) inputs.update({'rotation_rate': rotation_rate}) surface = get_pandas(config['technical']['surface'], lambda x: pd.read_csv(x, index_col=[0, 1, 2]).squeeze().rename(None)) inputs.update({'surface': surface}) ratio_surface = get_pandas(config['technical']['ratio_surface'], lambda x: pd.read_csv(x, index_col=[0])) inputs.update({'ratio_surface': ratio_surface}) if config['macro']['surface_built'] is None: inputs.update({'surface_max': other_inputs['surface_max']}) inputs.update({'surface_elasticity': other_inputs['surface_elasticity']}) else: surface_built = get_pandas(config['macro']['surface_built'], lambda x: pd.read_csv(x, index_col=[0]).squeeze().rename(None)) inputs.update({'surface_built': surface_built}) if config['macro'].get('flow_construction') is not None: flow_construction = get_pandas(config['macro']['flow_construction'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) inputs.update({'flow_construction': flow_construction}) temp = get_series(config['switch_heater']['ms_heater_built'], header=[0]) # Create a dictionary with the year as key and the share of each heating system as value if 'Year' in temp.index.names: # Interpolation over year using last known value start = temp.index.get_level_values('Year').min() ms_heater_built = temp.unstack('Year').reindex(range(start, config['end']), axis=1) temp_idx = ms_heater_built.index.names ms_heater_built = ms_heater_built.reset_index().interpolate(axis=1, method='pad').set_index(temp_idx) # ms_heater_built = temp.unstack('Heating system').fillna(0) inputs.update({'ms_heater_built': ms_heater_built.fillna(0)}) inputs.update({'health_cost_dpe': get_series(config['health_cost_dpe'])}) inputs.update({'health_cost_income': get_series(config['health_cost_income'])}) carbon_value = get_pandas(config['carbon_value'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) inputs.update({'carbon_value': carbon_value.loc[config['start']:]}) carbon_emission = get_pandas(config['energy']['carbon_emission'], lambda x: pd.read_csv(x, index_col=[0]).rename_axis('Year')) inputs.update({'carbon_emission': carbon_emission.loc[config['start']:, :] * 1000}) renewable_gas = None if isinstance(config['energy']['renewable_gas'], str): renewable_gas = get_series(config['energy']['renewable_gas'], header=None) renewable_gas = renewable_gas.loc[config['start']:] # set carbon emission of natural gas constant inputs['carbon_emission'].loc[:, 'Natural gas'] = inputs['carbon_emission'].loc[config['start'], 'Natural gas'] inputs.update({'renewable_gas': renewable_gas}) footprint_built = get_series(config['technical']['footprint_construction'], header=None) inputs.update({'footprint_built': footprint_built}) footprint_renovation = get_pandas(config['technical']['footprint_renovation'], lambda x: pd.read_csv(x, index_col=[0])) inputs.update({'footprint_renovation': footprint_renovation}) if config['macro'].get('use_subsidies'): temp = get_pandas(config['macro']['use_subsidies'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()) inputs.update({'use_subsidies': temp}) else: inputs.update({'use_subsidies': pd.Series(dtype=float)}) if 'implicit_discount_rate' in config.keys(): inputs['implicit_discount_rate'] = get_series(config['implicit_discount_rate']) if 'hourly_profile' in config['technical'].keys(): temp = get_series(config['technical']['hourly_profile'], header=None) temp.index = pd.TimedeltaIndex(range(0, 24), unit='h') inputs.update({'hourly_profile': temp}) inputs['input_financing'].update(config['financing_cost']) if inputs['input_financing']['activated'] is False: temp_idx = get_series(inputs['input_financing']['upfront_max']).index inputs['input_financing']['upfront_max'] = pd.Series(100000, index=temp_idx) inputs['input_financing']['saving_rate'] = pd.Series(0, index=idx) inputs['input_financing']['interest_rate'] = pd.Series(0, index=idx) else: inputs['input_financing']['upfront_max'] = get_series(inputs['input_financing']['upfront_max']) inputs['input_financing']['saving_rate'] = get_series(inputs['input_financing']['saving_rate'], header=None) inputs['input_financing']['interest_rate'] = get_series(inputs['input_financing']['interest_rate'], header=None) return inputs def parse_inputs(inputs, taxes, config, stock): """Macro module : run exogenous dynamic parameters. Parameters ---------- inputs: dict Raw inputs read as Python object. taxes: list config: dict Configuration file. stock: Series Building stock. Returns ------- dict Parsed input """ idx = range(config['start'], config['end']) parsed_inputs = copy.deepcopy(inputs) if config['technical'].get('technical_progress') is not None: parsed_inputs['technical_progress'] = dict() if 'insulation' in config['technical']['technical_progress'].keys(): if config['technical']['technical_progress']['insulation']['activated']: value = config['technical']['technical_progress']['insulation']['value_end'] start = config['technical']['technical_progress']['insulation']['start'] end = config['technical']['technical_progress']['insulation']['end'] value = round((1 + value) ** (1 / (end - start + 1)) - 1, 5) parsed_inputs['technical_progress']['insulation'] = Series(value, index=range(start, end + 1)).reindex(idx).fillna(0) if 'heater' in config['technical']['technical_progress'].keys(): if config['technical']['technical_progress']['heater']['activated']: value = config['technical']['technical_progress']['heater']['value_end'] start = config['technical']['technical_progress']['heater']['start'] end = config['technical']['technical_progress']['heater']['end'] value = round((1 + value) ** (1 / (end - start + 1)) - 1, 5) parsed_inputs['technical_progress']['heater'] = Series(value, index=range(start, end + 1)).reindex(idx).fillna(0) if 'Year' in inputs['efficiency'].index.names: s = inputs['efficiency'].index.get_level_values('Year').min() df = inputs['efficiency'].copy() df = df.unstack('Heating system').reindex(range(s, config['end'])).fillna(method='ffill') parsed_inputs['efficiency'] = df.T s = inputs['temp_sink'].index.min() parsed_inputs['temp_sink'] = inputs['temp_sink'].reindex(range(s, config['end'])).fillna(method='ffill') if isinstance(inputs['demolition_rate'], (float, int)): parsed_inputs['demolition_rate'] = pd.Series(inputs['demolition_rate'], index=idx[1:]) elif isinstance(inputs['demolition_rate'], Series): s = inputs['demolition_rate'].index.min() parsed_inputs['demolition_rate'] = inputs['demolition_rate'].reindex(range(s, config['end'])).fillna(method='ffill') parsed_inputs['demolition_rate'] = parsed_inputs['demolition_rate'].loc[idx[1:]] if parsed_inputs['demolition_rate'] is None: parsed_inputs['flow_demolition'] = 0 else: parsed_inputs['flow_demolition'] = parsed_inputs['demolition_rate'] * stock.sum() if 'flow_construction' not in parsed_inputs.keys(): if 'population' in inputs.keys(): parsed_inputs['population_total'] = inputs['population'] parsed_inputs['sizing_factor'] = stock.sum() / inputs['stock_ini'] parsed_inputs['population'] = inputs['population'] * parsed_inputs['sizing_factor'] if 'pop_housing' in parsed_inputs.keys(): parsed_inputs['stock_need'] = parsed_inputs['population'] / parsed_inputs['pop_housing'] else: parsed_inputs['stock_need'], parsed_inputs['pop_housing'] = stock_need(parsed_inputs['population'], parsed_inputs['population'][ config['start']] / stock.sum(), inputs['pop_housing_min'], config['start'], inputs['factor_pop_housing']) parsed_inputs['flow_need'] = parsed_inputs['stock_need'] - parsed_inputs['stock_need'].shift(1) # if 'flow_construction' not in parsed_inputs.keys(): parsed_inputs['flow_construction'] = parsed_inputs['flow_need'] + parsed_inputs['flow_demolition'] parsed_inputs['available_income'] = pd.Series( [inputs['available_income'] * (1 + config['macro']['income_rate']) ** (i - idx[0]) for i in idx], index=idx) else: s = inputs['flow_construction'].index.min() parsed_inputs['flow_construction'] = inputs['flow_construction'].reindex(range(s, config['end'])).fillna(method='ffill') parsed_inputs['flow_construction'] = parsed_inputs['flow_construction'].loc[idx] parsed_inputs['surface'] = pd.concat([parsed_inputs['surface']] * len(idx), axis=1, keys=idx) if 'share_single_family_construction' in inputs.keys(): s = inputs['share_single_family_construction'].index.min() parsed_inputs['share_single_family_construction'] = inputs['share_single_family_construction'].reindex(range(s, config['end'])).fillna(method='ffill') parsed_inputs['share_single_family_construction'] = parsed_inputs['share_single_family_construction'].loc[idx] temp = pd.concat((parsed_inputs['share_single_family_construction'], (1 - parsed_inputs['share_single_family_construction'])), axis=1, keys=['Single-family', 'Multi-family'], names=['Housing type']) type_built = parsed_inputs['flow_construction'] * temp.T else: if 'share_multi_family' not in inputs.keys(): parsed_inputs['share_multi_family'] = share_multi_family(parsed_inputs['stock_need'], inputs['factor_multi_family']) type_built = share_type_built(parsed_inputs['stock_need'], parsed_inputs['share_multi_family'], parsed_inputs['flow_construction']) * parsed_inputs['flow_construction'] # multiply by the rate of income from start to end income = parsed_inputs['income'].copy() income = pd.concat([income] * len(idx), axis=1, keys=idx, names=['Year']) rate = pd.Series([(1 + parsed_inputs['income_rate']) ** (i - idx[0]) for i in idx], index=idx) parsed_inputs['income'] = income * rate share_decision_maker = stock.groupby( ['Occupancy status', 'Housing type', 'Income owner', 'Income tenant']).sum().unstack( ['Occupancy status', 'Income owner', 'Income tenant']) share_decision_maker = (share_decision_maker.T / share_decision_maker.sum(axis=1)).T share_decision_maker = pd.concat([share_decision_maker] * type_built.shape[1], keys=type_built.columns, axis=1) construction = (reindex_mi(type_built, share_decision_maker.columns, axis=1) * share_decision_maker).stack( ['Occupancy status', 'Income owner', 'Income tenant']).fillna(0) construction.rename_axis('Year', axis=1, inplace=True) construction = construction.loc[:, idx] construction = construction.stack() ms_heater_built = inputs['ms_heater_built'].stack('Year').unstack('Heating system').fillna(0) restriction = [k for k, p in config['policies'].items() if p.get('policy') in ['restriction_energy', 'restriction_heater']] for policy in restriction: if config['policies'][policy]['policy'] == 'restriction_energy': heating_system = [i for i, energy in resources_data['heating2heater'].items() if energy == config['policies'][policy]['value']] else: heating_system = config['policies'][policy]['value'] heating_system = [i for i in heating_system if i in ms_heater_built.columns] start_policy = config['policies'][policy]['start'] ms_heater_built.loc[ms_heater_built.index.get_level_values('Year') >= start_policy, heating_system] = 0 ms_heater_built = (ms_heater_built.T / ms_heater_built.sum(axis=1)).T ms_heater_built = reindex_mi(ms_heater_built, construction.index) temp = construction.copy() construction = (reindex_mi(construction, ms_heater_built.index) * ms_heater_built.T).T construction = construction.loc[(construction != 0).any(axis=1)] construction = construction.stack('Heating system').unstack('Year') assert round(temp.sum() - construction.sum().sum(), 0) == 0, 'Construction is not equal to the sum of the heating system' construction_dh = select(construction, {'Heating system': 'Heating-District heating'}).sum() parsed_inputs['flow_district_heating'] = parsed_inputs['flow_district_heating'] - construction_dh parsed_inputs['flow_district_heating'][parsed_inputs['flow_district_heating'] < 0] = 0 performance_insulation = pd.concat([pd.Series(inputs['performance_insulation_construction'])] * construction.shape[0], axis=1, keys=construction.index).T parsed_inputs['flow_built'] = pd.concat((construction, performance_insulation), axis=1).set_index( list(performance_insulation.keys()), append=True) parsed_inputs['flow_built'] = pd.concat([parsed_inputs['flow_built']], keys=[False], names=['Existing']).reorder_levels(stock.index.names) if not config['macro']['construction']: parsed_inputs['flow_built'][parsed_inputs['flow_built'] > 0] = 0 """ parsed_inputs['health_expenditure'] = df['Health expenditure'] parsed_inputs['mortality_cost'] = df['Social cost of mortality'] parsed_inputs['loss_well_being'] = df['Loss of well-being']""" parsed_inputs['carbon_value_kwh'] = (parsed_inputs['carbon_value'] * parsed_inputs['carbon_emission'].T).T.dropna() / 10**6 parsed_inputs['embodied_energy_built'] = inputs['footprint_built'].loc['Grey energy (kWh/m2)'] parsed_inputs['carbon_footprint_built'] = inputs['footprint_built'].loc['Carbon content (kgCO2/m2)'] # carbon footprint of renovation parsed_inputs['embodied_energy_renovation'] = inputs['footprint_renovation'].loc['Grey energy (kWh/m2)', :] parsed_inputs['carbon_footprint_renovation'] = inputs['footprint_renovation'].loc['Carbon content (kgCO2/m2)', :] temp = parsed_inputs['surface'].xs(False, level='Existing', drop_level=True) temp = (parsed_inputs['flow_built'].groupby(temp.index.names).sum() * temp).sum() / 10**6 parsed_inputs['Surface construction (Million m2)'] = temp parsed_inputs['Carbon footprint construction (MtCO2)'] = (parsed_inputs['Surface construction (Million m2)'] * parsed_inputs['carbon_footprint_built']) / 10**3 parsed_inputs['Embodied energy construction (TWh PE)'] = (parsed_inputs['Surface construction (Million m2)'] * parsed_inputs['embodied_energy_built']) / 10**3 energy_prices = parsed_inputs['energy_prices'].copy() parsed_inputs['energy_prices_wt'] = energy_prices.copy() energy_taxes = parsed_inputs['energy_taxes'].copy() if config['simple']['prices_constant']: energy_prices = pd.concat([energy_prices.loc[config['start'], :]] * energy_prices.shape[0], keys=energy_prices.index, axis=1).T total_taxes = pd.DataFrame(0, index=energy_prices.index, columns=energy_prices.columns) export_prices = dict() export_prices.update({'energy_prices': energy_prices}) for t in taxes: total_taxes = total_taxes.add(t.value, fill_value=0) export_prices.update({t.name: t.value}) if energy_taxes is not None: total_taxes = total_taxes.add(energy_taxes, fill_value=0) taxes += [PublicPolicy('energy_taxes', energy_taxes.index[0], energy_taxes.index[-1], energy_taxes, 'tax')] export_prices.update({'energy_taxes': energy_taxes}) if config['simple']['prices_constant']: total_taxes = pd.concat([total_taxes.loc[config['start'], :]] * total_taxes.shape[0], keys=total_taxes.index, axis=1).T # energy_vat = energy_prices * (inputs['energy_vat'] / (1 - inputs['energy_vat'])) energy_vat = energy_prices * inputs['energy_vat'] export_prices.update({'energy_vat': energy_vat}) taxes += [PublicPolicy('energy_vat', energy_vat.index[0], energy_vat.index[-1], energy_vat, 'tax')] total_taxes += energy_vat parsed_inputs['taxes'] = taxes parsed_inputs['total_taxes'] = total_taxes energy_prices = energy_prices.add(total_taxes, fill_value=0) parsed_inputs['energy_prices'] = energy_prices export_prices = reverse_dict({k: item.to_dict() for k, item in export_prices.items()}) export_prices = concat([pd.DataFrame(item) for k, item in export_prices.items()], axis=1, keys=export_prices.keys()) parsed_inputs.update({'export_prices': export_prices}) supply = {'insulation': None, 'heater': None} if config.get('supply') is not None: if config['supply']['activated_insulation']: supply.update({'insulation': {'markup_insulation': config['supply']['markup_insulation']}}) if config['supply']['activated_heater']: supply.update({'heater': {'markup_heater': config['supply']['markup_heater']}}) parsed_inputs.update({'supply': supply}) premature_replacement = None if config['switch_heater'].get('premature_replacement') is not None: premature_replacement = {'time': config['switch_heater']['premature_replacement'], 'information_rate': config['switch_heater']['information_rate']} parsed_inputs.update({'premature_replacement': premature_replacement}) return parsed_inputs def dump_inputs(parsed_inputs, path, figures=None): """Create summary input DataFrame. Parameters ---------- parsed_inputs: dict Returns ------- DataFrame """ summary_input = dict() if 'sizing_factor' in parsed_inputs.keys(): summary_input['Sizing factor (%)'] = pd.Series(parsed_inputs['sizing_factor'], index=parsed_inputs['population'].index) summary_input['Total population (Millions)'] = parsed_inputs['population'] / 10**6 summary_input['Income (Billions euro)'] = parsed_inputs['available_income'] * parsed_inputs['sizing_factor'] / 10**9 summary_input['Buildings stock (Millions)'] = parsed_inputs['stock_need'] / 10**6 summary_input['Person by housing'] = parsed_inputs['pop_housing'] summary_input['Buildings additional (Thousands)'] = parsed_inputs['flow_need'] / 10**3 summary_input['Buildings built (Thousands)'] = parsed_inputs['flow_construction'] / 10**3 summary_input['Buildings demolished (Thousands)'] = parsed_inputs['flow_demolition'] / 10**3 temp = parsed_inputs['surface'].xs(True, level='Existing', drop_level=True) temp.index = temp.index.map(lambda x: 'Surface existing {} - {} (m2/dwelling)'.format(x[0], x[1])) summary_input.update(temp.T) temp = parsed_inputs['surface'].xs(False, level='Existing', drop_level=True) temp.index = temp.index.map(lambda x: 'Surface construction {} - {} (m2/dwelling)'.format(x[0], x[1])) summary_input.update(temp.T) summary_input['Surface construction (Million m2)'] = parsed_inputs['Surface construction (Million m2)'] summary_input['Carbon footprint construction (MtCO2)'] = parsed_inputs['Carbon footprint construction (MtCO2)'] summary_input['Embodied energy construction (TWh PE)'] = parsed_inputs['Embodied energy construction (TWh PE)'] summary_input = pd.DataFrame(summary_input) t = parsed_inputs['total_taxes'].copy() t.columns = t.columns.map(lambda x: 'Taxes {} (euro/kWh)'.format(x)) temp = parsed_inputs['energy_prices'].copy() if figures is not False: make_plot(temp.dropna(), 'Prices (euro/kWh)', format_y=lambda y, _: '{:.2f}'.format(y), colors=resources_data['colors'], save=os.path.join(path, 'energy_prices.png')) temp.columns = temp.columns.map(lambda x: 'Prices {} (euro/kWh)'.format(x)) summary_input = pd.concat((summary_input, t, temp), axis=1) temp = parsed_inputs['income'].copy() temp.index = temp.index.map(lambda x: 'Income {} (euro/year)'.format(x)) summary_input = pd.concat((summary_input, temp.T), axis=1) summary_input.T.round(3).to_csv(os.path.join(path, 'input.csv')) parsed_inputs['export_prices'].round(4).to_csv(os.path.join(path, 'energy_prices.csv')) return summary_input def dict2data_inputs(inputs): """Grouped all inputs in the same DataFrame. Process is useful to implement a global sensitivity analysis. Returns ------- DataFrame """ data = DataFrame(columns=['variables', 'index', 'value']) metadata = DataFrame(columns=['variables', 'type', 'name', 'index', 'columns']) for key, item in inputs.items(): i = True if isinstance(item, dict): metadata = concat((metadata.T, Series({'variables': key, 'type': type(item).__name__})), axis=1).T i = False item = Series(item) if isinstance(item, (float, int)): data = concat((data.T, Series({'variables': key, 'value': item})), axis=1).T metadata = concat((metadata.T, Series({'variables': key, 'type': type(item).__name__})), axis=1).T if isinstance(item, DataFrame): metadata = concat((metadata.T, Series({'variables': key, 'type': type(item).__name__, 'index': item.index.names.copy(), 'columns': item.columns.names.copy()})), axis=1).T i = False item = item.stack(item.columns.names) if isinstance(item, Series): if i: metadata = concat((metadata.T, Series({'variables': key, 'type': type(item).__name__, 'name': item.name, 'index': item.index.names.copy()})), axis=1).T if isinstance(item.index, MultiIndex): item.index = item.index.to_flat_index() item.index = item.index.rename('index') df = concat([item.rename('value').reset_index()], keys=[key], names=['variables']).reset_index('variables') data = concat((data, df), axis=0) data = data.astype({'variables': 'string', 'value': 'float64'}) data.reset_index(drop=True, inplace=True) return data def data2dict_inputs(data, metadata): """Parse aggregate data pandas and return dict fill with several inputs. Parameters ---------- data: DataFrame Model data input. metadata: DataFrame Additional information to find out how to parse data. Returns ------- dict """ def parse_index(n, index_values): if len(n) == 1: idx = Index(index_values, name=n[0]) else: idx = MultiIndex.from_tuples(index_values) idx.names = n return idx parsed_input = dict() for variables, df in data.groupby('variables'): meta = metadata[metadata['variables'] == variables] if meta['type'].iloc[0] == 'int': parsed_input.update({variables: int(df['value'].iloc[0])}) elif meta['type'].iloc[0] == 'float': parsed_input.update({variables: float(df['value'].iloc[0])}) elif meta['type'].iloc[0] == 'Series': idx = parse_index(meta['index'].iloc[0], df['index'].values) parsed_input.update({variables: Series(df['value'].values, name=str(meta['name'].iloc[0]), index=idx)}) elif meta['type'].iloc[0] == 'DataFrame': idx = parse_index(meta['index'].iloc[0] + meta['columns'].iloc[0], df['index'].values) parsed_input.update({variables: Series(df['value'].values, name=str(meta['name'].iloc[0]), index=idx).unstack( meta['columns'].iloc[0])}) elif meta['type'].iloc[0] == 'dict': parsed_input.update({variables: Series(df['value'].values, index=df['index'].values).to_dict()}) return parsed_input def create_simple_policy(start, end, value=0.3, gest='insulation'): return PublicPolicy('sub_ad_valorem', start, end, value, 'subsidy_ad_valorem', gest=gest) \ No newline at end of file +# Copyright 2020-2021 Ecole Nationale des Ponts et Chaussées +# +# This file is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Original author Lucas Vivier + +import copy +import os +import pandas as pd +from pandas import Series, DataFrame, concat, MultiIndex, Index +from numpy.random import normal +from numpy.testing import assert_almost_equal + +from project.utils import reindex_mi, get_pandas, make_plot, get_series, reverse_dict, select +from project.dynamic import stock_need, share_multi_family, share_type_built +from project.input.param import generic_input +from project.input.resources import resources_data + + +class PublicPolicy: + """Public policy parent class. + + Attributes + ---------- + name : str + Name of the policy. + start : int + Year policy starts. + end : int + Year policy ends. + value: float + policy : {'energy_taxes', 'subsidies'} + """ + def __init__(self, name, start, end, value, policy, gest=None, cap=None, target=None, cost_min=None, cost_max=None, + new=None, by='index', non_cumulative=None, frequency=None, intensive=None, min_performance=None, + bonus=False, social_housing=True, duration=None, recycling=None, proportional=None, + recycling_ini=None, year_stop=None, years_stop=None, public_cost=None, variable=True): + self.name = name + self.start = start + self.end = end + self.value = value + self.policy = policy + self.gest = gest + self.cap = cap + self.target = target + self.cost_max = cost_max + self.cost_min = cost_min + self.new = new + self.by = by + self.non_cumulative = non_cumulative + self.frequency = frequency + self.intensive = intensive + self.min_performance = min_performance + self.bonus = bonus + self.social_housing = social_housing + self.duration = duration + self.recycling = recycling + self.recycling_ini = recycling_ini + self.proportional = proportional + self.public_cost = public_cost + self.year_stop = year_stop + self.years_stop = years_stop + self.variable = variable + + self.incentive = False + if policy in ['subsidy_ad_valorem', 'subsidy_target', 'subsidy_proportional', 'bonus', 'reduced_vat', + 'zero_interest_loan']: + self.incentive = True + + if (year_stop or years_stop) and self.incentive: + if not isinstance(value, dict): + self.value = {k: value for k in range(self.start, self.end)} + + if year_stop: + if self.incentive: + if year_stop < end: + self.apply_year_stop(year_stop, self.value) + else: + self.end = min(year_stop, end) + + if years_stop: + if self.incentive: + for y in years_stop: + if y < end: + self.apply_year_stop(y, self.value) + else: + self.end = min(years_stop[0], end) + + def __repr__(self): + return self.name + + def __str__(self): + return self.name + + def cost_targeted(self, cost_insulation, target_subsidies=None): + """ + Gives the amount of the cost of a gesture for a segment over which the subvention applies. + + If self.new, cost global is the amount loaned for gestures which are considered as 'global renovations', + and thus caped by the proper maximum zil amount taking the heater replacement into account. + Also, cost_no_global are the amount loaned for unique or bunch renovations actions. + + + Parameters + ---------- + cost_insulation: pd.DataFrame + Cost of an insulation gesture + target_subsidies: pd.DataFrame + Boolean values. If self.new it corresponds to the global renovations + + + Returns + ------- + cost: pd.DataFrame + Each cell of the DataFrame corresponds to the cost after subventions of a specific gesture and segment + """ + cost = cost_insulation.copy() + if self.target is not None and target_subsidies is not None: + if isinstance(target_subsidies, Series): + target_subsidies = pd.concat([target_subsidies] * cost.shape[1], axis=1, keys=cost.columns) + cost = cost[target_subsidies.astype(bool)].fillna(0) + if self.cost_max is not None: + cost_max = reindex_mi(self.cost_max, cost.index) + cost_max = pd.concat([cost_max] * cost.shape[1], axis=1).set_axis(cost.columns, axis=1) + cost[cost > cost_max] = cost_max + if self.cost_min is not None: + cost_min = reindex_mi(self.cost_min, cost.index) + cost_min = pd.concat([cost_min] * cost.shape[1], axis=1).set_axis( + cost.columns, axis=1) + cost[cost < cost_min] = 0 + return cost + + def apply_year_stop(self, year_stop, value): + """Put values of an incentive to 1e-4 to assess the impact of removing the incentive. + + + Rationale is to keep calculating the number of eligible households that renovate without the incentive. + + Parameters + ---------- + year_stop + value + + Returns + ------- + + """ + if self.policy == 'zero_interest_loan': + if not isinstance(self.cost_max, dict): + self.cost_max = {k: self.cost_max for k in range(self.start, self.end)} + self.cost_max[year_stop] = 1e-4 + + else: + if self.policy == 'reduced_vat': + val = 0.1 - 1e-4 + else: + val = 1e-4 + + if isinstance(value[year_stop], (pd.Series, pd.DataFrame)): + temp = value[year_stop].mask(value[year_stop] > 0, val) + else: + temp = val + + value[year_stop] = temp + self.value = value + + +def read_stock(config): + """Read initial building stock. + + Parameters + ---------- + config: dict + + Returns + ------- + pd.Series + MultiIndex Series with building stock attributes as levels. + """ + + stock = get_pandas(config['building_stock'], lambda x: pd.read_csv(x, index_col=[0, 1, 2, 3, 4, 5, 6, 7, 8]).squeeze()).rename('Stock buildings') + stock_sum = stock.sum() + + stock = stock.reset_index('Heating system') + + # replace wood fuel multi-family by natural gas in building stock + idx = (stock['Heating system'] == 'Wood fuel-Standard boiler') & (stock.index.get_level_values('Housing type') == 'Multi-family') + stock.loc[idx, 'Heating system'] = 'Natural gas-Standard boiler' + idx = (stock['Heating system'] == 'Wood fuel-Performance boiler') & (stock.index.get_level_values('Housing type') == 'Multi-family') + stock.loc[idx, 'Heating system'] = 'Natural gas-Performance boiler' + + assert_almost_equal(stock['Stock buildings'].sum(), stock_sum) + + # specify heat-pump + repartition = 0.8 + idx = stock['Heating system'] == 'Electricity-Heat pump' + hp_water = stock.loc[idx, :].copy() + hp_water['Stock buildings'] *= repartition + hp_water['Heating system'] = hp_water['Heating system'].str.replace('Electricity-Heat pump', 'Electricity-Heat pump water') + + hp_air = stock.loc[idx, :].copy() + hp_air['Stock buildings'] *= (1 - repartition) + hp_air['Heating system'] = hp_air['Heating system'].str.replace('Electricity-Heat pump', 'Electricity-Heat pump air') + stock = stock.loc[~idx, :] + stock = pd.concat((stock, hp_water, hp_air), axis=0) + + if config['simple'].get('collective_boiler'): + + multi_family = stock.index.get_level_values('Housing type') == 'Multi-family' + oil_fuel = stock['Heating system'].isin(['Oil fuel-Performance boiler', 'Oil fuel-Standard boiler']) + stock.loc[multi_family & oil_fuel, 'Heating system'] = 'Oil fuel-Collective boiler' + + repartition = 0.5 + idx_gas = stock['Heating system'].isin(['Natural gas-Performance boiler', 'Natural gas-Standard boiler']) & multi_family + collective_gas = stock.loc[idx_gas, :].copy() + collective_gas['Stock buildings'] *= repartition + collective_gas['Heating system'] = collective_gas['Heating system'].str.replace('Natural gas-Performance boiler', 'Natural gas-Collective boiler') + collective_gas['Heating system'] = collective_gas['Heating system'].str.replace('Natural gas-Standard boiler', 'Natural gas-Collective boiler') + individual_gas = stock.loc[idx_gas, :].copy() + individual_gas['Stock buildings'] *= (1 - repartition) + stock = stock.loc[~idx_gas, :] + stock = pd.concat((stock, collective_gas, individual_gas), axis=0) + + stock = stock.set_index('Heating system', append=True).squeeze() + stock = stock.groupby(stock.index.names).sum() + stock = pd.concat([stock], keys=[True], names=['Existing']) + idx_names = ['Existing', 'Occupancy status', 'Income owner', 'Income tenant', 'Housing type', + 'Heating system', 'Wall', 'Floor', 'Roof', 'Windows'] + + stock = stock.reorder_levels(idx_names) + assert_almost_equal(stock.sum(), stock_sum) + + return stock + + +def read_policies(config): + + # TODO: replace names to clarify the definition of policies (index = households, columns = technologies). + # TODO: target should be a list with combination of simple condition. + def read_mpr(data): + l = list() + heater = get_pandas(data['heater'], + lambda x: pd.read_csv(x, index_col=[0, 1]).squeeze().unstack('Heating system final')) + if data.get('growth_heater'): + growth_heater = get_pandas(data['growth_heater'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) + heater = {k: i * heater for k, i in growth_heater.items()} + + insulation = get_pandas(data['insulation'], lambda x: pd.read_csv(x, index_col=[0, 1])) + if data.get('growth_insulation'): + growth_insulation = get_pandas(data['growth_insulation'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) + insulation = {k: i * insulation for k, i in growth_insulation.items()} + + if data.get('deep_renovation'): + deep_renovation = get_pandas(data['deep_renovation'], + lambda x: pd.read_csv(x, index_col=[0]).squeeze()) + l.append(PublicPolicy(data['name'], data['start'], data['end'], deep_renovation, 'subsidy_target', + gest='insulation', target='deep_renovation', year_stop=data.get('year_stop'), + years_stop=data.get('years_stop'))) + + if data['bonus']: + bonus_best = get_pandas(data['bonus'], lambda x: pd.read_csv(x, index_col=[0, 1]).squeeze()) + bonus_worst = get_pandas(data['bonus'], lambda x: pd.read_csv(x, index_col=[0, 1]).squeeze()) + + l.append(PublicPolicy(data['name'], data['start'], data['end'], bonus_best, 'bonus', gest='insulation', + target='reach_best', year_stop=data.get('year_stop'), + years_stop=data.get('years_stop'))) + l.append(PublicPolicy(data['name'], data['start'], data['end'], bonus_worst, 'bonus', gest='insulation', + target='out_worst', year_stop=data.get('year_stop'), + years_stop=data.get('years_stop'))) + + l.append(PublicPolicy(data['name'], data['start'], data['end'], heater, 'subsidy_target', gest='heater', + year_stop=data.get('year_stop'), + years_stop=data.get('years_stop'))) + l.append(PublicPolicy(data['name'], data['start'], data['end'], insulation, 'subsidy_target', gest='insulation', + target=data.get('target'), year_stop=data.get('year_stop'), + years_stop=data.get('years_stop'))) + + return l + + def read_mpr_serenite(data): + """Create MPR Serenite PublicPolicy instance. + + MaPrimeRénov' Sérénité (formerly Habiter Mieux Sérénité) for major energy renovation work in your home. + To do so, your work must result in an energy gain of at least 35%. + The amount of the bonus varies according to the amount of your resources. + + Parameters + ---------- + data + + Returns + ------- + list + """ + l = list() + + value = get_series(data['insulation']) + cap = None + if data.get('cap') is not None: + cap = get_series(data['cap']) + + if data.get('growth_insulation'): + growth_insulation = get_series(data['growth_insulation']) + value = {k: i * value for k, i in growth_insulation.items()} + cap = {k: i * cap for k, i in growth_insulation.items()} + + l.append(PublicPolicy(data['name'], data['start'], data['end'], value, data['policy'], + target=data.get('target'), gest='insulation', non_cumulative=data.get('non_cumulative'), + cap=cap, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) + + if data.get('bonus') is not None: + bonus = get_series(data['bonus']) + l.append(PublicPolicy(data['name'], data['start'], data['end'], bonus, 'bonus', gest='insulation', + year_stop=data.get('year_stop'), + years_stop=data.get('years_stop'))) + return l + + def read_cee(data): + l = list() + if isinstance(data['value'], str): + cee_value = get_series(data['value']) + elif isinstance(data['value'], (int, float)): + cee_value = pd.Series(data['value'], index=range(data['start'], data['end'] + 1)) + else: + raise NotImplemented + + cumac_heater = get_series(data['cumac_heater']) + cee_heater = cumac_heater * cee_value / 1000 + cee_heater = cee_heater.unstack('Heating system final') + cee_heater = {y: cee_heater.loc[cee_heater.index.get_level_values('Year') == y, :].droplevel('Year') for y in + cee_heater.index.get_level_values('Year').unique()} + + cumac_insulation = get_series(data['cumac_insulation']) + cee_insulation = cumac_insulation * cee_value / 1000 + cee_insulation = cee_insulation.unstack('Insulation').rename_axis(None, axis=1) + cee_insulation = {y: cee_insulation.loc[cee_insulation.index.get_level_values('Year') == y, :].squeeze() for y + in cee_insulation.index.get_level_values('Year').unique()} + + bonus_heater = get_series(data['bonus_heater']['value']).unstack('Heating system final') + bonus_heater = bonus_heater.reindex(cee_heater[data['start']].columns, axis=1).fillna(0) + end = min(data['bonus_heater']['end'], data['end']) + l.append(PublicPolicy('cee', data['bonus_heater']['start'], end, bonus_heater, 'bonus', gest='heater', + social_housing=True, year_stop=data.get('year_stop'), + years_stop=data.get('years_stop'))) + + bonus_insulation = get_pandas(data['bonus_insulation']['value'], lambda x: pd.read_csv(x, index_col=[0])) + + end = min(data['bonus_heater']['end'], data['end']) + l.append(PublicPolicy('cee', data['bonus_insulation']['start'], end, bonus_insulation, 'bonus', + gest='insulation', social_housing=True, year_stop=data.get('year_stop'), + years_stop=data.get('years_stop'))) + + l.append(PublicPolicy('cee', data['start'], data['end'], cee_heater, 'subsidy_target', gest='heater', + social_housing=True, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) + l.append(PublicPolicy('cee', data['start'], data['end'], cee_insulation, 'subsidy_target', gest='insulation', + social_housing=True, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) + + coefficient_obligation = get_pandas(data['coefficient_obligation'], lambda x: pd.read_csv(x, index_col=[0])).rename_axis('Energy', axis=1) + cee_tax = (coefficient_obligation.T * cee_value).T / 1000 + + end = data['end'] + if data.get('year_stop') is not None: + end = data['year_stop'] + cee_tax = cee_tax.loc[data['start']:end - 1, :] + + if data.get('years_stop') is not None: + if data['years_stop']: + cee_tax.loc[data['years_stop'], :] = 0 + + l.append(PublicPolicy('cee', data['start'], end, cee_tax, 'tax')) + + return l + + def read_cap(data): + l = list() + if 'insulation' in data.keys(): + l.append(PublicPolicy('subsidies_cap', data['start'], data['end'], get_series(data['insulation']), + 'subsidies_cap', gest='insulation', target=data.get('target_insulation'))) + if 'heater' in data.keys(): + l.append(PublicPolicy('subsidies_cap', data['start'], data['end'], get_series(data['heater']), + 'subsidies_cap', gest='heater', target=data.get('target_heater'))) + return l + + def read_carbon_tax(data): + tax = get_series(data['tax'], header=None) + emission = get_pandas(data['emission'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()) + tax = (tax * emission.T).T.fillna(0) / 10 ** 6 + tax = tax.loc[(tax != 0).any(axis=1)] + + recycling = None + if data.get('recycling') is not None: + if isinstance(data['recycling'], str): + recycling = get_series(data['recycling'], header=0) + else: + recycling = data['recycling'] + + end = data['end'] + if data.get('year_stop') is not None: + end = data['year_stop'] + + tax = tax.loc[data['start']:end - 1, :] + if data.get('years_stop') is not None: + if data['years_stop']: + tax.loc[data['years_stop'], :] = 0 + + return [PublicPolicy('carbon_tax', data['start'], end, tax, 'tax', + recycling=recycling, recycling_ini=data.get('recycling_ini'))] + + def read_cite(data): + """Creates the income tax credit PublicPolicy instance. + + Oil fuel-Performant Boiler exempted. + + Parameters + ---------- + data + + Returns + ------- + list + """ + l = list() + if data['heater'] is not None: + heater = get_series(data['heater']).unstack('Heating system final') + l.append(PublicPolicy('cite', data['start'], data['end'], heater, 'subsidy_ad_valorem', gest='heater', + cap=data['cap'], year_stop=data.get('year_stop'), + years_stop=data.get('years_stop')) + ) + if data['insulation'] is not None: + insulation = get_pandas(data['insulation'], lambda x: pd.read_csv(x, index_col=[0])) + l.append( + PublicPolicy('cite', data['start'], data['end'], insulation, 'subsidy_ad_valorem', gest='insulation', + cap=data['cap'], target=data.get('target'), year_stop=data.get('year_stop'), + years_stop=data.get('years_stop')) + ) + return l + + def read_zil(data): + l = list() + if isinstance(data['gest'], str): + data['gest'] = [data['gest']] + for gest in data['gest']: + l.append(PublicPolicy('zero_interest_loan', data['start'], data['end'], data['value'], 'zero_interest_loan', + cost_max=data.get('cost_max'), gest=gest, duration=data['duration'], + year_stop=data.get('year_stop'), years_stop=data.get('years_stop'), + public_cost=data.get('public_cost'), target=data.get('target'))) + return l + + def read_reduced_vat(data): + l = list() + l.append(PublicPolicy('reduced_vat', data['start'], data['end'], data['value'], 'reduced_vat', gest='heater', + social_housing=True, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) + l.append( + PublicPolicy('reduced_vat', data['start'], data['end'], data['value'], 'reduced_vat', gest='insulation', + social_housing=True, year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) + return l + + def read_ad_valorem(data): + l = list() + value = data['value'] + + if isinstance(value, str): + value = get_series(data['value']) + + by = 'index' + if data.get('index') is not None: + mask = get_series(data['index'], header=0) + value *= mask + + if data.get('columns') is not None: + mask = get_pandas(data['columns'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()).rename(None) + if isinstance(value, (float, int)): + value *= mask + else: + value = value.to_frame().dot(mask.to_frame().T) + by = 'columns' + + if data.get('growth'): + growth = get_series(data['growth'], header=None) + value = {k: i * value for k, i in growth.items()} + + name = 'sub_ad_valorem' + if data.get('name') is not None: + name = data['name'] + + if isinstance(data['gest'], str): + data['gest'] = [data['gest']] + + for gest in data['gest']: + l.append(PublicPolicy(name, data['start'], data['end'], value, 'subsidy_ad_valorem', + gest=gest, by=by, target=data.get('target'), cap=data.get('cap'), + year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) + return l + + def read_proportional(data): + l = list() + + value = data['value'] + if isinstance(value, str): + value = get_series(data['value']) + + by = 'index' + if data.get('index') is not None: + mask = get_series(data['index'], header=0) + value *= mask + + if data.get('columns') is not None: + mask = get_pandas(data['columns'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()).rename(None) + if isinstance(value, (float, int)): + value *= mask + else: + value = value.to_frame().dot(mask.to_frame().T) + by = 'columns' + + if data.get('growth'): + growth = get_series(data['growth'], header=None) + value = {k: i * value for k, i in growth.items()} + + name = 'sub_proportional' + if data.get('name') is not None: + name = data['name'] + + proportional = 'MWh_cumac' # tCO2_cumac + if data.get('proportional') is not None: + proportional = data['proportional'] + + if isinstance(data['gest'], str): + data['gest'] = [data['gest']] + for gest in data['gest']: + l.append(PublicPolicy(name, data['start'], data['end'], value, 'subsidy_proportional', + gest=gest, by=by, target=data.get('target'), proportional=proportional, + year_stop=data.get('year_stop'), years_stop=data.get('years_stop'))) + + return l + + def restriction_energy(data): + return [PublicPolicy(data['name'], data['start'], data['end'], data['value'], + 'restriction_energy', gest='heater', target=data.get('target'), + variable=data.get('variable', True))] + + def restriction_heater(data): + return [PublicPolicy(data['name'], data['start'], data['end'], data['value'], + 'restriction_heater', gest='heater', target=data.get('target'), + variable=data.get('variable', True))] + + def premature_heater(data): + return [PublicPolicy(data['name'], data['start'], data['end'], data['value'], + 'premature_heater', gest='heater', target=data.get('target'))] + + def read_obligation(data): + l = list() + banned_performance = get_pandas(data['value'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()).dropna() + start = min(banned_performance.index) + if data['start'] > start: + start = data['start'] + frequency = data['frequency'] + if frequency is not None: + frequency = pd.Series(frequency['value'], index=pd.Index(frequency['index'], name=frequency['name'])) + + target = None + if data.get('target') is not None: + target = pd.Series(True, index=pd.Index(data['target']['value'], name=data['target']['name'])) + + end = data['end'] + if data.get('year_stop') is not None: + end = data['year_stop'] + if data.get('years_stop') is not None: + end = data['years_stop'][0] + + l.append(PublicPolicy(data['name'], start, end, banned_performance, 'obligation', + gest='insulation', frequency=frequency, intensive=data['intensive'], + min_performance=data['minimum_performance'], target=target)) + + if data.get('sub_obligation') is not None: + value = get_pandas(data['sub_obligation'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()) + l.append(PublicPolicy('sub_obligation', start, data['end'], value, 'subsidy_ad_valorem', + gest='insulation')) + return l + + def read_regulation(data): + return [PublicPolicy(data['name'], data['start'], data['end'], None, 'regulation', gest=data['gest'])] + + read = {'mpr': read_mpr, + 'mpr_variant': read_mpr, + 'mpr_efficacite': read_mpr, + 'mpr_serenite': read_mpr_serenite, + 'mpr_performance': read_mpr_serenite, + 'mpr_serenite_variant': read_mpr_serenite, + 'mpr_serenite_high_income': read_mpr_serenite, + 'mpr_serenite_low_income': read_mpr_serenite, + 'mpr_multifamily': read_mpr_serenite, + "mpr_multifamily_updated": read_mpr_serenite, + 'mpr_multifamily_deep': read_mpr_serenite, + 'mpr_serenite_multifamily_variant': read_mpr_serenite, + 'cee': read_cee, + 'cee_variant': read_cee, + 'cap': read_cap, + 'cap_updated': read_cap, + 'cap_variant': read_cee, + 'carbon_tax': read_carbon_tax, + 'carbon_tax_variant': read_carbon_tax, + 'cite': read_cite, + 'cite_insulation': read_cite, + 'cite_heater': read_cite, + 'reduced_vat': read_reduced_vat, + 'reduced_vat_variant': read_reduced_vat, + 'zero_interest_loan': read_zil} + + list_policies = list() + for key, item in config['policies'].items(): + item['name'] = key + if key in read.keys(): + list_policies += read[key](item) + else: + if item.get('policy') == 'subsidy_ad_valorem': + list_policies += read_ad_valorem(item) + elif item.get('policy') == 'subsidy_proportional': + list_policies += read_proportional(item) + elif item.get('policy') == 'zero_interest_loan': + list_policies += read_zil(item) + elif item.get('policy') == 'premature_heater': + list_policies += premature_heater(item) + elif item.get('policy') == 'restriction_energy': + list_policies += restriction_energy(item) + elif item.get('policy') == 'restriction_heater': + list_policies += restriction_heater(item) + elif item.get('policy') == 'obligation': + list_policies += read_obligation(item) + elif item.get('policy') == 'regulation': + list_policies += read_regulation(item) + else: + print('{} reading function is not implemented'.format(key)) + + policies_heater = [p for p in list_policies if p.gest == 'heater'] + policies_insulation = [p for p in list_policies if p.gest == 'insulation'] + taxes = [p for p in list_policies if p.policy == 'tax'] + + return policies_heater, policies_insulation, taxes + + +def read_inputs(config, other_inputs=generic_input): + """Read all inputs in Python object and concatenate in one dict. + + Parameters + ---------- + config: dict + Configuration dictionary with path to data. + other_inputs: dict + Other inputs that are manually inserted in param.py + + Returns + ------- + dict + """ + + inputs = dict() + idx = range(config['start'], config['end']) + + inputs.update(other_inputs) + + if isinstance(config['energy']['energy_prices'], str): + energy_prices = get_pandas(config['macro']['energy_prices'], lambda x: pd.read_csv(x, index_col=[0]).rename_axis('Year').rename_axis('Energy', axis=1)) + elif isinstance(config['energy']['energy_prices'], dict): + energy_prices = get_pandas(config['energy']['energy_prices']['ini'], lambda x: pd.read_csv(x, index_col=[0]).rename_axis('Year').rename_axis('Energy', axis=1)) + energy_prices = energy_prices.loc[config['start'], :] + rate = Series(config['energy']['energy_prices']['rate']).rename_axis('Energy') + if config['energy']['energy_prices'].get('factor') is not None: + rate *= config['energy']['energy_prices']['factor'] + temp = range(config['start'] + 1, 2051) + rate = concat([(1 + rate) ** n for n in range(len(temp))], axis=1, keys=temp) + rate = concat((pd.Series(1, index=rate.index, name=config['start']), rate), axis=1) + energy_prices = rate.T * energy_prices + if config['energy']['energy_prices'].get('shock') is not None: + temp = config['energy']['energy_prices']['shock'] + energy_prices.loc[temp['start']:, :] *= temp['factor'] + else: + raise NotImplemented + + inputs.update({'energy_prices': energy_prices}) + + energy_taxes = get_pandas(config['energy']['energy_taxes'], lambda x: pd.read_csv(x, index_col=[0]).rename_axis('Year').rename_axis('Heating energy', axis=1)) + inputs.update({'energy_taxes': energy_taxes}) + + inputs.update({'energy_vat': get_series(config['energy']['energy_vat'], header=None)}) + + inputs.update({'cost_heater': get_series(config['technical']['cost_heater'], header=[0])}) + + inputs.update({'efficiency': get_series(config['technical']['efficiency'], header=[0])}) + + inputs.update({'temp_sink': get_series(config['technical']['temp_sink'], header=None)}) + + inputs.update({'lifetime_heater': get_series(config['technical']['lifetime_heater'], header=[0])}) + + inputs.update({'cost_insulation': get_series(config['technical']['cost_insulation'], header=[0])}) + + inputs.update({'lifetime_insulation': config['renovation']['lifetime_insulation']}) + + inputs.update({'performance_insulation_renovation': get_series(config['technical']['performance_insulation_renovation'], header=None).to_dict()}) + + inputs.update({'performance_insulation_construction': get_series(config['technical']['performance_insulation_construction'], header=None).to_dict()}) + + """bill_saving_preferences = get_pandas(config['macro']['preferences_saving'], + lambda x: pd.read_csv(x, index_col=[0])) + preferences_insulation.update({'bill_saved': bill_saving_preferences.loc[:, 'Insulation']}) + preferences_heater.update({'bill_saved': bill_saving_preferences.loc[:, 'Heater']})""" + + preferences_insulation = get_series(config['renovation']['preferences_insulation'], header=None).to_dict() + preferences_insulation.update({'present_discount_rate': get_series(config['macro']['present_discount_rate'])}) + + preferences_heater = get_series(config['switch_heater']['preferences_heater'], header=None).to_dict() + preferences_heater.update({'present_discount_rate': get_series(config['macro']['present_discount_rate'])}) + + inputs.update({'preferences': {'insulation': preferences_insulation, + 'heater': preferences_heater}}) + + consumption_ini = get_series(config['macro']['consumption_ini'], header=[0]) + inputs.update({'consumption_ini': consumption_ini}) + + ms_heater = get_pandas(config['switch_heater']['ms_heater'], lambda x: pd.read_csv(x, index_col=[0, 1])) + ms_heater.columns.set_names('Heating system final', inplace=True) + calibration_heater = { + 'ms_heater': ms_heater, + 'scale': config['switch_heater']['scale'] + } + inputs.update({'calibration_heater': calibration_heater}) + + if config['switch_heater'].get('district_heating') is not None: + district_heating = get_series(config['switch_heater']['district_heating'], header=None) + inputs.update({'flow_district_heating': district_heating.diff().fillna(0)}) + + calibration_renovation = None + if config['renovation']['endogenous']: + renovation_rate_ini = get_series(config['renovation']['renovation_rate_ini']).round(decimals=3) + scale_calibration = config['renovation']['scale'] + ms_insulation_ini = get_pandas(config['renovation']['ms_insulation']['ms_insulation_ini'], lambda x: pd.read_csv(x, index_col=[0, 1, 2, 3]).squeeze().rename(None).round(decimals=3)) + minimum_performance = config['renovation']['ms_insulation']['minimum_performance'] + + calibration_renovation = {'renovation_rate_ini': renovation_rate_ini, + 'scale': scale_calibration, + 'threshold_indicator': config['renovation'].get('threshold'), + 'ms_insulation_ini': ms_insulation_ini, + 'minimum_performance': minimum_performance} + inputs.update({'calibration_renovation': calibration_renovation}) + + if config['renovation'].get('exogenous_social'): + exogenous_social = get_pandas(config['renovation']['exogenous_social'], + lambda x: pd.read_csv(x, index_col=[0, 1])) + exogenous_social.columns = exogenous_social.columns.astype(int) + + inputs.update({'exogenous_social': exogenous_social}) + + temp = None + if config['renovation'].get('rational_behavior') is not None: + if config['renovation']['rational_behavior']['activated']: + temp = {'calibration': config['renovation']['rational_behavior']['calibration'], + 'social': config['renovation']['rational_behavior']['social'], + } + inputs.update({'rational_behavior_insulation': temp}) + temp = None + if config['switch_heater'].get('rational_behavior') is not None: + if config['switch_heater']['rational_behavior']['activated']: + temp = { + 'social': config['switch_heater']['rational_behavior']['social'], + } + inputs.update({'rational_behavior_heater': temp}) + + if config['macro'].get('population') is not None: + population = get_pandas(config['macro']['population'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) + inputs.update({'population': population.loc[:config['end']]}) + + if config['macro'].get('stock_ini') is not None: + inputs.update({'stock_ini': config['macro']['stock_ini']}) + + if config['macro'].get('pop_housing') is None: + inputs.update({'pop_housing_min': other_inputs['pop_housing_min']}) + inputs.update({'factor_pop_housing': other_inputs['factor_pop_housing']}) + else: + pop_housing = get_pandas(config['macro']['pop_housing'], + lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) + inputs.update({'pop_housing': pop_housing.loc[:config['end']]}) + + if config['macro'].get('share_single_family_construction') is not None: + temp = get_pandas(config['macro']['share_single_family_construction'], + lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) + inputs.update({'share_single_family_construction': temp}) + + else: + if config['macro'].get('share_multi_family') is None: + inputs.update({'factor_multi_family': other_inputs['factor_multi_family']}) + else: + _share_multi_family = get_pandas(config['macro']['share_multi_family'], + lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) + inputs.update({'share_multi_family': _share_multi_family}) + + if config['macro']['available_income'] is not None: + inputs.update({'available_income': config['macro']['available_income']}) + inputs.update({'income_rate': config['macro']['income_rate']}) + income = get_series(config['macro']['income'], header=[0]) + inputs.update({'income': income}) + + if isinstance(config['macro']['demolition_rate'], (float, int)): + demolition_rate = config['macro']['demolition_rate'] + elif config['macro'].get('demolition_rate') is not None: + demolition_rate = get_series(config['macro']['demolition_rate'], header=None) + else: + demolition_rate = None + inputs.update({'demolition_rate': demolition_rate}) + + rotation_rate = get_pandas(config['macro']['rotation_rate'], lambda x: pd.read_csv(x, index_col=[0])).squeeze().rename(None) + inputs.update({'rotation_rate': rotation_rate}) + + surface = get_pandas(config['technical']['surface'], lambda x: pd.read_csv(x, index_col=[0, 1, 2]).squeeze().rename(None)) + inputs.update({'surface': surface}) + + ratio_surface = get_pandas(config['technical']['ratio_surface'], lambda x: pd.read_csv(x, index_col=[0])) + inputs.update({'ratio_surface': ratio_surface}) + + if config['macro']['surface_built'] is None: + inputs.update({'surface_max': other_inputs['surface_max']}) + inputs.update({'surface_elasticity': other_inputs['surface_elasticity']}) + else: + surface_built = get_pandas(config['macro']['surface_built'], lambda x: pd.read_csv(x, index_col=[0]).squeeze().rename(None)) + inputs.update({'surface_built': surface_built}) + + if config['macro'].get('flow_construction') is not None: + flow_construction = get_pandas(config['macro']['flow_construction'], + lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) + inputs.update({'flow_construction': flow_construction}) + + temp = get_series(config['switch_heater']['ms_heater_built'], header=[0]) + # Create a dictionary with the year as key and the share of each heating system as value + if 'Year' in temp.index.names: + # Interpolation over year using last known value + start = temp.index.get_level_values('Year').min() + ms_heater_built = temp.unstack('Year').reindex(range(start, config['end']), axis=1) + temp_idx = ms_heater_built.index.names + ms_heater_built = ms_heater_built.reset_index().interpolate(axis=1, method='pad').set_index(temp_idx) + # ms_heater_built = temp.unstack('Heating system').fillna(0) + + inputs.update({'ms_heater_built': ms_heater_built.fillna(0)}) + + inputs.update({'health_cost_dpe': get_series(config['health_cost_dpe'])}) + inputs.update({'health_cost_income': get_series(config['health_cost_income'])}) + + carbon_value = get_pandas(config['carbon_value'], lambda x: pd.read_csv(x, index_col=[0], header=None).squeeze()) + inputs.update({'carbon_value': carbon_value.loc[config['start']:]}) + + carbon_emission = get_pandas(config['energy']['carbon_emission'], lambda x: pd.read_csv(x, index_col=[0]).rename_axis('Year')) + inputs.update({'carbon_emission': carbon_emission.loc[config['start']:, :] * 1000}) + + renewable_gas = None + if isinstance(config['energy']['renewable_gas'], str): + renewable_gas = get_series(config['energy']['renewable_gas'], header=None) + renewable_gas = renewable_gas.loc[config['start']:] + # set carbon emission of natural gas constant + inputs['carbon_emission'].loc[:, 'Natural gas'] = inputs['carbon_emission'].loc[config['start'], 'Natural gas'] + inputs.update({'renewable_gas': renewable_gas}) + + footprint_built = get_series(config['technical']['footprint_construction'], header=None) + inputs.update({'footprint_built': footprint_built}) + footprint_renovation = get_pandas(config['technical']['footprint_renovation'], lambda x: pd.read_csv(x, index_col=[0])) + inputs.update({'footprint_renovation': footprint_renovation}) + + if config['macro'].get('use_subsidies'): + temp = get_pandas(config['macro']['use_subsidies'], lambda x: pd.read_csv(x, index_col=[0]).squeeze()) + inputs.update({'use_subsidies': temp}) + else: + inputs.update({'use_subsidies': pd.Series(dtype=float)}) + + if 'implicit_discount_rate' in config.keys(): + inputs['implicit_discount_rate'] = get_series(config['implicit_discount_rate']) + + if 'hourly_profile' in config['technical'].keys(): + temp = get_series(config['technical']['hourly_profile'], header=None) + temp.index = pd.TimedeltaIndex(range(0, 24), unit='h') + inputs.update({'hourly_profile': temp}) + + inputs['input_financing'].update(config['financing_cost']) + if inputs['input_financing']['activated'] is False: + temp_idx = get_series(inputs['input_financing']['upfront_max']).index + inputs['input_financing']['upfront_max'] = pd.Series(100000, index=temp_idx) + inputs['input_financing']['saving_rate'] = pd.Series(0, index=idx) + inputs['input_financing']['interest_rate'] = pd.Series(0, index=idx) + + else: + inputs['input_financing']['upfront_max'] = get_series(inputs['input_financing']['upfront_max']) + inputs['input_financing']['saving_rate'] = get_series(inputs['input_financing']['saving_rate'], header=None) + inputs['input_financing']['interest_rate'] = get_series(inputs['input_financing']['interest_rate'], header=None) + + if config['energy'].get('pef_elec'): + df = pd.read_csv(config['energy']['pef_elec']) + pef_elec = pd.Series(df['Electricity'].values, index=pd.Index(df['Year'].values, name='Year'), name=None) + inputs['pef_elec'] = pef_elec + + return inputs + + +def parse_inputs(inputs, taxes, config, stock): + """Macro module : run exogenous dynamic parameters. + + + Parameters + ---------- + inputs: dict + Raw inputs read as Python object. + taxes: list + config: dict + Configuration file. + stock: Series + Building stock. + + Returns + ------- + dict + Parsed input + """ + + # Fill missing years in a Pandas DataFrame or Series by copying values from the previous year. + def fill_missing_years(data, start_year=config['start'], end_year=config['end']): + + if isinstance(data, pd.Series) and data.index.name == 'Year': + full_years = list(range(start_year, end_year + 1)) + df = data.reindex(full_years) + df = df.ffill() + return df + + if 'Year' in data.index.names: + df = data.unstack(level='Year') + full_years = list(range(start_year, end_year + 1)) + df = df.reindex(columns=full_years) + df = df.ffill(axis=1) + df = df.stack('Year') + return df + + raise ValueError("L'index doit contenir 'Year'.") + + idx = range(config['start'], config['end']) + + parsed_inputs = copy.deepcopy(inputs) + + if config['technical'].get('technical_progress') is not None: + parsed_inputs['technical_progress'] = dict() + if 'insulation' in config['technical']['technical_progress'].keys(): + if config['technical']['technical_progress']['insulation']['activated']: + value = config['technical']['technical_progress']['insulation']['value_end'] + start = config['technical']['technical_progress']['insulation']['start'] + end = config['technical']['technical_progress']['insulation']['end'] + value = round((1 + value) ** (1 / (end - start + 1)) - 1, 5) + parsed_inputs['technical_progress']['insulation'] = Series(value, index=range(start, end + 1)).reindex(idx).fillna(0) + if 'heater' in config['technical']['technical_progress'].keys(): + if config['technical']['technical_progress']['heater']['activated']: + value = config['technical']['technical_progress']['heater']['value_end'] + start = config['technical']['technical_progress']['heater']['start'] + end = config['technical']['technical_progress']['heater']['end'] + value = round((1 + value) ** (1 / (end - start + 1)) - 1, 5) + parsed_inputs['technical_progress']['heater'] = Series(value, index=range(start, end + 1)).reindex(idx).fillna(0) + + if 'Year' in inputs['efficiency'].index.names: + s = inputs['efficiency'].index.get_level_values('Year').min() + df = inputs['efficiency'].copy() + df = df.unstack('Heating system').reindex(range(s, config['end'])).fillna(method='ffill') + parsed_inputs['efficiency'] = df.T + + s = inputs['temp_sink'].index.min() + parsed_inputs['temp_sink'] = inputs['temp_sink'].reindex(range(s, config['end'])).fillna(method='ffill') + + if isinstance(inputs['demolition_rate'], (float, int)): + parsed_inputs['demolition_rate'] = pd.Series(inputs['demolition_rate'], index=idx[1:]) + elif isinstance(inputs['demolition_rate'], Series): + s = inputs['demolition_rate'].index.min() + parsed_inputs['demolition_rate'] = inputs['demolition_rate'].reindex(range(s, config['end'])).fillna(method='ffill') + parsed_inputs['demolition_rate'] = parsed_inputs['demolition_rate'].loc[idx[1:]] + + if parsed_inputs['demolition_rate'] is None: + parsed_inputs['flow_demolition'] = 0 + else: + parsed_inputs['flow_demolition'] = parsed_inputs['demolition_rate'] * stock.sum() + + if 'flow_construction' not in parsed_inputs.keys(): + + if 'population' in inputs.keys(): + parsed_inputs['population_total'] = inputs['population'] + parsed_inputs['sizing_factor'] = stock.sum() / inputs['stock_ini'] + parsed_inputs['population'] = inputs['population'] * parsed_inputs['sizing_factor'] + + if 'pop_housing' in parsed_inputs.keys(): + parsed_inputs['stock_need'] = parsed_inputs['population'] / parsed_inputs['pop_housing'] + else: + parsed_inputs['stock_need'], parsed_inputs['pop_housing'] = stock_need(parsed_inputs['population'], + parsed_inputs['population'][ + config['start']] / stock.sum(), + inputs['pop_housing_min'], + config['start'], + inputs['factor_pop_housing']) + + parsed_inputs['flow_need'] = parsed_inputs['stock_need'] - parsed_inputs['stock_need'].shift(1) + # if 'flow_construction' not in parsed_inputs.keys(): + parsed_inputs['flow_construction'] = parsed_inputs['flow_need'] + parsed_inputs['flow_demolition'] + + parsed_inputs['available_income'] = pd.Series( + [inputs['available_income'] * (1 + config['macro']['income_rate']) ** (i - idx[0]) for i in idx], index=idx) + else: + s = inputs['flow_construction'].index.min() + parsed_inputs['flow_construction'] = inputs['flow_construction'].reindex(range(s, config['end'])).fillna(method='ffill') + parsed_inputs['flow_construction'] = parsed_inputs['flow_construction'].loc[idx] + parsed_inputs['surface'] = pd.concat([parsed_inputs['surface']] * len(idx), axis=1, keys=idx) + + if 'share_single_family_construction' in inputs.keys(): + s = inputs['share_single_family_construction'].index.min() + parsed_inputs['share_single_family_construction'] = inputs['share_single_family_construction'].reindex(range(s, config['end'])).fillna(method='ffill') + parsed_inputs['share_single_family_construction'] = parsed_inputs['share_single_family_construction'].loc[idx] + temp = pd.concat((parsed_inputs['share_single_family_construction'], (1 - parsed_inputs['share_single_family_construction'])), + axis=1, keys=['Single-family', 'Multi-family'], names=['Housing type']) + type_built = parsed_inputs['flow_construction'] * temp.T + else: + if 'share_multi_family' not in inputs.keys(): + parsed_inputs['share_multi_family'] = share_multi_family(parsed_inputs['stock_need'], + inputs['factor_multi_family']) + type_built = share_type_built(parsed_inputs['stock_need'], parsed_inputs['share_multi_family'], + parsed_inputs['flow_construction']) * parsed_inputs['flow_construction'] + + # multiply by the rate of income from start to end + income = parsed_inputs['income'].copy() + income = pd.concat([income] * len(idx), axis=1, keys=idx, names=['Year']) + + rate = pd.Series([(1 + parsed_inputs['income_rate']) ** (i - idx[0]) for i in idx], index=idx) + parsed_inputs['income'] = income * rate + + share_decision_maker = stock.groupby( + ['Occupancy status', 'Housing type', 'Income owner', 'Income tenant']).sum().unstack( + ['Occupancy status', 'Income owner', 'Income tenant']) + share_decision_maker = (share_decision_maker.T / share_decision_maker.sum(axis=1)).T + share_decision_maker = pd.concat([share_decision_maker] * type_built.shape[1], keys=type_built.columns, axis=1) + construction = (reindex_mi(type_built, share_decision_maker.columns, axis=1) * share_decision_maker).stack( + ['Occupancy status', 'Income owner', 'Income tenant']).fillna(0) + construction.rename_axis('Year', axis=1, inplace=True) + construction = construction.loc[:, idx] + construction = construction.stack() + + ms_heater_built = inputs['ms_heater_built'].stack('Year').unstack('Heating system').fillna(0) + restriction = [k for k, p in config['policies'].items() if p.get('policy') in ['restriction_energy', 'restriction_heater']] + for policy in restriction: + if config['policies'][policy]['policy'] == 'restriction_energy': + heating_system = [i for i, energy in resources_data['heating2heater'].items() if energy == config['policies'][policy]['value']] + else: + heating_system = config['policies'][policy]['value'] + heating_system = [i for i in heating_system if i in ms_heater_built.columns] + start_policy = config['policies'][policy]['start'] + ms_heater_built.loc[ms_heater_built.index.get_level_values('Year') >= start_policy, heating_system] = 0 + + ms_heater_built = (ms_heater_built.T / ms_heater_built.sum(axis=1)).T + + ms_heater_built = reindex_mi(ms_heater_built, construction.index) + temp = construction.copy() + construction = (reindex_mi(construction, ms_heater_built.index) * ms_heater_built.T).T + construction = construction.loc[(construction != 0).any(axis=1)] + construction = construction.stack('Heating system').unstack('Year') + assert round(temp.sum() - construction.sum().sum(), 0) == 0, 'Construction is not equal to the sum of the heating system' + + construction_dh = select(construction, {'Heating system': 'Heating-District heating'}).sum() + parsed_inputs['flow_district_heating'] = parsed_inputs['flow_district_heating'] - construction_dh + parsed_inputs['flow_district_heating'][parsed_inputs['flow_district_heating'] < 0] = 0 + + performance_insulation = pd.concat([pd.Series(inputs['performance_insulation_construction'])] * construction.shape[0], axis=1, + keys=construction.index).T + + parsed_inputs['flow_built'] = pd.concat((construction, performance_insulation), axis=1).set_index( + list(performance_insulation.keys()), append=True) + + parsed_inputs['flow_built'] = pd.concat([parsed_inputs['flow_built']], keys=[False], + names=['Existing']).reorder_levels(stock.index.names) + + if not config['macro']['construction']: + parsed_inputs['flow_built'][parsed_inputs['flow_built'] > 0] = 0 + + """ + parsed_inputs['health_expenditure'] = df['Health expenditure'] + parsed_inputs['mortality_cost'] = df['Social cost of mortality'] + parsed_inputs['loss_well_being'] = df['Loss of well-being']""" + + parsed_inputs['carbon_value_kwh'] = (parsed_inputs['carbon_value'] * parsed_inputs['carbon_emission'].T).T.dropna() / 10**6 + + parsed_inputs['embodied_energy_built'] = inputs['footprint_built'].loc['Grey energy (kWh/m2)'] + parsed_inputs['carbon_footprint_built'] = inputs['footprint_built'].loc['Carbon content (kgCO2/m2)'] + + # carbon footprint of renovation + parsed_inputs['embodied_energy_renovation'] = inputs['footprint_renovation'].loc['Grey energy (kWh/m2)', :] + parsed_inputs['carbon_footprint_renovation'] = inputs['footprint_renovation'].loc['Carbon content (kgCO2/m2)', :] + + temp = parsed_inputs['surface'].xs(False, level='Existing', drop_level=True) + temp = (parsed_inputs['flow_built'].groupby(temp.index.names).sum() * temp).sum() / 10**6 + parsed_inputs['Surface construction (Million m2)'] = temp + + parsed_inputs['Carbon footprint construction (MtCO2)'] = (parsed_inputs['Surface construction (Million m2)'] * parsed_inputs['carbon_footprint_built']) / 10**3 + parsed_inputs['Embodied energy construction (TWh PE)'] = (parsed_inputs['Surface construction (Million m2)'] * parsed_inputs['embodied_energy_built']) / 10**3 + + energy_prices = parsed_inputs['energy_prices'].copy() + parsed_inputs['energy_prices_wt'] = energy_prices.copy() + + energy_taxes = parsed_inputs['energy_taxes'].copy() + + if config['simple']['prices_constant']: + energy_prices = pd.concat([energy_prices.loc[config['start'], :]] * energy_prices.shape[0], keys=energy_prices.index, + axis=1).T + + total_taxes = pd.DataFrame(0, index=energy_prices.index, columns=energy_prices.columns) + export_prices = dict() + export_prices.update({'energy_prices': energy_prices}) + + for t in taxes: + total_taxes = total_taxes.add(t.value, fill_value=0) + export_prices.update({t.name: t.value}) + + if energy_taxes is not None: + total_taxes = total_taxes.add(energy_taxes, fill_value=0) + taxes += [PublicPolicy('energy_taxes', energy_taxes.index[0], energy_taxes.index[-1], energy_taxes, 'tax')] + export_prices.update({'energy_taxes': energy_taxes}) + + if config['simple']['prices_constant']: + total_taxes = pd.concat([total_taxes.loc[config['start'], :]] * total_taxes.shape[0], keys=total_taxes.index, + axis=1).T + + # energy_vat = energy_prices * (inputs['energy_vat'] / (1 - inputs['energy_vat'])) + energy_vat = energy_prices * inputs['energy_vat'] + export_prices.update({'energy_vat': energy_vat}) + + taxes += [PublicPolicy('energy_vat', energy_vat.index[0], energy_vat.index[-1], energy_vat, 'tax')] + total_taxes += energy_vat + parsed_inputs['taxes'] = taxes + parsed_inputs['total_taxes'] = total_taxes + + energy_prices = energy_prices.add(total_taxes, fill_value=0) + parsed_inputs['energy_prices'] = energy_prices + + export_prices = reverse_dict({k: item.to_dict() for k, item in export_prices.items()}) + export_prices = concat([pd.DataFrame(item) for k, item in export_prices.items()], axis=1, keys=export_prices.keys()) + parsed_inputs.update({'export_prices': export_prices}) + + supply = {'insulation': None, 'heater': None} + if config.get('supply') is not None: + if config['supply']['activated_insulation']: + supply.update({'insulation': {'markup_insulation': config['supply']['markup_insulation']}}) + + if config['supply']['activated_heater']: + supply.update({'heater': {'markup_heater': config['supply']['markup_heater']}}) + + parsed_inputs.update({'supply': supply}) + + premature_replacement = None + if config['switch_heater'].get('premature_replacement') is not None: + premature_replacement = {'time': config['switch_heater']['premature_replacement'], + 'information_rate': config['switch_heater']['information_rate']} + parsed_inputs.update({'premature_replacement': premature_replacement}) + + if inputs.get('pef_elec') is not None: + parsed_inputs['pef_elec'] = fill_missing_years(inputs['pef_elec'], config['start'], config['end']) + + return parsed_inputs + + +def dump_inputs(parsed_inputs, path, figures=None): + """Create summary input DataFrame. + + Parameters + ---------- + parsed_inputs: dict + + Returns + ------- + DataFrame + """ + + summary_input = dict() + if 'sizing_factor' in parsed_inputs.keys(): + summary_input['Sizing factor (%)'] = pd.Series(parsed_inputs['sizing_factor'], index=parsed_inputs['population'].index) + summary_input['Total population (Millions)'] = parsed_inputs['population'] / 10**6 + summary_input['Income (Billions euro)'] = parsed_inputs['available_income'] * parsed_inputs['sizing_factor'] / 10**9 + summary_input['Buildings stock (Millions)'] = parsed_inputs['stock_need'] / 10**6 + summary_input['Person by housing'] = parsed_inputs['pop_housing'] + summary_input['Buildings additional (Thousands)'] = parsed_inputs['flow_need'] / 10**3 + summary_input['Buildings built (Thousands)'] = parsed_inputs['flow_construction'] / 10**3 + summary_input['Buildings demolished (Thousands)'] = parsed_inputs['flow_demolition'] / 10**3 + + temp = parsed_inputs['surface'].xs(True, level='Existing', drop_level=True) + temp.index = temp.index.map(lambda x: 'Surface existing {} - {} (m2/dwelling)'.format(x[0], x[1])) + summary_input.update(temp.T) + + temp = parsed_inputs['surface'].xs(False, level='Existing', drop_level=True) + temp.index = temp.index.map(lambda x: 'Surface construction {} - {} (m2/dwelling)'.format(x[0], x[1])) + summary_input.update(temp.T) + + summary_input['Surface construction (Million m2)'] = parsed_inputs['Surface construction (Million m2)'] + summary_input['Carbon footprint construction (MtCO2)'] = parsed_inputs['Carbon footprint construction (MtCO2)'] + summary_input['Embodied energy construction (TWh PE)'] = parsed_inputs['Embodied energy construction (TWh PE)'] + + summary_input = pd.DataFrame(summary_input) + + t = parsed_inputs['total_taxes'].copy() + t.columns = t.columns.map(lambda x: 'Taxes {} (euro/kWh)'.format(x)) + temp = parsed_inputs['energy_prices'].copy() + if figures is not False: + make_plot(temp.dropna(), 'Prices (euro/kWh)', format_y=lambda y, _: '{:.2f}'.format(y), + colors=resources_data['colors'], save=os.path.join(path, 'energy_prices.png')) + temp.columns = temp.columns.map(lambda x: 'Prices {} (euro/kWh)'.format(x)) + summary_input = pd.concat((summary_input, t, temp), axis=1) + + temp = parsed_inputs['income'].copy() + temp.index = temp.index.map(lambda x: 'Income {} (euro/year)'.format(x)) + summary_input = pd.concat((summary_input, temp.T), axis=1) + + summary_input.T.round(3).to_csv(os.path.join(path, 'input.csv')) + + parsed_inputs['export_prices'].round(4).to_csv(os.path.join(path, 'energy_prices.csv')) + + return summary_input + + +def dict2data_inputs(inputs): + """Grouped all inputs in the same DataFrame. + + Process is useful to implement a global sensitivity analysis. + + Returns + ------- + DataFrame + """ + + data = DataFrame(columns=['variables', 'index', 'value']) + metadata = DataFrame(columns=['variables', 'type', 'name', 'index', 'columns']) + for key, item in inputs.items(): + i = True + + if isinstance(item, dict): + metadata = concat((metadata.T, Series({'variables': key, 'type': type(item).__name__})), axis=1).T + i = False + item = Series(item) + + if isinstance(item, (float, int)): + data = concat((data.T, Series({'variables': key, 'value': item})), axis=1).T + metadata = concat((metadata.T, Series({'variables': key, 'type': type(item).__name__})), axis=1).T + + if isinstance(item, DataFrame): + metadata = concat((metadata.T, Series({'variables': key, 'type': type(item).__name__, + 'index': item.index.names.copy(), + 'columns': item.columns.names.copy()})), axis=1).T + i = False + item = item.stack(item.columns.names) + + if isinstance(item, Series): + if i: + metadata = concat((metadata.T, Series({'variables': key, 'type': type(item).__name__, 'name': item.name, + 'index': item.index.names.copy()})), axis=1).T + + if isinstance(item.index, MultiIndex): + item.index = item.index.to_flat_index() + + item.index = item.index.rename('index') + df = concat([item.rename('value').reset_index()], keys=[key], names=['variables']).reset_index('variables') + data = concat((data, df), axis=0) + + data = data.astype({'variables': 'string', 'value': 'float64'}) + data.reset_index(drop=True, inplace=True) + return data + + +def data2dict_inputs(data, metadata): + """Parse aggregate data pandas and return dict fill with several inputs. + + Parameters + ---------- + data: DataFrame + Model data input. + metadata: DataFrame + Additional information to find out how to parse data. + + Returns + ------- + dict + """ + + def parse_index(n, index_values): + if len(n) == 1: + idx = Index(index_values, name=n[0]) + else: + idx = MultiIndex.from_tuples(index_values) + idx.names = n + return idx + + parsed_input = dict() + for variables, df in data.groupby('variables'): + meta = metadata[metadata['variables'] == variables] + if meta['type'].iloc[0] == 'int': + parsed_input.update({variables: int(df['value'].iloc[0])}) + elif meta['type'].iloc[0] == 'float': + parsed_input.update({variables: float(df['value'].iloc[0])}) + elif meta['type'].iloc[0] == 'Series': + idx = parse_index(meta['index'].iloc[0], df['index'].values) + parsed_input.update({variables: Series(df['value'].values, name=str(meta['name'].iloc[0]), index=idx)}) + elif meta['type'].iloc[0] == 'DataFrame': + idx = parse_index(meta['index'].iloc[0] + meta['columns'].iloc[0], df['index'].values) + parsed_input.update({variables: Series(df['value'].values, name=str(meta['name'].iloc[0]), index=idx).unstack( + meta['columns'].iloc[0])}) + + elif meta['type'].iloc[0] == 'dict': + parsed_input.update({variables: Series(df['value'].values, index=df['index'].values).to_dict()}) + + return parsed_input + + + + +def create_simple_policy(start, end, value=0.3, gest='insulation'): + return PublicPolicy('sub_ad_valorem', start, end, value, 'subsidy_ad_valorem', + gest=gest) From 130e3aaa8f4edda985f9dc2eec11fb240a7a2987 Mon Sep 17 00:00:00 2001 From: VictorMannoni Date: Thu, 11 Sep 2025 17:37:02 +0200 Subject: [PATCH 25/31] feat(model): propagate pef_elec in res_irf and stock_turnover Add and propagate pef_elec in res_irf. Add and propagate pef_elec in stock_turnover. --- project/model.py | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/project/model.py b/project/model.py index e8eb5bb7..7cb51ca1 100644 --- a/project/model.py +++ b/project/model.py @@ -343,7 +343,8 @@ def initialize(inputs, stock, year, taxes, path=None, config=None, logger=None, residual_rate=config['technical'].get('residual_rate'), constraint_heat_pumps=config['technical'].get('constraint_heat_pumps', True), variable_size_heater=config['technical'].get('variable_size_heater', True), - temp_sink=parsed_inputs['temp_sink']) + temp_sink=parsed_inputs['temp_sink'], + pef_elec=parsed_inputs['pef_elec']) technical_progress = None if 'technical_progress' in parsed_inputs.keys(): @@ -372,7 +373,8 @@ def initialize(inputs, stock, year, taxes, path=None, config=None, logger=None, 'health_cost_dpe': parsed_inputs['health_cost_dpe'], 'health_cost_income': parsed_inputs['health_cost_income'], 'output': config['output'], - 'hourly_profile': parsed_inputs.get('hourly_profile') + 'hourly_profile': parsed_inputs.get('hourly_profile'), + 'pef_elec': parsed_inputs.get('pef_elec') } return inputs_dynamic @@ -382,7 +384,7 @@ def stock_turnover(buildings, prices, taxes, cost_heater, cost_insulation, lifet post_inputs, calib_heater=None, calib_renovation=None, financing_cost=None, prices_before=None, climate=None, district_heating=None, step=1, demolition_rate=None, memory=False, exogenous_social=None, output_options='full', premature_replacement=None, supply=None, - carbon_content=None, carbon_content_before=None): + carbon_content=None, carbon_content_before=None, pef_elec=None): """Update stock vintage due to renovation, demolition and construction. @@ -437,7 +439,8 @@ def stock_turnover(buildings, prices, taxes, cost_heater, cost_insulation, lifet if output_options == 'full': buildings.consumption_before_retrofit = buildings.store_consumption(prices_before, carbon_content_before, - bill_rebate=bill_rebate_before) + bill_rebate=bill_rebate_before, + pef_elec=pef_elec) flow_retrofit = buildings.flow_retrofit(prices, cost_heater, cost_insulation, lifetime_insulation, financing_cost=financing_cost, @@ -450,7 +453,8 @@ def stock_turnover(buildings, prices, taxes, cost_heater, cost_insulation, lifet carbon_value_kwh=post_inputs['carbon_value_kwh'].loc[year, :], carbon_value=post_inputs['carbon_value'].loc[year], carbon_content=carbon_content, - bill_rebate=bill_rebate) + bill_rebate=bill_rebate, + pef_elec=pef_elec) """if memory: memory_dict = {'Memory': '{:.1f} MiB'.format(psutil.Process().memory_info().rss / (1024 * 1024)), @@ -461,7 +465,8 @@ def stock_turnover(buildings, prices, taxes, cost_heater, cost_insulation, lifet buildings.add_flows([flow_retrofit]) flows_obligation = buildings.flow_obligation(p_insulation, prices, cost_insulation, - financing_cost=financing_cost) + financing_cost=financing_cost, + pef_elec=pef_elec) if flows_obligation is not None: buildings.add_flows(flows_obligation) @@ -474,15 +479,15 @@ def stock_turnover(buildings, prices, taxes, cost_heater, cost_insulation, lifet if output_options == 'full': buildings.logger.debug('Full output') stock, output, df_renovations, merged_df_heater, df_final_grouped = buildings.parse_output_run(prices, post_inputs, climate=climate, step=step, taxes=taxes, - bill_rebate=bill_rebate) + bill_rebate=bill_rebate, pef_elec=pef_elec) elif output_options == 'cost_benefit': buildings.logger.debug('Cost-benefit output') stock = buildings.simplified_stock().rename(year) - output = buildings.parse_output_run_cba(prices, post_inputs, step=step, taxes=taxes, bill_rebate=bill_rebate) + output = buildings.parse_output_run_cba(prices, post_inputs, step=step, taxes=taxes, bill_rebate=bill_rebate, pef_elec=pef_elec) elif output_options == 'consumption': buildings.logger.debug('Consumption output') stock = buildings.simplified_stock().rename(year) - output = buildings.parse_output_consumption(prices, bill_rebate=bill_rebate) + output = buildings.parse_output_consumption(prices, bill_rebate=bill_rebate, pef_elec=pef_elec) else: raise NotImplemented('output_options should be full, cost_benefit or consumption') @@ -580,10 +585,12 @@ def res_irf(config, path, level_logger='DEBUG'): buildings.calibration_consumption(energy_prices.loc[buildings.first_year, :], inputs_dynamics['consumption_ini'], inputs_dynamics['health_cost_income'], - inputs_dynamics['health_cost_dpe']) + inputs_dynamics['health_cost_dpe'], + pef_elec=inputs_dynamics['pef_elec'].loc[buildings.first_year] + ) s, o, df_renovations, merged_df_heater, df_final_grouped = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs'], - taxes=taxes) + taxes=taxes, pef_elec=inputs_dynamics['pef_elec'].loc[buildings.first_year]) stock = pd.concat((stock, s), axis=1) output = pd.concat((output, o), axis=1) @@ -669,8 +676,9 @@ def res_irf(config, path, level_logger='DEBUG'): prices_before=prices_before, carbon_content=carbon_content, carbon_content_before=carbon_content_before, - step=step) - + step=step, + pef_elec=inputs_dynamics['pef_elec'].loc[year]) + df_renovations_final = pd.concat([df_renovations_final, df_renovations], ignore_index=True) merged_df_heater_final = pd.concat([merged_df_heater_final, merged_df_heater], ignore_index=True) df_final_grouped_final = pd.concat([df_final_grouped_final, df_final_grouped], ignore_index=True) @@ -684,7 +692,8 @@ def res_irf(config, path, level_logger='DEBUG'): buildings.make_static_analysis(inputs_dynamics['cost_insulation'], inputs_dynamics['cost_heater'], prices, 0.05, 0.05, inputs_dynamics['post_inputs']['health_cost_dpe'], inputs_dynamics['post_inputs']['carbon_emission'].loc[year, :], - carbon_value=50) + carbon_value=50, + pef_elec=inputs_dynamics['pef_elec'].loc[year]) with open(os.path.join(buildings.path_calibration, 'calibration.pkl'), 'wb') as file: dump({ @@ -749,11 +758,12 @@ def calibration_res_irf(path, config=None, level_logger='DEBUG'): buildings.calibration_consumption(energy_prices.loc[buildings.first_year, :], inputs_dynamics['consumption_ini'], inputs_dynamics['health_cost_income'], - inputs_dynamics['health_cost_dpe'] + inputs_dynamics['health_cost_dpe'], + pef_elec=inputs_dynamics['pef_elec'].loc[buildings.first_year] ) output = pd.DataFrame() - _, o, df_renovations, merged_df_heater = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs']) + _, o, df_renovations, merged_df_heater = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs'], pef_elec=inputs_dynamics['pef_elec'].loc[buildings.first_year]) output = pd.concat((output, o), axis=1) year = buildings.first_year + 1 From 34c67c2d1d9379578b2bc68deb63b0a9ae760fd8 Mon Sep 17 00:00:00 2001 From: VictorMannoni Date: Thu, 11 Sep 2025 17:46:11 +0200 Subject: [PATCH 26/31] feat(buildings): add and propagate pef_elec Add self.pef_elec attribute in ThermalBuilding. Add pef_elec=None argument to functions. Update function calls to propagate pef_elec. Add reset_consumption_store method to reinitialize consumption and certificate caches. --- project/building.py | 284 ++++++++++++++++++++++++++------------------ 1 file changed, 167 insertions(+), 117 deletions(-) diff --git a/project/building.py b/project/building.py index 3109f271..78741e48 100644 --- a/project/building.py +++ b/project/building.py @@ -75,7 +75,7 @@ class ThermalBuildings: """ def __init__(self, stock, surface, ratio_surface, efficiency, income, path=None, year=2018, - resources_data=None, detailed_output=None, figures=None, residual_rate=0, temp_sink=None): + resources_data=None, detailed_output=None, figures=None, residual_rate=0, temp_sink=None, pef_elec=None): # default values self.hi_threshold = None @@ -145,6 +145,7 @@ def __init__(self, stock, surface, ratio_surface, efficiency, income, path=None, 'certificate_renovation': Series(dtype='float'), } + self.pef_elec = pef_elec self.stock = stock if self.path_ini is not None: stock = self.add_certificate(stock).groupby('Performance').sum() / 10 ** 6 @@ -169,6 +170,14 @@ def year(self, year): if self._temp_sink_yrs is not None: self._temp_sink = self._temp_sink_yrs.loc[year] + @property + def pef_elec(self): + return self._pef_elec + + @pef_elec.setter + def pef_elec(self, values): + self._pef_elec = values + @property def stock(self): return self._stock @@ -196,8 +205,8 @@ def stock(self, stock): self.energy = self.to_energy(stock).astype('category') self._resources_data['index']['Energy'] = [i for i in self._resources_data['index']['Energy'] if i in self.energy.unique()] - - consumption_sd, _, certificate = self.consumption_heating_store(stock.index) + pef_elec = self.pef_elec.loc[self.year] + consumption_sd, _, certificate = self.consumption_heating_store(stock.index, pef_elec=pef_elec) self.certificate = reindex_mi(certificate, stock.index).astype('category') @property @@ -328,9 +337,21 @@ def size_heater(self, index=None): return size_heating_system / 1e3 + def reset_consumption_store(self): + """Réinitialise le cache des consommations / certificats.""" + import pandas as pd + self._consumption_store = { + 'consumption': pd.Series(dtype='float'), + 'consumption_3uses': pd.Series(dtype='float'), + 'certificate': pd.Series(dtype='float'), + 'consumption_renovation': pd.Series(dtype='float'), + 'consumption_3uses_renovation': pd.Series(dtype='float'), + 'certificate_renovation': pd.Series(dtype='float'), + } + def consumption_heating(self, index=None, freq='year', climate=None, smooth=False, full_output=False, efficiency_hour=False, level_heater='Heating system', - method='5uses', hourly_profile=None, temp_sink=None): + method='5uses', hourly_profile=None, temp_sink=None, pef_elec=None): """Calculation consumption standard of the current building stock [kWh/m2.a]. Parameters @@ -375,7 +396,9 @@ def consumption_heating(self, index=None, freq='year', climate=None, smooth=Fals certificate, consumption_3uses = thermal.conventional_energy_3uses(wall, floor, roof, windows, self._ratio_surface.copy(), efficiency, _index, - method=method) + method=method, + pef_elec=pef_elec + ) certificate = reindex_mi(certificate, index) consumption_3uses = reindex_mi(consumption_3uses, index) @@ -383,7 +406,7 @@ def consumption_heating(self, index=None, freq='year', climate=None, smooth=Fals else: return consumption - def consumption_heating_store(self, index, level_heater='Heating system', full_output=True): + def consumption_heating_store(self, index, level_heater='Heating system', full_output=True, pef_elec=None): """Pre-calculate space energy consumption based only on relevant levels. @@ -414,7 +437,7 @@ def consumption_heating_store(self, index, level_heater='Heating system', full_o if not idx.empty: consumption, certificate, consumption_3uses = self.consumption_heating(index=idx, freq='year', climate=None, - full_output=True) + full_output=True, pef_elec=pef_elec) self._consumption_store['consumption'] = concat((self._consumption_store['consumption'], consumption)) self._consumption_store['consumption'].index = MultiIndex.from_tuples( @@ -446,7 +469,7 @@ def consumption_heating_store(self, index, level_heater='Heating system', full_o return consumption_sd def to_heating_intensity(self, index, prices, consumption=None, level_heater='Heating system', bill_rebate=0, - full_output=False): + full_output=False, pef_elec=None): """Calculate heating intensity of index based on energy prices. Parameters @@ -463,7 +486,7 @@ def to_heating_intensity(self, index, prices, consumption=None, level_heater='He Heating intensity """ if consumption is None: - consumption = reindex_mi(self.consumption_heating_store(index, full_output=False), index) * reindex_mi( + consumption = reindex_mi(self.consumption_heating_store(index, full_output=False, pef_elec=pef_elec), index) * reindex_mi( self._surface, index) energy_bill = AgentBuildings.energy_bill(prices, consumption, level_heater=level_heater, bill_rebate=bill_rebate) @@ -482,7 +505,7 @@ def to_heating_intensity(self, index, prices, consumption=None, level_heater='He else: return heating_intensity, budget_share - def consumption_actual(self, prices, consumption=None, full_output=False, bill_rebate=0): + def consumption_actual(self, prices, consumption=None, full_output=False, bill_rebate=0, pef_elec=None): """Space heating consumption based on standard space heating consumption and heating intensity (kWh/building.a). @@ -504,14 +527,15 @@ def consumption_actual(self, prices, consumption=None, full_output=False, bill_r if consumption is None: index = self.stock.index - consumption = self.consumption_heating_store(index, full_output=False) + consumption = self.consumption_heating_store(index, full_output=False, pef_elec=pef_elec) consumption = reindex_mi(consumption, index) * reindex_mi(self._surface, index) else: consumption = consumption.copy() index = consumption.index heating_intensity, budget_share = self.to_heating_intensity(index, prices, consumption=consumption, - full_output=True, bill_rebate=bill_rebate) + full_output=True, bill_rebate=bill_rebate, + pef_elec=pef_elec) consumption = consumption * heating_intensity if full_output is False: @@ -521,7 +545,7 @@ def consumption_actual(self, prices, consumption=None, full_output=False, bill_r def consumption_agg(self, prices=None, freq='year', climate=None, smooth=False, standard=False, efficiency_hour=False, existing=False, agg='all', bill_rebate=0, - hourly_profile=None): + hourly_profile=None, pef_elec=None): """Aggregated final energy consumption (TWh final energy). Parameters @@ -547,7 +571,7 @@ def consumption_agg(self, prices=None, freq='year', climate=None, smooth=False, if standard is True: if freq == 'year': - consumption = self.consumption_heating(freq=freq, climate=None) + consumption = self.consumption_heating(freq=freq, climate=None, pef_elec=pef_elec) consumption = reindex_mi(consumption, self.stock.index) * self.surface * self.stock if existing is True: consumption = consumption[consumption.index.get_level_values('Existing')] @@ -563,11 +587,11 @@ def consumption_agg(self, prices=None, freq='year', climate=None, smooth=False, if standard is False: if freq == 'year': # TODO: if climate is none consumption_heating_store ? - consumption = self.consumption_heating(freq=freq, climate=climate, temp_sink=self._temp_sink) + consumption = self.consumption_heating(freq=freq, climate=climate, temp_sink=self._temp_sink, pef_elec=pef_elec) consumption = reindex_mi(consumption, self.stock.index) * self.surface if existing is True: consumption = consumption[consumption.index.get_level_values('Existing')] - consumption = self.consumption_actual(prices, consumption=consumption, bill_rebate=bill_rebate) * self.stock + consumption = self.consumption_actual(prices, consumption=consumption, bill_rebate=bill_rebate, pef_elec=pef_elec) * self.stock if agg == 'all': consumption = self.apply_calibration(consumption, agg='energy') / 10 ** 9 @@ -580,11 +604,12 @@ def consumption_agg(self, prices=None, freq='year', climate=None, smooth=False, if freq == 'hour': consumption = self.consumption_heating(freq=freq, climate=climate, smooth=smooth, efficiency_hour=efficiency_hour, hourly_profile=hourly_profile, - temp_sink=self._temp_sink) + temp_sink=self._temp_sink, pef_elec=pef_elec) consumption = (reindex_mi(consumption, self.stock.index).T * self.surface).T heating_intensity = self.to_heating_intensity(consumption.index, prices, consumption=consumption.sum(axis=1), - bill_rebate=bill_rebate) + bill_rebate=bill_rebate, + pef_elec=pef_elec) consumption = (consumption.T * heating_intensity * self.stock).T consumption = self.apply_calibration(consumption) return consumption @@ -625,7 +650,7 @@ def apply_calibration(self, consumption, level_heater='Heating system', agg='ene return _consumption_energy - def calibration_consumption(self, prices, consumption_ini, health_cost_income, health_cost_dpe, climate=None): + def calibration_consumption(self, prices, consumption_ini, health_cost_income, health_cost_dpe, climate=None, pef_elec=None): """Calculate energy indicators. Parameters @@ -640,7 +665,7 @@ def calibration_consumption(self, prices, consumption_ini, health_cost_income, h """ if self.coefficient_global is None: - consumption, certificate, consumption_3uses = self.consumption_heating(climate=climate, full_output=True) + consumption, certificate, consumption_3uses = self.consumption_heating(climate=climate, full_output=True, pef_elec=pef_elec) s = self.stock.groupby(consumption.index.names).sum() if self.path_ini is not None: df = concat((consumption_3uses, s), axis=1, keys=['Consumption', 'Stock']) @@ -662,10 +687,11 @@ def calibration_consumption(self, prices, consumption_ini, health_cost_income, h _consumption_actual, heating_intensity, budget_share = self.consumption_actual(prices, consumption=consumption, - full_output=True) + full_output=True, + pef_elec=pef_elec) # calibration health_cost on heating intensity total_health_cost = self.health_cost(health_cost_dpe, health_cost_income, prices, - method_health_cost='epc') + method_health_cost='epc', pef_elec=pef_elec) """_, certificate, _ = self.consumption_heating(method='3uses', full_output=True) temp = concat((heating_intensity, self.stock), axis=1, keys=['Heating intensity', 'Stock']) temp = concat((temp, reindex_mi(certificate, temp.index).rename('Performance')), axis=1) @@ -831,7 +857,7 @@ def energy_bill(prices, consumption, level_heater='Heating system', bill_rebate= # * reindex_mi(self._surface, index) return (reindex_mi(consumption, index).T * prices - bill_rebate).T - def optimal_temperature(self, prices): + def optimal_temperature(self, prices, pef_elec=None): """Find indoor temperature based on energy prices, housing performance and income level. Parameters @@ -844,18 +870,18 @@ def optimal_temperature(self, prices): """ - def func(temp, consumption, index): - consumption_temp = self.consumption_heating(temp_indoor=temp) + def func(temp, consumption, index, pef_elec): + consumption_temp = self.consumption_heating(temp_indoor=temp, pef_elec=pef_elec) consumption_temp = reindex_mi(consumption_temp, index) * self.surface return consumption - consumption_temp - consumption_actual = self.consumption_actual(prices) - consumption_sd = self.consumption_heating(temp_indoor=None) + consumption_actual = self.consumption_actual(prices, pef_elec=pef_elec) + consumption_sd = self.consumption_heating(temp_indoor=None, pef_elec=pef_elec) consumption_sd = reindex_mi(consumption_sd, self.stock.index) * self.surface temp_optimal = {} for i, v in consumption_actual.iteritems(): - temp_optimal.update({i: fsolve(func, 19, args=(consumption_actual.loc[i], i))[0]}) + temp_optimal.update({i: fsolve(func, 19, args=(consumption_actual.loc[i], i, pef_elec))[0]}) temp_optimal = Series(temp_optimal) temp = concat((consumption_actual, consumption_sd, temp_optimal), axis=1, @@ -863,7 +889,7 @@ def func(temp, consumption, index): return temp_optimal - def store_consumption(self, prices, carbon_content, bill_rebate=0): + def store_consumption(self, prices, carbon_content, bill_rebate=0, pef_elec=None): """Store energy consumption. @@ -876,14 +902,14 @@ def store_consumption(self, prices, carbon_content, bill_rebate=0): bill_rebate """ output = dict() - temp = self.consumption_agg(freq='year', standard=True, existing=True, agg='energy') + temp = self.consumption_agg(freq='year', standard=True, existing=True, agg='energy', pef_elec=pef_elec) temp = temp.reindex(prices.index).fillna(0) output.update({'Consumption standard (TWh)': temp.sum()}) temp.index = temp.index.map(lambda x: 'Consumption standard {} (TWh)'.format(x)) output.update(temp) temp = self.consumption_agg(prices=prices, freq='year', standard=False, climate=None, smooth=False, - existing=True, agg='energy', bill_rebate=bill_rebate) + existing=True, agg='energy', bill_rebate=bill_rebate, pef_elec=pef_elec) temp = temp.reindex(prices.index).fillna(0) output.update({'Consumption (TWh)': temp.sum()}) emission = (temp * carbon_content).sum() / 10 ** 3 @@ -954,11 +980,11 @@ def __init__(self, stock, surface, ratio_surface, efficiency, income, preference rational_behavior_insulation=None, rational_behavior_heater=None, resources_data=None, detailed_output=True, figures=None, method_health_cost=None, residual_rate=0, constraint_heat_pumps=True, - variable_size_heater=True, temp_sink=None + variable_size_heater=True, temp_sink=None, pef_elec=None ): super().__init__(stock, surface, ratio_surface, efficiency, income, path=path, year=year, resources_data=resources_data, detailed_output=detailed_output, figures=figures, - residual_rate=residual_rate, temp_sink=temp_sink) + residual_rate=residual_rate, temp_sink=temp_sink, pef_elec=pef_elec) if logger is None: logger = logging.getLogger() @@ -1330,7 +1356,7 @@ def select_deep_renovation(certificate_after): return condition def prepare_consumption(self, choice_insulation=None, performance_insulation=None, index=None, method_epc='5uses', - level_heater='Heating system', full_output=True, store=True, climate=None): + level_heater='Heating system', full_output=True, store=True, climate=None, pef_elec=None): """Standard energy consumption and energy performance certificate for each renovation works option. Standard energy consumption only depends on building characteristics. @@ -1418,10 +1444,12 @@ def prepare_consumption(self, choice_insulation=None, performance_insulation=Non if climate is not None or method_epc == '3uses': consumption, certificate, consumption_3uses = self.consumption_heating(index=index, climate=climate, level_heater='Heating system', - method=method_epc, full_output=True) + method=method_epc, full_output=True, + pef_elec=pef_elec) else: consumption, consumption_3uses, certificate = self.consumption_heating_store(index, - level_heater='Heating system') + level_heater='Heating system', + pef_elec=pef_elec) rslt = dict() @@ -2001,7 +2029,7 @@ def calibration(x, _ms, _utility_ini, _flow, _idx, _ref, _u_shock, _target, return market_share, error_conditional - def exogenous_market_share_heater(self, index, choice_heater_idx): + def exogenous_market_share_heater(self, index, choice_heater_idx, pef_elec=None): """Define exogenous market-share. Market-share is defined by _market_share_exogenous attribute. @@ -2038,9 +2066,9 @@ def exogenous_market_share_heater(self, index, choice_heater_idx): temp = Series(0, index=index, dtype='float').to_frame().dot( Series(0, index=choice_heater_idx, dtype='float').to_frame().T) index_final = temp.stack().index - _, _, certificate = self.consumption_heating_store(index_final, level_heater='Heating system final') + _, _, certificate = self.consumption_heating_store(index_final, level_heater='Heating system final', pef_elec=pef_elec) certificate = reindex_mi(certificate.unstack('Heating system final'), index) - certificate_before = self.consumption_heating_store(index)[2] + certificate_before = self.consumption_heating_store(index, pef_elec=pef_elec)[2] certificate_before = reindex_mi(certificate_before, index) self._heater_store['epc_upgrade'] = - certificate.replace(EPC2INT).sub( @@ -2170,7 +2198,7 @@ def store_information_heater(self, cost_heater, subsidies_total, bill_saved, sub def heater_replacement(self, stock, prices, cost_heater, policies_heater, calib_heater=None, step=1, financing_cost=None, district_heating=None, premature_replacement=None, prices_before=None, supply=None, store_information=True, bill_rebate=0, - carbon_content=None, carbon_value=None): + carbon_content=None, carbon_value=None, pef_elec=None): """Function returns building stock updated after switching heating system. @@ -2264,7 +2292,7 @@ def apply_rational_choice(_consumption_saved, _subsidies_total, _cost_total, _bi condition.columns.names = ['Heating system final'] if self._constraint_heat_pumps: if isinstance(self._constraint_heat_pumps, list): - _, certificate, _ = self.consumption_heating(method='3uses', full_output=True) + _, certificate, _ = self.consumption_heating(method='3uses', full_output=True, pef_elec=pef_elec) condition = concat((condition, reindex_mi(certificate.rename('Performance'), condition.index)), axis=1) condition = condition.set_index('Performance', append=True) idx = (condition.index.get_level_values('Performance').isin(['F', 'G'])) & ( @@ -2309,12 +2337,12 @@ def apply_rational_choice(_consumption_saved, _subsidies_total, _cost_total, _bi temp = Series(0, index=index, dtype='float').to_frame().dot(Series(0, index=choice_heater_idx, dtype='float').to_frame().T) index_final = temp.stack().index - consumption, _, certificate = self.consumption_heating_store(index_final, level_heater='Heating system final') + consumption, _, certificate = self.consumption_heating_store(index_final, level_heater='Heating system final', pef_elec=pef_elec) consumption = reindex_mi(consumption.unstack('Heating system final'), index) prices_re = prices.reindex(energy).set_axis(consumption.columns) bill = ((consumption * prices_re).T * reindex_mi(self._surface, index)).T - consumption_before = self.consumption_heating_store(index, level_heater='Heating system')[0] + consumption_before = self.consumption_heating_store(index, level_heater='Heating system', pef_elec=pef_elec)[0] consumption_before = reindex_mi(consumption_before, index) * reindex_mi(self._surface, index) emission_before = AgentBuildings.energy_bill(carbon_content, consumption_before) bill_before = AgentBuildings.energy_bill(prices, consumption_before) @@ -2322,7 +2350,7 @@ def apply_rational_choice(_consumption_saved, _subsidies_total, _cost_total, _bi bill_saved = - bill.sub(bill_before, axis=0) certificate = reindex_mi(certificate.unstack('Heating system final'), index) - certificate_before = self.consumption_heating_store(index)[2] + certificate_before = self.consumption_heating_store(index, pef_elec=pef_elec)[2] certificate_before = reindex_mi(certificate_before, index) consumption = (reindex_mi(self._surface, consumption.index) * consumption.T).T @@ -2362,7 +2390,8 @@ def apply_rational_choice(_consumption_saved, _subsidies_total, _cost_total, _bi heating_intensity_before = self.to_heating_intensity(consumption_before.index, prices_before, consumption=consumption_before, level_heater='Heating system', - bill_rebate=bill_rebate) + bill_rebate=bill_rebate, + pef_elec=pef_elec) consumption_before *= heating_intensity_before consumption = self.add_attribute(consumption, 'Income tenant') @@ -2372,7 +2401,8 @@ def apply_rational_choice(_consumption_saved, _subsidies_total, _cost_total, _bi heating_intensity_after = self.to_heating_intensity(consumption.index, prices_before, consumption=consumption, level_heater='Heating system final', - bill_rebate=bill_rebate) + bill_rebate=bill_rebate, + pef_elec=pef_elec) consumption_actual = (consumption * heating_intensity_after).unstack('Heating system final') consumption_no_rebound = (consumption.unstack('Heating system final').T * heating_intensity_before).T @@ -2415,7 +2445,7 @@ def apply_rational_choice(_consumption_saved, _subsidies_total, _cost_total, _bi discount_social=0.032) else: - market_share = self.exogenous_market_share_heater(index, cost_heater.columns) + market_share = self.exogenous_market_share_heater(index, cost_heater.columns, pef_elec=pef_elec) assert (market_share.sum(axis=1).round(0) == 1).all(), 'Market-share issue' @@ -3046,7 +3076,8 @@ def apply_regulation(idx_target, idx_replace, level): def endogenous_renovation(self, stock, prices, subsidies_total, cost_insulation, lifetime, calib_renovation=None, min_performance=None, subsidies_details=None, cost_financing=None, supply=None, discount=None, - carbon_value=None, credit_constraint=None, performance_gap=1): + carbon_value=None, credit_constraint=None, performance_gap=1, + pef_elec=None): """Calculate endogenous retrofit based on discrete choice model. @@ -3814,12 +3845,12 @@ def indicator_renovation_rate(_stock, _cost_insulation, _bill_saved, _subsidies_ proba_replacement = 1 / lifetime - consumption_before = self.consumption_heating_store(index, level_heater='Heating system final')[0] + consumption_before = self.consumption_heating_store(index, level_heater='Heating system final', pef_elec=pef_elec)[0] consumption_before = reindex_mi(consumption_before, index) * reindex_mi(self._surface, index) energy_bill_before = AgentBuildings.energy_bill(prices, consumption_before, level_heater='Heating system final') consumption_after = self.prepare_consumption(self._choice_insulation, index=index, - level_heater='Heating system final', full_output=False) + level_heater='Heating system final', full_output=False, pef_elec=pef_elec) consumption_after = reindex_mi(consumption_after, index).reindex(self._choice_insulation, axis=1) consumption_after = (consumption_after.T * reindex_mi(self._surface, index)).T consumption_saved = (consumption_before - consumption_after.T).T @@ -4284,7 +4315,7 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim calib_renovation=None, min_performance=None, exogenous_social=None, prices_before=None, supply=None, carbon_value=None, carbon_content=None, calculate_condition=True, bill_rebate=0, - credit_constraint=True, call_from_obligation=False): + credit_constraint=True, call_from_obligation=False, pef_elec=None): """Calculate insulation retrofit in the dwelling stock. 1. Intensive margin @@ -4329,16 +4360,17 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim if not stock.empty: # select index that can undertake insulation replacement - _, consumption_3uses_before_heater, certificate_before_heater = self.consumption_heating_store(index, level_heater='Heating system') + _, consumption_3uses_before_heater, certificate_before_heater = self.consumption_heating_store(index, level_heater='Heating system', pef_elec=pef_elec) # before include the change of heating system - consumption_before, consumption_3uses_before, certificate_before = self.consumption_heating_store(index, level_heater='Heating system final') + consumption_before, consumption_3uses_before, certificate_before = self.consumption_heating_store(index, level_heater='Heating system final', pef_elec=pef_elec) surface = reindex_mi(self._surface, index) # calculation of energy_saved_3uses after heating system final consumption_after, consumption_3uses, certificate_after = self.prepare_consumption(self._choice_insulation, index=index, - level_heater='Heating system final') + level_heater='Heating system final', + pef_elec=pef_elec) energy_saved_3uses = ((consumption_3uses_before_heater - consumption_3uses.T) / consumption_3uses_before_heater).T energy_saved_3uses.dropna(inplace=True) @@ -4400,7 +4432,8 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim credit_constraint=credit_constraint, performance_gap=round( self._heating_intensity_avg, - 1)) + 1), + pef_elec=pef_elec) if exogenous_social is not None: index = renovation_rate[ @@ -4505,7 +4538,8 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim heating_intensity = self.to_heating_intensity(consumption_before.index, prices, consumption=consumption_before, level_heater='Heating system final', - bill_rebate=bill_rebate) + bill_rebate=bill_rebate, + pef_elec=pef_elec) consumption_before *= heating_intensity @@ -4516,7 +4550,8 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim heating_intensity_after = self.to_heating_intensity(consumption_after.index, prices_before, consumption=consumption_after, level_heater='Heating system final', - bill_rebate=bill_rebate) + bill_rebate=bill_rebate, + pef_elec=pef_elec) consumption_saved_actual = (consumption_before - (consumption_after * heating_intensity_after).T).T consumption_saved_no_rebound = (consumption_before - consumption_after.T * heating_intensity).T @@ -4536,12 +4571,14 @@ def insulation_replacement(self, stock_ini, prices, cost_insulation_raw, lifetim return renovation_rate, market_share - def certificate_flow_heater(self): + def certificate_flow_heater(self, pef_elec=None): """ Calculates the flow for each possible pair of certificates for those who change their heater but do not renovate. Parameters ---------- - + pef_elec : float + Primary energy factor for electricity + Returns ------- @@ -4551,8 +4588,8 @@ def certificate_flow_heater(self): # Creation of 3 Series : flow of heater replacement, certificates before and after heater replacement flow = self._only_heater index = flow.index - _, _, certificate_before_heater = self.consumption_heating_store(index, level_heater='Heating system') - _, _, certificate_after_heater = self.consumption_heating_store(index, level_heater='Heating system final') + _, _, certificate_before_heater = self.consumption_heating_store(index, level_heater='Heating system', pef_elec=pef_elec) + _, _, certificate_after_heater = self.consumption_heating_store(index, level_heater='Heating system final', pef_elec=pef_elec) # Merging flow of heater replacement only with certificates before and after heater replacement flow = flow.rename('Flow') @@ -4589,7 +4626,7 @@ def flow_retrofit(self, prices, cost_heater, cost_insulation, lifetime_insulatio policies_heater=None, policies_insulation=None, calib_heater=None, district_heating=None, financing_cost=None, calib_renovation=None, step=1, exogenous_social=None, premature_replacement=None, prices_before=None, supply=None, - carbon_value_kwh=None, carbon_value=None, bill_rebate=0, carbon_content=None): + carbon_value_kwh=None, carbon_value=None, bill_rebate=0, carbon_content=None, pef_elec=None): """Compute heater replacement and insulation retrofit. @@ -4624,7 +4661,7 @@ def flow_retrofit(self, prices, cost_heater, cost_insulation, lifetime_insulatio # calculate average heating intensity temp = concat((self.stock_mobile, - self.to_heating_intensity(self.stock_mobile.index, prices, level_heater='Heating system')), + self.to_heating_intensity(self.stock_mobile.index, prices, level_heater='Heating system', pef_elec=pef_elec)), axis=1, keys=['Stock', 'Heating intensity']) self._heating_intensity_avg = temp['Stock'].mul(temp['Heating intensity']).sum() / temp['Stock'].sum() @@ -4634,7 +4671,8 @@ def flow_retrofit(self, prices, cost_heater, cost_insulation, lifetime_insulatio calib_heater=calib_heater, step=1, financing_cost=financing_cost, district_heating=district_heating, premature_replacement=premature_replacement, prices_before=prices_before, bill_rebate=bill_rebate, - carbon_content=carbon_content, carbon_value=carbon_value) + carbon_content=carbon_content, carbon_value=carbon_value, + pef_elec=pef_elec) if supply is not None: if supply['heater']: @@ -4661,13 +4699,14 @@ def func(x, index): demand_ini = Series(root[index.shape[0]:], index=index) self.cost_curve_heater = (alpha, demand_ini) - def supply_demand_heater_equilibrium(_prices_heater, _prices_index): + def supply_demand_heater_equilibrium(_prices_heater, _prices_index, pef_elec): _prices_heater = pd.Series(_prices_heater, index=_prices_index) _stock = self.heater_replacement(stock_mobile, prices, _prices_heater, policies_heater, financing_cost=financing_cost, district_heating=district_heating, premature_replacement=premature_replacement, + pef_elec=pef_elec ) demand = stock.xs(True, level='Heater replacement').groupby('Heating system final').sum() @@ -4681,7 +4720,7 @@ def supply_demand_heater_equilibrium(_prices_heater, _prices_index): stock = self.heater_replacement(stock_mobile, prices, prices_heater, policies_heater, calib_heater=calib_heater, step=1, financing_cost=financing_cost, district_heating=district_heating, premature_replacement=premature_replacement, - prices_before=prices_before) + prices_before=prices_before, pef_elec=pef_elec) assert ~stock.index.duplicated().any(), 'Duplicated index after heater replacement' self.logger.info('Number of agents that can insulate: {:,.0f}'.format(stock.shape[0])) @@ -4700,7 +4739,8 @@ def supply_demand_heater_equilibrium(_prices_heater, _prices_index): supply=supply_insulation, carbon_value=carbon_value_kwh, carbon_content=carbon_content, - bill_rebate=bill_rebate) + bill_rebate=bill_rebate, + pef_elec=pef_elec) cost_curve_insulation = False if cost_curve_insulation: @@ -4721,7 +4761,8 @@ def supply_demand_insulation_equilibrium(_prices_insulation): policies_insulation=policies_insulation, financing_cost=financing_cost, exogenous_social=exogenous_social, - calculate_condition=True) + calculate_condition=True, + pef_elec=pef_elec) _demand = (stock * _renovation_rate * _market_share.T).T.sum() _demand = pd.Series({i: _demand.xs(True, level=i).sum() / 10 ** 3 for i in _demand.index.names}) @@ -4738,7 +4779,8 @@ def supply_demand_insulation_equilibrium(_prices_insulation): exogenous_social=exogenous_social, prices_before=prices_before, supply=supply['insulation'], - carbon_value=carbon_value) + carbon_value=carbon_value, + pef_elec=pef_elec) self.logger.info('Formatting and storing replacement') renovation_rate = renovation_rate.reindex(stock.index).fillna(0) @@ -4749,7 +4791,7 @@ def supply_demand_insulation_equilibrium(_prices_insulation): # approximation stock = self.heater_replacement(stock_mobile, prices, cost_heater, policies_heater, calib_heater=calib_heater, step=step, financing_cost=financing_cost, - district_heating=district_heating, supply=supply) + district_heating=district_heating, supply=supply, pef_elec=pef_elec) flow_insulation = flow_insulation.where(flow_insulation < stock, stock) flow_only_heater = stock - flow_insulation @@ -4773,7 +4815,7 @@ def supply_demand_insulation_equilibrium(_prices_insulation): self.logger.debug('Store information retrofit') self._replaced_by = replaced_by.copy() self._only_heater = only_heater.copy() - self.certificate_flow_heater() + self.certificate_flow_heater(pef_elec=pef_elec) # removing heater replacement level replaced_by = replaced_by.groupby( @@ -4820,7 +4862,7 @@ def supply_demand_insulation_equilibrium(_prices_insulation): return flow_retrofit - def flow_obligation(self, policies_insulation, prices, cost_insulation, financing_cost=True): + def flow_obligation(self, policies_insulation, prices, cost_insulation, financing_cost=True, pef_elec=None): """Account for flow obligation if defined in policies_insulation. Parameters @@ -4910,7 +4952,8 @@ def flow_obligation(self, policies_insulation, prices, cost_insulation, financin policies_insulation=policies_insulation, financing_cost=financing_cost, min_performance=obligation.min_performance, - credit_constraint=False, call_from_obligation=True) + credit_constraint=False, call_from_obligation=True, + pef_elec=pef_elec) if obligation.intensive == 'market_share': # market_share endogenously calculated by insulation_replacement @@ -4943,7 +4986,7 @@ def flow_obligation(self, policies_insulation, prices, cost_insulation, financin return flows_obligation def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, - lifetime_insulation=30, social_discount_rate=0.032, bill_rebate=0): + lifetime_insulation=30, social_discount_rate=0.032, bill_rebate=0, pef_elec=None): """Parse output. Renovation : envelope @@ -5008,31 +5051,32 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, output.update(temp.T) output['Consumption standard (TWh)'] = self.consumption_agg(prices=prices, freq='year', climate=climate, - standard=True, agg='all') + standard=True, agg='all', pef_elec=pef_elec) output['Consumption standard (kWh/m2)'] = (output['Consumption standard (TWh)'] * 10 ** 9) / ( output['Surface (Million m2)'] * 10 ** 6) consumption_energy = self.consumption_agg(prices=prices, freq='year', climate=None, standard=False, - agg='energy', bill_rebate=bill_rebate) + agg='energy', bill_rebate=bill_rebate, pef_elec=pef_elec) output['Consumption (TWh)'] = consumption_energy.sum() self.store_over_years[self.year].update({'Consumption (TWh)': output['Consumption (TWh)']}) output['Consumption (kWh/m2)'] = (output['Consumption (TWh)'] * 10 ** 9) / ( output['Surface (Million m2)'] * 10 ** 6) output['Consumption existing (TWh)'] = self.consumption_agg(prices=prices, freq='year', existing=True, - agg='all', bill_rebate=bill_rebate) + agg='all', bill_rebate=bill_rebate, pef_elec=pef_elec) output['Consumption new (TWh)'] = output['Consumption (TWh)'] - output['Consumption existing (TWh)'] output['Consumption existing (kWh/m2)'] = (output['Consumption existing (TWh)'] * 10 ** 9) / ( output['Surface existing (Million m2)'] * 10 ** 6) - output['Consumption PE (TWh)'] = thermal.final2primary(consumption_energy, Series(consumption_energy.index, index=consumption_energy.index)).sum() + output['Consumption PE (TWh)'] = thermal.final2primary(consumption_energy, Series(consumption_energy.index, index=consumption_energy.index), pef_elec=pef_elec).sum() if surface_new > 0: output['Consumption new (kWh/m2)'] = (output['Consumption new (TWh)'] * 10 ** 9) / ( output['Surface new (Million m2)'] * 10 ** 6) heating_intensity, budget_share = self.to_heating_intensity(self.stock.index, prices, - full_output=True, bill_rebate=bill_rebate) + full_output=True, bill_rebate=bill_rebate, + pef_elec=pef_elec) condition_poverty = self.stock.index.get_level_values('Income tenant').isin(['D1', 'D2', 'D3', 'C1', 'C2']) & ( budget_share >= 0.08) @@ -5046,7 +5090,7 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, output.update(temp.T) temp = self.consumption_agg(prices=prices, freq='year', climate=None, standard=False, - agg='heater', bill_rebate=bill_rebate).dropna() + agg='heater', bill_rebate=bill_rebate, pef_elec=pef_elec).dropna() consumption_hp = sum([temp.loc[i] for i in self._resources_data['index']['Heat pumps'] if i in temp.index]) temp.index = temp.index.map(lambda x: 'Consumption {} (TWh)'.format(x)) output.update(temp.T) @@ -5058,7 +5102,7 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, consumption_energy_climate = None if climate is not None: consumption_energy_climate = self.consumption_agg(prices=prices, freq='year', climate=climate, - standard=False, agg='energy', bill_rebate=bill_rebate) + standard=False, agg='energy', bill_rebate=bill_rebate, pef_elec=pef_elec) output['Consumption climate (TWh)'] = consumption_energy_climate.sum() temp = consumption_energy_climate.copy() temp.index = temp.index.map(lambda x: 'Consumption {} climate (TWh)'.format(x)) @@ -5067,7 +5111,7 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, if False: consumption_hourly = self.consumption_agg(prices=prices, freq='hour', standard=False, climate=2006, - efficiency_hour=True, hourly_profile='power') + efficiency_hour=True, hourly_profile='power', pef_elec=pef_elec) # format_x datetime hourly temp = consumption_hourly.loc['Electricity'] @@ -5082,7 +5126,7 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, save=os.path.join(self.path, 'consumption_day.png'), format_y=lambda y, _: '{:.0f}'.format(y / 1e6), integer=False, legend=False) - consumption = self.consumption_actual(prices) * self.stock + consumption = self.consumption_actual(prices, pef_elec=pef_elec) * self.stock consumption_calib = consumption * self.coefficient_global # correct that do consider secondary heating system temp = consumption_calib.groupby('Existing').sum() @@ -5094,7 +5138,7 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, temp.index = temp.index.map(lambda x: 'Consumption {} (TWh)'.format(x)) output.update(temp.T / 10 ** 9) - temp = self.consumption_agg(agg='heater', standard=True, freq='year') + temp = self.consumption_agg(agg='heater', standard=True, freq='year', pef_elec=pef_elec) temp.index = temp.index.map(lambda x: 'Consumption standard {} (TWh)'.format(x)) output.update(temp.T) @@ -5151,14 +5195,14 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, ####################### Adding consumption standard real ###################################################### ############################################################################################################### - consumption_std = self.consumption_heating(freq="year", climate=None) + consumption_std = self.consumption_heating(freq="year", climate=None, pef_elec=pef_elec) consumption_std_2 = reindex_mi(consumption_std, self.stock.index) * self.surface * self.stock consumption_std_3 = consumption_std_2.reset_index() consumption_std_3.rename(columns={0: "consumption_standard"}) - consumption_real = self.consumption_heating(freq="year", climate=climate, temp_sink=self._temp_sink) + consumption_real = self.consumption_heating(freq="year", climate=climate, temp_sink=self._temp_sink, pef_elec=pef_elec) consumption_real_2 = reindex_mi(consumption_real, self.stock.index) * self.surface - consumption_real_3 = self.consumption_actual(prices, consumption=consumption_real_2, bill_rebate=bill_rebate) * self.stock + consumption_real_3 = self.consumption_actual(prices, consumption=consumption_real_2, bill_rebate=bill_rebate, pef_elec=pef_elec) * self.stock consumption_real_4 = consumption_real_3.reset_index() consumption_real_4.rename(columns={0: "consumption_real"}, inplace=True) @@ -5307,7 +5351,7 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, output['Taxes expenditure (Billion euro)'] = taxes_expenditures.sum() / step output['Carbon value (Billion euro)'] = (consumption_energy * carbon_value_kwh).sum() - output['Health cost (Billion euro)'] = self.health_cost(inputs['health_cost_dpe'], inputs['health_cost_income'], prices) + output['Health cost (Billion euro)'] = self.health_cost(inputs['health_cost_dpe'], inputs['health_cost_income'], prices, pef_elec=pef_elec) output['Health expenditure (Billion euro)'] = 0 # temp['Health expenditure (Billion euro)'] self.store_over_years[self.year].update({'Health cost (Billion euro)': output['Health cost (Billion euro)']}) @@ -5334,7 +5378,7 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, cost = reindex_mi(cost, replacement.index) consumption_before, certificate_before, _ = self.consumption_heating(index=replacement.index, method='3uses', - full_output=True) + full_output=True, pef_elec=pef_elec) s = concat([Series(index=replacement.index, dtype=float)] * len(replacement.columns), axis=1).set_axis(replacement.columns, axis=1) # choice_insulation = choice_insulation.drop(no_insulation) # only for @@ -5352,7 +5396,7 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, {'Housing type': 'string', 'Wall': 'float', 'Floor': 'float', 'Roof': 'float', 'Windows': 'float', 'Heating system': 'string'}) index = MultiIndex.from_frame(temp) - consumption_after, certificate_after, _ = self.consumption_heating(index=index, method='3uses', full_output=True) + consumption_after, certificate_after, _ = self.consumption_heating(index=index, method='3uses', full_output=True, pef_elec=pef_elec) certificate_after = reindex_mi(certificate_after, index).droplevel(['Wall', 'Floor', 'Roof', 'Windows']).unstack( ['{} bool'.format(i) for i in ['Wall', 'Floor', 'Roof', 'Windows']]) @@ -5410,7 +5454,7 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, # consumption saving if self.consumption_before_retrofit is not None: consumption_before_retrofit = self.consumption_before_retrofit - consumption_after_retrofit = self.store_consumption(prices, emission, bill_rebate=bill_rebate) + consumption_after_retrofit = self.store_consumption(prices, emission, bill_rebate=bill_rebate, pef_elec=pef_elec) temp = {'{} saving (TWh/year)'.format(k.split(' (TWh)')[0]): consumption_before_retrofit[k] - consumption_after_retrofit[k] for k in consumption_before_retrofit.keys() if 'TWh' in k} @@ -5456,7 +5500,8 @@ def parse_output_run(self, prices, inputs, climate=None, step=1, taxes=None, {'Emission saving insulation (MtCO2/year)': self.to_emission(temp, emission).sum() / 10 ** 12}) consumption = self.consumption_heating_store(self._renovation_store['consumption_saved_households'].index, - full_output=False, level_heater='Heating system final') + full_output=False, level_heater='Heating system final', + pef_elec=pef_elec) consumption = reindex_mi(consumption, self._renovation_store['consumption_saved_households'].index) consumption *= reindex_mi(self._surface, consumption.index) @@ -5588,7 +5633,7 @@ def condition_decarbonizing(x): temp.index = temp.index.map(lambda x: 'Rate Single-family - Owner-occupied {} (%)'.format(x)) output.update(temp.T) - _, _, certificate = self.consumption_heating_store(self._stock_ref.index) + _, _, certificate = self.consumption_heating_store(self._stock_ref.index, pef_elec=pef_elec) temp = concat((self._replaced_by, reindex_mi(certificate.rename('Performance'), self._replaced_by.index)), axis=1) temp = temp.set_index('Performance', append=True).set_axis(self._replaced_by.columns, axis=1) s = concat((self._stock_ref, reindex_mi(certificate.rename('Performance'), self._stock_ref.index)), axis=1) @@ -5900,13 +5945,13 @@ def condition_decarbonizing(x): annuities_cumulated = sum([self.expenditure_store[y]['annuities'] for y in years]) annuities_cumulated += annuities_year - consumption_std = reindex_mi(self.consumption_heating(full_output=False), self.stock.index) + consumption_std = reindex_mi(self.consumption_heating(full_output=False, pef_elec=pef_elec), self.stock.index) consumption_std *= reindex_mi(self._surface, self.stock.index) energy_exp_std = self.energy_bill(prices, consumption_std, bill_rebate=0) energy_exp_std *= self.stock energy_exp_std = energy_exp_std.groupby(lvls).sum() - consumption = self.consumption_actual(prices, bill_rebate=bill_rebate) + consumption = self.consumption_actual(prices, bill_rebate=bill_rebate, pef_elec=pef_elec) energy_exp = self.energy_bill(prices, consumption, bill_rebate=bill_rebate) energy_exp *= self.stock energy_exp = energy_exp.groupby(lvls).sum() @@ -6468,13 +6513,13 @@ def condition_decarbonizing(x): return stock, output, df_renovations_total, merged_df_heater, df_final_grouped - def parse_output_run_cba(self, prices, inputs, step=1, taxes=None, bill_rebate=0): + def parse_output_run_cba(self, prices, inputs, step=1, taxes=None, bill_rebate=0, pef_elec=None): output = dict() # emission emission = inputs['carbon_emission'].loc[self.year, :] consumption_energy = self.consumption_agg(prices=prices, freq='year', climate=None, standard=False, agg='energy', - bill_rebate=bill_rebate) + bill_rebate=bill_rebate, pef_elec=pef_elec) temp = consumption_energy * emission output['Emission (MtCO2)'] = temp.sum() / 10 ** 3 @@ -6490,7 +6535,7 @@ def parse_output_run_cba(self, prices, inputs, step=1, taxes=None, bill_rebate=0 output['VAT heater (Billion euro)'] = self._heater_store['vat'] / 10 ** 9 / step output['Investment heater WT (Billion euro)'] = investment_heater - output['VAT heater (Billion euro)'] - output['Health cost (Billion euro)'] = self.health_cost(inputs['health_cost_dpe'], inputs['health_cost_income'], prices) + output['Health cost (Billion euro)'] = self.health_cost(inputs['health_cost_dpe'], inputs['health_cost_income'], prices, pef_elec=inputs['pef_elec'].loc[self.year]) output['VAT (Billion euro)'] = output['VAT insulation (Billion euro)'] + output['VAT heater (Billion euro)'] output['Health expenditure (Billion euro)'] = 0 # temp['Health expenditure (Billion euro)'] @@ -6501,7 +6546,7 @@ def parse_output_run_cba(self, prices, inputs, step=1, taxes=None, bill_rebate=0 if taxes is not None: consumption_energy = self.consumption_agg(prices=prices, freq='year', climate=None, standard=False, - agg='energy', bill_rebate=bill_rebate) + agg='energy', bill_rebate=bill_rebate, pef_elec=inputs['pef_elec'].loc[self.year]) taxes_expenditures = dict() total_taxes = Series(0, index=prices.index) @@ -6558,9 +6603,9 @@ def parse_output_run_cba(self, prices, inputs, step=1, taxes=None, bill_rebate=0 return output - def parse_output_consumption(self, prices, bill_rebate=0): + def parse_output_consumption(self, prices, bill_rebate=0, pef_elec=None): output = self.consumption_agg(prices=prices, freq='year', climate=None, standard=False, agg='energy', - bill_rebate=bill_rebate) + bill_rebate=bill_rebate, pef_elec=pef_elec) output.index = output.index.map(lambda x: 'Consumption {} (TWh)'.format(x)) temp = prices.T temp.index = temp.index.map(lambda x: 'Prices {} (euro/kWh)'.format(x)) @@ -6647,7 +6692,7 @@ def calculate_indicators_insulation(): def calibration_exogenous(self, coefficient_global=None, coefficient_heater=None, constant_heater=None, scale_heater=None, constant_insulation_intensive=None, constant_insulation_extensive=None, scale_insulation=None, energy_prices=None, rational_hidden_cost=None, - number_firms_insulation=None, number_firms_heater=None, hi_threshold=None): + number_firms_insulation=None, number_firms_heater=None, hi_threshold=None, pef_elec=None): """Function calibrating buildings object with exogenous data. @@ -6667,7 +6712,7 @@ def calibration_exogenous(self, coefficient_global=None, coefficient_heater=None # calibration energy consumption first year if (coefficient_global is None) and (energy_prices is not None): - self.calibration_consumption(energy_prices.loc[self.first_year, :], None) + self.calibration_consumption(energy_prices.loc[self.first_year, :], None, pef_elec=pef_elec.loc[self.first_year]) else: self.coefficient_global = coefficient_global self.coefficient_heater = coefficient_heater @@ -6735,7 +6780,7 @@ def flow_demolition(self, demolition_rate, step=1): flow_demolition = (stock_demolition * demolition_total).dropna() return flow_demolition.reorder_levels(self.stock.index.names) - def health_cost(self, health_cost_dpe, health_cost_income, prices, stock=None, method_health_cost=None): + def health_cost(self, health_cost_dpe, health_cost_income, prices, stock=None, method_health_cost=None, pef_elec=None): if method_health_cost is None: method_health_cost = self.method_health_cost @@ -6744,7 +6789,7 @@ def health_cost(self, health_cost_dpe, health_cost_income, prices, stock=None, m stock = self.stock if method_health_cost == 'epc': - _, certificate, _ = self.consumption_heating(method='3uses', full_output=True) + _, certificate, _ = self.consumption_heating(method='3uses', full_output=True, pef_elec=pef_elec) temp = concat((stock, reindex_mi(certificate, stock.index).rename('Performance')), axis=1) temp.set_index('Performance', append=True, inplace=True) temp = temp.squeeze() @@ -6752,7 +6797,7 @@ def health_cost(self, health_cost_dpe, health_cost_income, prices, stock=None, m elif method_health_cost == 'heating_intensity': - heating_intensity = self.to_heating_intensity(stock.index, prices) + heating_intensity = self.to_heating_intensity(stock.index, prices, pef_elec=pef_elec) stock = concat((stock, heating_intensity), axis=1, keys=['Stock', 'Heating intensity']) stock_health = stock.loc[stock['Heating intensity'] <= self.hi_threshold, 'Stock'] @@ -6761,7 +6806,7 @@ def health_cost(self, health_cost_dpe, health_cost_income, prices, stock=None, m def marginal_abatement_cost(self, consumption_saved, emission_saved, cost_insulation, stock, prices, certificate_after, certificate_after_3uses, lifetime=30, discount_rate=0.05, measures='deep_renovation', plot=False, carbon_saved=None, - health_cost=None, cash_flow_option=True): + health_cost=None, cash_flow_option=True, pef_elec=None): """Calculate the marginal abatement cost of insulation measures. Parameters @@ -6807,7 +6852,7 @@ def marginal_abatement_cost(self, consumption_saved, emission_saved, cost_insula _output_statistics = {} health_cost_saved = None if health_cost is not None: - _, certificate_before, _ = self.consumption_heating(index=index, method='3uses', full_output=True) + _, certificate_before, _ = self.consumption_heating(index=index, method='3uses', full_output=True, pef_elec=pef_elec) df = concat((certificate_before, stock.loc[index]), keys=['Performance', 'Stock'], axis=1).dropna().set_index( 'Performance', append=True).squeeze() health_cost_before = reindex_mi(health_cost, df.index).droplevel('Performance').fillna(0) @@ -7020,7 +7065,8 @@ def closest(df, value): def make_static_analysis(self, cost_insulation, cost_heater, prices, discount_rate, implicit_discount_rate, health_cost, carbon_content, - path_out=None, carbon_value=50, selected_options=None, sufix=''): + path_out=None, carbon_value=50, selected_options=None, sufix='', + pef_elec=None): # select only stock mobile and existing before the first year if path_out is None: path_out = self.path_ini @@ -7030,12 +7076,13 @@ def make_static_analysis(self, cost_insulation, cost_heater, prices, discount_ra stock = stock.droplevel('Performance') index = stock.index - consumption_before = self.consumption_heating_store(index, full_output=False) + consumption_before = self.consumption_heating_store(index, full_output=False, pef_elec=pef_elec) consumption_before = reindex_mi(consumption_before, index) temp = consumption_before * reindex_mi(self._surface, consumption_before.index) heating_intensity_before = self.to_heating_intensity(temp.index, prices, consumption=temp, - level_heater='Heating system') + level_heater='Heating system', + pef_elec=pef_elec) consumption_before *= heating_intensity_before c_content = carbon_content.reindex(self.to_energy(consumption_before)).set_axis(consumption_before.index) @@ -7053,19 +7100,22 @@ def make_static_analysis(self, cost_insulation, cost_heater, prices, discount_ra consumption_after, _, certificate_after = self.prepare_consumption(self._choice_insulation, index=s.index, level_heater='Heating system final', - full_output=True) + full_output=True, + pef_elec=pef_elec) consumption_after = reindex_mi(consumption_after, s.index) _, _, certificate_after_3uses = self.prepare_consumption(self._choice_insulation, index=s.index, level_heater='Heating system final', full_output=True, - method_epc='3uses') + method_epc='3uses', + pef_elec=pef_elec) temp = (consumption_after.T * reindex_mi(self._surface, consumption_after.index)).T heating_intensity_after = self.to_heating_intensity(temp.index, prices, consumption=temp, - level_heater='Heating system final') + level_heater='Heating system final', + pef_elec=pef_elec) consumption_after *= heating_intensity_after c_content = carbon_content.reindex(self.to_energy(consumption_after, level_heater='Heating system final')).set_axis(consumption_after.index) @@ -7220,16 +7270,16 @@ def make_static_analysis(self, cost_insulation, cost_heater, prices, discount_ra dict_rslt, dict_stats = {}, {} for key, option in options.items(): temp = self.marginal_abatement_cost(consumption_saved, emission_saved, cost, self._stock_ref, - prices, certificate_after, certificate_after_3uses, lifetime=25, **option) + prices, certificate_after, certificate_after_3uses, lifetime=25, **option, pef_elec=pef_elec) dict_rslt.update({key: temp[0]}) dict_stats.update({key: temp[1]}) dict_rslt = reverse_dict(dict_rslt) - c_before = self.consumption_heating_store(self._stock_ref.index, full_output=False) + c_before = self.consumption_heating_store(self._stock_ref.index, full_output=False, pef_elec=pef_elec) c_before = reindex_mi(c_before, self._stock_ref.index) * self._stock_ref * reindex_mi(self._surface, self._stock_ref.index) - heating_intensity_before = self.to_heating_intensity(c_before.index, prices) + heating_intensity_before = self.to_heating_intensity(c_before.index, prices, pef_elec=pef_elec) c_before *= heating_intensity_before c_content = carbon_content.reindex(self.to_energy(c_before)).set_axis(c_before.index) From d50445cde1e1520300fdc8c8a9004d740c1f30dc Mon Sep 17 00:00:00 2001 From: VictorMannoni Date: Thu, 11 Sep 2025 18:00:44 +0200 Subject: [PATCH 27/31] docs(buildings): improve docstring for reset_consumption_store --- project/building.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/project/building.py b/project/building.py index 78741e48..1537bb84 100644 --- a/project/building.py +++ b/project/building.py @@ -338,16 +338,25 @@ def size_heater(self, index=None): return size_heating_system / 1e3 def reset_consumption_store(self): - """Réinitialise le cache des consommations / certificats.""" - import pandas as pd - self._consumption_store = { - 'consumption': pd.Series(dtype='float'), - 'consumption_3uses': pd.Series(dtype='float'), - 'certificate': pd.Series(dtype='float'), - 'consumption_renovation': pd.Series(dtype='float'), - 'consumption_3uses_renovation': pd.Series(dtype='float'), - 'certificate_renovation': pd.Series(dtype='float'), - } + """ + Reset the internal cache for consumption and certificates. + + This method initializes empty pandas Series for all stored + consumption and certificate variables, including those related + to renovations. + + Returns + ------- + None + """ + self._consumption_store = { + 'consumption': pd.Series(dtype='float'), + 'consumption_3uses': pd.Series(dtype='float'), + 'certificate': pd.Series(dtype='float'), + 'consumption_renovation': pd.Series(dtype='float'), + 'consumption_3uses_renovation': pd.Series(dtype='float'), + 'certificate_renovation': pd.Series(dtype='float'), + } def consumption_heating(self, index=None, freq='year', climate=None, smooth=False, full_output=False, efficiency_hour=False, level_heater='Heating system', From e418032ef4a31c46dd037a51d22c6992036765a5 Mon Sep 17 00:00:00 2001 From: VictorMannoni Date: Fri, 12 Sep 2025 10:47:00 +0200 Subject: [PATCH 28/31] add: CSV file for pef_elec Add new file primary_energy_factor_electricity.csv --- project/input/energy/primary_energy_factor_electricity.csv | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 project/input/energy/primary_energy_factor_electricity.csv diff --git a/project/input/energy/primary_energy_factor_electricity.csv b/project/input/energy/primary_energy_factor_electricity.csv new file mode 100644 index 00000000..b21c93cd --- /dev/null +++ b/project/input/energy/primary_energy_factor_electricity.csv @@ -0,0 +1,3 @@ +Year,Electricity +2023,2.3 +2026,1.9 \ No newline at end of file From 780a46b38dce90db1df5e632b2a2b15b9fa899ea Mon Sep 17 00:00:00 2001 From: VictorMannoni Date: Fri, 12 Sep 2025 10:48:15 +0200 Subject: [PATCH 29/31] feat(coupling): add pef_elec argument to functions --- project/coupling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/coupling.py b/project/coupling.py index 7c41909b..89bb8eb7 100644 --- a/project/coupling.py +++ b/project/coupling.py @@ -84,7 +84,7 @@ def ini_res_irf(config=None, path=None, level_logger='DEBUG'): output = pd.DataFrame() # run first year - consumption - _, o = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs']) + _, o = buildings.parse_output_run(energy_prices.loc[buildings.first_year, :], inputs_dynamics['post_inputs'], pef_elec=inputs_dynamics['pef_elec'].loc[buildings.first_year]) output = pd.concat((output, o), axis=1) if config['simple'].get('no_policy_insulation'): From 851da5a2b6b3ea8544f73ce5fa5bc64cd9faea6a Mon Sep 17 00:00:00 2001 From: VictorMannoni Date: Fri, 12 Sep 2025 10:53:06 +0200 Subject: [PATCH 30/31] fix(model): reset consumption store when pef_elec changes In model.py, add check on yearly pef_elec value. If pef_elec differs from previous year, call buildings.reset_consumption_store(). Ensures cached consumption values are recalculated with updated PEF. --- project/model.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/project/model.py b/project/model.py index 7cb51ca1..427155f9 100644 --- a/project/model.py +++ b/project/model.py @@ -657,6 +657,11 @@ def res_irf(config, path, level_logger='DEBUG'): heat_pump = [i for i in resources_data['index']['Heat pumps'] if i in inputs_dynamics['cost_heater'].index] inputs_dynamics['cost_heater'].loc[heat_pump] *= (1 + technical_progress['heater'].loc[year])**step + # Reset consumption store if pef_elec changed + if year != buildings.first_year : + if inputs_dynamics['pef_elec'].loc[year] != inputs_dynamics['pef_elec'].loc[year - 1]: + buildings.reset_consumption_store() + buildings, s, o, df_renovations, merged_df_heater, df_final_grouped = stock_turnover(buildings, prices, taxes, inputs_dynamics['cost_heater'], inputs_dynamics['cost_insulation'], From b0b30e6dfd889fbb2278e27d78d229b9b9e3415a Mon Sep 17 00:00:00 2001 From: VictorMannoni Date: Fri, 12 Sep 2025 10:54:11 +0200 Subject: [PATCH 31/31] feat(model): add import/export functionality for renovation calibration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read new configuration keys: - config['load_calibration_renovation'] → path to calibration file to import. - config['export_calibration_renovation'] → path where calibration should be exported. -config['stop_after_calibration_export'] → boolean to stop execution after export. Enable loading and saving of renovation calibration data through the model. --- project/model.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/project/model.py b/project/model.py index 427155f9..f523e931 100644 --- a/project/model.py +++ b/project/model.py @@ -657,6 +657,34 @@ def res_irf(config, path, level_logger='DEBUG'): heat_pump = [i for i in resources_data['index']['Heat pumps'] if i in inputs_dynamics['cost_heater'].index] inputs_dynamics['cost_heater'].loc[heat_pump] *= (1 + technical_progress['heater'].loc[year])**step + # Import renovation calibration if path provided in config + if year == buildings.first_year + 1 and config.get('load_calibration_renovation') is not None: + try: + with open(config['load_calibration_renovation'], 'rb') as f: + c = load(f) + except FileNotFoundError: + buildings.logger.error(f"[Calibration] Fichier introuvable : {config['load_calibration_renovation']}") + raise + except Exception as e: + buildings.logger.exception(f"[Calibration] Erreur d'ouverture/lecture : {config['load_calibration_renovation']}") + raise + + buildings.coefficient_global = c['coefficient_global'] + buildings.coefficient_backup = c['coefficient_backup'] + buildings.constant_insulation_extensive = c['constant_insulation_extensive'] + buildings.constant_insulation_intensive = c['constant_insulation_intensive'] + buildings.constant_heater = c['constant_heater'] + buildings.scale_insulation = c['scale_insulation'] + buildings.apply_scale(buildings.scale_insulation, gest='insulation') + buildings.scale_heater = c['scale_heater'] + + try: + buildings.apply_scale(buildings.scale_heater, gest='heater') + except Exception: + buildings.logger.debug("[Calibration] apply_scale('heater') non utilisé/pas nécessaire") + + buildings.logger.info(f"[Calibration] Rénovation importée depuis {config['load_calibration_renovation']}") + # Reset consumption store if pef_elec changed if year != buildings.first_year : if inputs_dynamics['pef_elec'].loc[year] != inputs_dynamics['pef_elec'].loc[year - 1]: @@ -711,6 +739,34 @@ def res_irf(config, path, level_logger='DEBUG'): 'scale_heater': buildings.scale_heater }, file) + # Export renovation calibration if requested (calculated during the stock_turnover process) + if year == buildings.first_year + 1 and config.get('export_calibration_renovation') is not None: + os.makedirs(os.path.dirname(config['export_calibration_renovation']), exist_ok=True) + + payload = { + 'schema_version': 1, + # Rénovation + 'constant_insulation_extensive': getattr(buildings, 'constant_insulation_extensive', None), + 'constant_insulation_intensive': getattr(buildings, 'constant_insulation_intensive', None), + 'scale_insulation': getattr(buildings, 'scale_insulation', None), + # Chauffage (si dispo) + 'constant_heater': getattr(buildings, 'constant_heater', None), + 'scale_heater': getattr(buildings, 'scale_heater', None), + # Coeffs globaux/backup (selon ton usage) + 'coefficient_global': getattr(buildings, 'coefficient_global', None), + 'coefficient_backup': getattr(buildings, 'coefficient_backup', None), + } + + with open(config['export_calibration_renovation'], 'wb') as f: + dump(payload, f) + + buildings.logger.info(f"[Calibration] Rénovation exportée vers {config['export_calibration_renovation']}") + + if config.get('stop_after_calibration_export') is True: + buildings.logger.info('[Calibration] Arrêt demandé après export de calibration.') + import sys + sys.exit(0) + if path is not None: buildings.logger.info('Writing output in {}'.format(path))