diff --git a/energy_models/core/ccus/ccus.py b/energy_models/core/ccus/ccus.py index 49a4afc2..4db58eb1 100644 --- a/energy_models/core/ccus/ccus.py +++ b/energy_models/core/ccus/ccus.py @@ -15,16 +15,16 @@ limitations under the License. ''' -import numpy as np +import autograd.numpy as np import pandas as pd +from autograd import jacobian -from energy_models.core.stream_type.base_stream import BaseStream from energy_models.core.stream_type.carbon_models.carbon import Carbon from energy_models.core.stream_type.carbon_models.carbon_capture import CarbonCapture from energy_models.glossaryenergy import GlossaryEnergy -class CCUS(BaseStream): +class CCUS: """ Class CCUS """ @@ -35,7 +35,6 @@ class CCUS(BaseStream): TOTAL_PRODUCTION = 'Total production' RESOURCE_LIST = ['natural_gas_resource', 'uranium_resource', 'coal_resource', 'oil_resource'] - CARBON_STORAGE_CONSTRAINT = 'carbon_storage_constraint' resource_list = RESOURCE_LIST CO2_list = [f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})', @@ -48,524 +47,154 @@ def __init__(self, name): ''' Constructor ''' - super(CCUS, self).__init__(name) - - self.CCS_price = None - self.carbonstorage_limit = None - self.carbonstorage_constraint_ref = None - self.stream_prices = None - self.all_resource_demand = None - self.co2_production = None - self.co2_consumption = None - self.emissions_by_energy = None - self.carbon_storage_constraint = None - self.total_co2_emissions = pd.DataFrame() - self.total_co2_emissions_Gt = None + self.year_start = None + self.year_end = None + self.years = None self.co2_for_food = None self.scaling_factor_energy_production = None self.scaling_factor_energy_consumption = None - self.co2_emissions_needed_by_energy_mix = None - self.carbon_capture_from_energy_mix = None - self.total_carbon_storage_by_invest = None - def configure(self, inputs_dict): - ''' - Configure method - ''' - self.configure_parameters(inputs_dict) - self.configure_parameters_update(inputs_dict) + self.inputs_dict = {} + self.outputs_dict = {} def configure_parameters(self, inputs_dict): ''' COnfigure parameters (variables that does not change during the run ''' - self.subelements_list = inputs_dict[GlossaryEnergy.ccs_list] - BaseStream.configure_parameters(self, inputs_dict) - - self.co2_for_food = pd.DataFrame( - {GlossaryEnergy.Years: self.production[GlossaryEnergy.Years], f'{GlossaryEnergy.carbon_capture} for food (Mt)': 0.0}) - self.CCS_price = pd.DataFrame( - {GlossaryEnergy.Years: np.arange(inputs_dict[GlossaryEnergy.YearStart], - inputs_dict[GlossaryEnergy.YearEnd] + 1)}) - self.carbonstorage_limit = inputs_dict['carbonstorage_limit'] - self.carbonstorage_constraint_ref = inputs_dict['carbonstorage_constraint_ref'] - self.total_carbon_storage_by_invest = np.zeros(len(self.production[GlossaryEnergy.Years])) - - def configure_parameters_update(self, inputs_dict): - ''' - COnfigure parameters with possible update (variables that does change during the run) - ''' - self.subelements_list = inputs_dict[GlossaryEnergy.ccs_list] - - # Specific configure for energy mix + self.inputs_dict = inputs_dict + self.year_start = inputs_dict[GlossaryEnergy.YearStart] + self.year_end = inputs_dict[GlossaryEnergy.YearEnd] + self.years = np.arange(self.year_start, self.year_end + 1) self.scaling_factor_energy_production = inputs_dict['scaling_factor_energy_production'] self.scaling_factor_energy_consumption = inputs_dict['scaling_factor_energy_consumption'] - for element in self.subelements_list: - self.sub_prices[element] = inputs_dict[f'{element}.{GlossaryEnergy.StreamPricesValue}'][element] - self.sub_production_dict[element] = inputs_dict[f'{element}.{GlossaryEnergy.EnergyProductionValue}'] * \ - self.scaling_factor_energy_production - self.sub_consumption_dict[element] = inputs_dict[f'{element}.{GlossaryEnergy.StreamConsumptionValue}'] * \ - self.scaling_factor_energy_consumption - self.sub_consumption_woratio_dict[element] = inputs_dict[f'{element}.{GlossaryEnergy.StreamConsumptionWithoutRatioValue}'] * \ - self.scaling_factor_energy_consumption - - self.stream_prices = self.sub_prices.copy(deep=True) - - # dataframe resource demand - self.all_resource_demand = pd.DataFrame( - {GlossaryEnergy.Years: self.stream_prices[GlossaryEnergy.Years].values}) - for elements in self.resource_list: - if elements in self.resource_list: - self.all_resource_demand[elements] = np.linspace( - 0, 0, len(self.all_resource_demand.index)) * 100. - for element in self.subelements_list: - for elements in self.sub_consumption_dict[element]: - if elements in self.resource_list: - self.all_resource_demand[elements] = self.all_resource_demand[elements] + \ - inputs_dict[ - f'{element}.{GlossaryEnergy.StreamConsumptionValue}'][ - elements].values * \ - self.scaling_factor_energy_consumption - - self.co2_emissions_needed_by_energy_mix = inputs_dict['co2_emissions_needed_by_energy_mix'] - self.carbon_capture_from_energy_mix = inputs_dict['carbon_capture_from_energy_mix'] - # self.co2_emissions = inputs_dict['co2_emissions'] - - def compute_CO2_emissions(self): - ''' - Compute CO2 total emissions - ''' - # Initialize dataframes - - self.co2_production = pd.DataFrame({GlossaryEnergy.Years: self.production[GlossaryEnergy.Years]}) - self.total_co2_emissions[GlossaryEnergy.Years] = self.production[GlossaryEnergy.Years] - self.co2_consumption = pd.DataFrame( - {GlossaryEnergy.Years: self.production[GlossaryEnergy.Years]}) - self.emissions_by_energy = pd.DataFrame( - {GlossaryEnergy.Years: self.production[GlossaryEnergy.Years]}) - # Do not loop over carbon capture and carbon storage which will be - # handled differently - - ''' CARBON STORAGE - Total carbon storage is production of carbon storage - Solid carbon is gaseous equivalent in the production for - solidcarbonstorage technology - ''' - if GlossaryEnergy.carbon_storage in self.sub_production_dict: - - self.total_co2_emissions[f'{GlossaryEnergy.carbon_storage} ({GlossaryEnergy.mass_unit})'] = self.sub_production_dict[ - GlossaryEnergy.carbon_storage][GlossaryEnergy.carbon_storage].values - try: - self.total_carbon_storage_by_invest = self.sub_production_dict[GlossaryEnergy.carbon_storage][ - GlossaryEnergy.carbon_storage].values * \ - self.sub_consumption_woratio_dict[GlossaryEnergy.carbon_storage][ - f'{GlossaryEnergy.carbon_capture} ({CarbonCapture.unit})'].values / \ - self.sub_consumption_dict[GlossaryEnergy.carbon_storage][ - f'{GlossaryEnergy.carbon_capture} ({CarbonCapture.unit})'].values - except: - a = 1 - else: - self.total_co2_emissions[f'{GlossaryEnergy.carbon_storage} ({GlossaryEnergy.mass_unit})'] = 0.0 - - ''' CARBON CAPTURE from CC technos - Total carbon capture = carbon captured from carboncapture stream + - carbon captured from energies (can be negative if FischerTropsch needs carbon - captured) - ''' - if GlossaryEnergy.carbon_capture in self.sub_production_dict: - - self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos'] = self.sub_production_dict[ - GlossaryEnergy.carbon_capture][GlossaryEnergy.carbon_capture].values - else: - self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos'] = 0.0 - - ''' Carbon captured that needs to be stored - sum of the one from CC technos and the one directly captured - we delete the one needed by energy mix and potentially later the CO2 for food - ''' - - self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} to be stored (Mt)'] = self.total_co2_emissions[ - f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos'] + \ - self.carbon_capture_from_energy_mix[ - f'{GlossaryEnergy.carbon_capture} from energy mix (Gt)'] * 1e3 - \ - self.co2_emissions_needed_by_energy_mix[ - f'{GlossaryEnergy.carbon_capture} needed by energy mix (Gt)'] * 1e3 - \ - self.co2_for_food[ - f'{GlossaryEnergy.carbon_capture} for food (Mt)'] - - ''' - Solid Carbon to be stored to limit the carbon solid storage - ''' - energy_producing_solid_carbon = self.co2_production[[ - col for col in self.co2_production if col.endswith(f'{Carbon.name} ({GlossaryEnergy.mass_unit})')]] - energy_producing_solid_carbon_list = [key.replace( - f' {Carbon.name} ({GlossaryEnergy.mass_unit})', '') for key in energy_producing_solid_carbon] - if len(energy_producing_solid_carbon_list) != 0: - self.total_co2_emissions[f'{Carbon.name} to be stored (Mt)'] = energy_producing_solid_carbon.sum( - axis=1).values - else: - self.total_co2_emissions[ - f'{Carbon.name} to be stored (Mt)'] = 0.0 - - ''' - Solid Carbon storage - ''' - self.total_co2_emissions[f'Solid {Carbon.name} storage (Mt)'] = 0.0 - if GlossaryEnergy.carbon_storage in self.sub_consumption_dict: - if f'{Carbon.name} ({GlossaryEnergy.mass_unit})' in self.sub_consumption_dict[GlossaryEnergy.carbon_storage]: - self.total_co2_emissions[f'Solid {Carbon.name} storage (Mt)'] = self.sub_consumption_dict[ - GlossaryEnergy.carbon_storage][f'{Carbon.name} ({GlossaryEnergy.mass_unit})'].values - - ''' - The carbon stored by invest is limited by the carbon previously captured to be stored - for gaseous and solid carbon storage - Solid carbon storage is taken into account into carbon storage - need to delete it before using the minimum - ''' - - self.total_co2_emissions[f'{GlossaryEnergy.carbon_storage} Limited by capture (Mt)'] = np.minimum( - self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} to be stored (Mt)'].values, - self.total_co2_emissions[f'{GlossaryEnergy.carbon_storage} ({GlossaryEnergy.mass_unit})'].values - self.total_co2_emissions[ - f'Solid {Carbon.name} storage (Mt)'].values * Carbon.data_energy_dict[GlossaryEnergy.CO2PerUse]) + \ - np.minimum( - self.total_co2_emissions[ - f'{Carbon.name} to be stored (Mt)'].values, - self.total_co2_emissions[ - f'Solid {Carbon.name} storage (Mt)'].values) * \ - Carbon.data_energy_dict[ - GlossaryEnergy.CO2PerUse] - - self.total_co2_emissions_Gt = pd.DataFrame( - {GlossaryEnergy.Years: self.production[GlossaryEnergy.Years], - f'{GlossaryEnergy.carbon_storage} Limited by capture (Gt)': self.total_co2_emissions[ - f'{GlossaryEnergy.carbon_storage} Limited by capture (Mt)'].values / 1e3}) + self.co2_for_food = pd.DataFrame({GlossaryEnergy.Years: self.years, f'{GlossaryEnergy.carbon_capture} for food (Mt)': 0.0}) + self.ccs_list = [GlossaryEnergy.carbon_capture, GlossaryEnergy.carbon_storage] + + def compute_carbon_storage_capacity(self): + total_carbon_storage_by_invest = self.inputs_dict[f"{GlossaryEnergy.carbon_storage}.{GlossaryEnergy.EnergyProductionValue}"][ GlossaryEnergy.carbon_storage].values * self.scaling_factor_energy_production + + self.outputs_dict['carbon_storage_capacity'] = pd.DataFrame({ + GlossaryEnergy.Years: self.years, + 'carbon_storage_capacity': total_carbon_storage_by_invest + }) + + def compute_co2_emissions(self): + self.compute_carbon_storage_capacity() + + carbon_capture_from_energy_mix = self.inputs_dict['carbon_capture_from_energy_mix'][f'{GlossaryEnergy.carbon_capture} from energy mix (Gt)'].values + co2_emissions_needed_by_energy_mix = self.inputs_dict['co2_emissions_needed_by_energy_mix'][f'{GlossaryEnergy.carbon_capture} needed by energy mix (Gt)'].values + co2_for_food = self.co2_for_food[f'{GlossaryEnergy.carbon_capture} for food (Mt)'].values + + carbon_capture_prod = self.inputs_dict[f"{GlossaryEnergy.carbon_capture}.{GlossaryEnergy.EnergyProductionValue}"][GlossaryEnergy.carbon_capture].values + carbon_storage_prod = self.inputs_dict[f"{GlossaryEnergy.carbon_storage}.{GlossaryEnergy.EnergyProductionValue}"][GlossaryEnergy.carbon_storage].values + + carbon_capture_to_be_stored, carbon_storage_limited_by_capture_gt, carbon_storage, carbon_capture_from_cc_technos = compute_carbon_storage_limited_by_capture_gt( + carbon_capture_prod=carbon_capture_prod, + carbon_storage_prod=carbon_storage_prod, + carbon_capture_from_energy_mix=carbon_capture_from_energy_mix, + co2_emissions_needed_by_energy_mix=co2_emissions_needed_by_energy_mix, + co2_for_food=co2_for_food, + scaling_factor_energy_production=self.scaling_factor_energy_production + ) + + self.outputs_dict['co2_emissions_ccus'] = pd.DataFrame({ + GlossaryEnergy.Years: self.years, + f'{GlossaryEnergy.carbon_storage} ({GlossaryEnergy.mass_unit})': carbon_storage, + f'{GlossaryEnergy.carbon_capture} to be stored (Mt)': carbon_capture_to_be_stored, + f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos': carbon_capture_from_cc_technos, + f'{GlossaryEnergy.carbon_storage} Limited by capture (Mt)': carbon_storage_limited_by_capture_gt / 1e3, + }) + + self.outputs_dict['co2_emissions_ccus_Gt'] = pd.DataFrame({ + GlossaryEnergy.Years: self.years, + f'{GlossaryEnergy.carbon_storage} Limited by capture (Gt)': carbon_storage_limited_by_capture_gt + }) + + def compute(self): + self.compute_co2_emissions() + self.compute_CCS_price() def compute_CCS_price(self): ''' Compute CCS_price ''' - self.CCS_price['ccs_price_per_tCO2'] = 0. - if GlossaryEnergy.carbon_capture in self.sub_prices: - self.CCS_price['ccs_price_per_tCO2'] += self.stream_prices[GlossaryEnergy.carbon_capture] - if GlossaryEnergy.carbon_storage in self.sub_prices: - self.CCS_price['ccs_price_per_tCO2'] += self.sub_prices[GlossaryEnergy.carbon_storage] - - def compute_carbon_storage_constraint(self): - ''' - Compute carbon storage constraint - ''' - - self.carbon_storage_constraint = np.array( - [- (self.total_co2_emissions[f'{GlossaryEnergy.carbon_storage} Limited by capture (Mt)'].sum( - ) - self.carbonstorage_limit) / self.carbonstorage_constraint_ref]) - - def compute_grad_CO2_emissions(self, co2_emissions): - ''' - Compute CO2 total emissions - ''' - # Initialize dataframes - len_years = len(self.production[GlossaryEnergy.Years]) - - co2_production = pd.DataFrame({GlossaryEnergy.Years: self.production[GlossaryEnergy.Years]}) - co2_consumption = pd.DataFrame( - {GlossaryEnergy.Years: self.production[GlossaryEnergy.Years]}) - - dtot_CO2_emissions = {} - # Do not loop over carbon capture and carbon storage which will be - # handled differently - - ''' CARBON STORAGE - Total carbon storage is production of carbon storage - Solid carbon is gaseous equivalent in the production for - solidcarbonstorage technology - ''' - if GlossaryEnergy.carbon_storage in self.sub_production_dict: - dtot_CO2_emissions[ - f'{GlossaryEnergy.carbon_storage} ({GlossaryEnergy.mass_unit}) vs {GlossaryEnergy.carbon_storage}#{GlossaryEnergy.carbon_storage}#prod'] = np.ones( - len_years) - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_storage} ({GlossaryEnergy.mass_unit})'] = self.sub_production_dict[ - # GlossaryEnergy.carbon_storage][GlossaryEnergy.carbon_storage].values - # else: - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_storage} ({GlossaryEnergy.mass_unit})'] = 0.0 - - ''' CARBON CAPTURE from CC technos - Total carbon capture = carbon captured from carboncapture stream + - carbon captured from energies (can be negative if FischerTropsch needs carbon - captured) - ''' - if GlossaryEnergy.carbon_capture in self.sub_production_dict: - dtot_CO2_emissions[ - f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos vs {GlossaryEnergy.carbon_capture}#{GlossaryEnergy.carbon_capture}#prod'] = np.ones( - len_years) - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos'] = self.sub_production_dict[ - # GlossaryEnergy.carbon_capture][GlossaryEnergy.carbon_capture].values - # else: - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos'] = 0.0 - - ''' CARBON CAPTURE from energy mix - Total carbon capture from energy mix if the technology offers carbon_capture - Ex : upgrading biogas technology is the same as Amine Scrubbing but - on a different gas (biogas for upgrading biogas and flue gas for - Amien scrubbing) - ''' - energy_producing_carbon_capture = co2_production[[ - col for col in co2_production if col.endswith(f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})')]] - energy_producing_carbon_capture_list = [key.replace( - f' {GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})', '') for key in energy_producing_carbon_capture] - if len(energy_producing_carbon_capture_list) != 0: - for energy1 in energy_producing_carbon_capture_list: - dtot_CO2_emissions[ - f'{GlossaryEnergy.carbon_capture} from energy mix (Mt) vs {energy1}#{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})#prod'] = np.ones( - len_years) - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} from energy mix (Mt)'] = energy_producing_carbon_capture.sum( - # axis=1).values - # else: - # self.total_co2_emissions[ - # f'{GlossaryEnergy.carbon_capture} from energy mix (Mt)'] = 0.0 - - ''' CARBON CAPTURE needed by energy mix - Total carbon capture needed by energy mix if a technology needs carbon_capture - Ex :Sabatier process or RWGS in FischerTropsch technology - ''' - energy_needing_carbon_capture = co2_consumption[[ - col for col in co2_consumption if col.endswith(f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})')]] - energy_needing_carbon_capture_list = [key.replace( - f' {GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})', '') for key in energy_needing_carbon_capture] - if len(energy_needing_carbon_capture_list) != 0: - for energy1 in energy_needing_carbon_capture_list: - dtot_CO2_emissions[ - f'{GlossaryEnergy.carbon_capture} needed by energy mix (Mt) vs {energy1}#{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})#cons'] = np.ones( - len_years) - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} needed by energy mix (Mt)'] = energy_needing_carbon_capture.sum( - # axis=1).values - # else: - # self.total_co2_emissions[ - # f'{GlossaryEnergy.carbon_capture} needed by energy mix (Mt)'] = 0.0 - - ''' CO2 from energy mix - CO2 expelled by energy mix technologies during the process - i.e. for machinery or tractors - ''' - energy_producing_co2 = co2_production[[ - col for col in co2_production if col.endswith(f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})')]] - energy_producing_co2_list = [key.replace( - f' {GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})', '') for key in energy_producing_co2] - if len(energy_producing_co2_list) != 0: - for energy1 in energy_producing_co2_list: - dtot_CO2_emissions[ - f'{GlossaryEnergy.carbon_capture} from energy mix (Mt) vs {energy1}#{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})#prod'] = np.ones(len_years) - - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} from energy mix (Mt)'] = energy_producing_co2.sum( - # axis=1).values - # else: - # self.total_co2_emissions[ - # f'{GlossaryEnergy.carbon_capture} from energy mix (Mt)'] = 0.0 - - ''' CO2 removed by energy mix - CO2 removed by energy mix technologies during the process - i.e. biomass processes as managed wood or crop energy - ''' - energy_removing_co2 = co2_consumption[[ - col for col in co2_consumption if col.endswith(f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})')]] - energy_removing_co2_list = [key.replace( - f' {GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})', '') for key in energy_removing_co2] - if len(energy_removing_co2_list) != 0: - for energy1 in energy_removing_co2_list: - dtot_CO2_emissions[ - f'{GlossaryEnergy.carbon_capture} removed by energy mix (Mt) vs {energy1}#{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit})#cons'] = np.ones(len_years) - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} removed by energy mix (Mt)'] = energy_removing_co2.sum( - # axis=1).values - # else: - # self.total_co2_emissions[ - # f'{GlossaryEnergy.carbon_capture} removed energy mix (Mt)'] = 0.0 - - ''' Total C02 from Flue gas - sum of all production of flue gas - it could be equal to carbon capture from CC technos if enough investment but not sure - ''' - # self.total_co2_emissions[f'Total {CarbonCapture.flue_gas_name} ({GlossaryEnergy.mass_unit})'] = self.co2_production[[ - # col for col in self.co2_production if - # col.endswith(f'{CarbonCapture.flue_gas_name} ({GlossaryEnergy.mass_unit})')]].sum(axis=1) - for col in co2_production: - if col.endswith(f'{CarbonCapture.flue_gas_name} ({GlossaryEnergy.mass_unit})'): - energy1 = col.replace( - f' {CarbonCapture.flue_gas_name} ({GlossaryEnergy.mass_unit})', '') - dtot_CO2_emissions[ - f'Total {CarbonCapture.flue_gas_name} ({GlossaryEnergy.mass_unit}) vs {energy1}#{CarbonCapture.flue_gas_name} ({GlossaryEnergy.mass_unit})#prod'] = np.ones( - len_years) - ''' Carbon captured that needs to be stored - sum of the one from CC technos and the one directly captured - we delete the one needed by energy mix and potentially later the CO2 for food - ''' - - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} to be stored (Mt)'] = self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos'] + \ - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} from energy mix (Mt)'] - \ - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} needed by energy mix (Mt)'] -\ - # self.total_co2_emissions[f'{GlossaryEnergy.carbon_capture} for food (Mt)' - - new_key = f'{GlossaryEnergy.carbon_capture} to be stored (Mt)' - dtot_CO2_emissions[f'{new_key} vs {GlossaryEnergy.carbon_capture} for food (Mt)#carbon_capture'] = - np.ones( - len_years) - dtot_CO2_emissions[f'{new_key} vs {GlossaryEnergy.carbon_capture} from energy mix (Mt)#carbon_capture'] = np.ones( - len_years) - dtot_CO2_emissions[f'{new_key} vs {GlossaryEnergy.carbon_capture} needed by energy mix (Mt)#carbon_capture'] = - np.ones( - len_years) - key_dep_tuple_list = [(f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos', 1.0), - (f'{GlossaryEnergy.carbon_capture} from energy mix (Mt)', 1.0), - (f'{GlossaryEnergy.carbon_capture} needed by energy mix (Mt)', -1.0), - (f'{GlossaryEnergy.carbon_capture} for food (Mt)', -1.0)] - dtot_CO2_emissions = update_new_gradient( - dtot_CO2_emissions, key_dep_tuple_list, new_key) - - cc_to_be_stored = co2_emissions[f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos'].values + \ - self.carbon_capture_from_energy_mix[ - f'{GlossaryEnergy.carbon_capture} from energy mix (Gt)'].values * 1e3 - \ - self.co2_emissions_needed_by_energy_mix[ - f'{GlossaryEnergy.carbon_capture} needed by energy mix (Gt)'].values * 1e3 - \ - self.co2_for_food[f'{GlossaryEnergy.carbon_capture} for food (Mt)'].values - # if the carbon to be stored is lower than zero that means that we need - # more carbon capture for energy mix than the one created by CC technos - # or upgrading biogas - if cc_to_be_stored.min() < 0: - cc_needed = self.co2_emissions_needed_by_energy_mix[ - f'{GlossaryEnergy.carbon_capture} needed by energy mix (Gt)'].values * 1e3 - cc_provided = co2_emissions[f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos'].values + \ - self.carbon_capture_from_energy_mix[ - f'{GlossaryEnergy.carbon_capture} from energy mix (Gt)'].values * 1e3 - dtot_CO2_emissions_old = dtot_CO2_emissions.copy() - dratio_dkey = {} - # The gradient is (u/v)' = (u'v -uv')/v**2 = u'/v -uv'/v**2 - # u is the cc needed f'{GlossaryEnergy.carbon_capture} needed by energy mix (Mt)' - # v is the cc provided which is the sum of f'{GlossaryEnergy.carbon_capture} - # (Mt) from CC technos' and f'{GlossaryEnergy.carbon_capture} from energy mix - # (Mt)' - - for key, grad_cc in dtot_CO2_emissions_old.items(): - key_co2_emissions = key.split(' vs ')[0] - grad_info_x = key.split(' vs ')[1] - if key_co2_emissions == f'{GlossaryEnergy.carbon_capture} needed by energy mix (Mt)': - - if grad_info_x in dratio_dkey: - dratio_dkey[grad_info_x] -= np.divide(cc_provided * grad_cc, cc_needed ** 2, - out=np.zeros_like(cc_needed), where=cc_needed > 1.0e-15) - else: - dratio_dkey[grad_info_x] = - np.divide(cc_provided * grad_cc, cc_needed ** 2, - out=np.zeros_like(cc_needed), where=cc_needed > 1.0e-15) - - elif key_co2_emissions in [f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.mass_unit}) from CC technos', - f'{GlossaryEnergy.carbon_capture} from energy mix (Mt)']: - - if grad_info_x in dratio_dkey: - dratio_dkey[grad_info_x] += np.divide(grad_cc, cc_needed, - out=np.zeros_like(cc_needed), where=cc_needed > 1.0e-15) - else: - dratio_dkey[grad_info_x] = np.divide(grad_cc, cc_needed, - out=np.zeros_like(cc_needed), where=cc_needed > 1.0e-15) - - grad_max = np.maximum(0.0, np.sign( - cc_to_be_stored)) - - for key, value in dtot_CO2_emissions.items(): - if key.startswith(f'{GlossaryEnergy.carbon_capture} to be stored (Mt)'): - value *= grad_max - # co2_emissions[f'{GlossaryEnergy.carbon_capture} to be stored (Mt)'] = np.maximum( - # co2_emissions[f'{GlossaryEnergy.carbon_capture} to be stored (Mt)'], - # 0.0) - - ''' - Solid Carbon to be stored to limit the carbon solid storage - ''' - # energy_producing_solid_carbon = self.co2_production[[ - # col for col in self.co2_production if col.endswith(f'{Carbon.name} ({GlossaryEnergy.mass_unit})')]] - # energy_producing_solid_carbon_list = [key.replace( - # f' {Carbon.name} ({GlossaryEnergy.mass_unit})', '') for key in energy_producing_solid_carbon] - # if len(energy_producing_solid_carbon_list) != 0: - # co2_emissions[f'{Carbon.name} to be stored (Mt)'] = energy_producing_solid_carbon.sum( - # axis=1).values - # else: - # co2_emissions[ - # f'{Carbon.name} to be stored (Mt)'] = 0.0 - - for col in co2_production: - if col.endswith(f'{Carbon.name} ({GlossaryEnergy.mass_unit})'): - energy1 = col.replace(f' {Carbon.name} ({GlossaryEnergy.mass_unit})', '') - dtot_CO2_emissions[f'{Carbon.name} to be stored (Mt) vs {energy1}#{Carbon.name} ({GlossaryEnergy.mass_unit})#prod'] = np.ones( - len_years) - ''' - Solid Carbon storage - ''' - - if GlossaryEnergy.carbon_storage in self.sub_consumption_dict: - if f'{Carbon.name} ({GlossaryEnergy.mass_unit})' in self.sub_consumption_dict[GlossaryEnergy.carbon_storage]: - dtot_CO2_emissions[ - f'Solid {Carbon.name} storage (Mt) vs {GlossaryEnergy.carbon_storage}#{Carbon.name} ({GlossaryEnergy.mass_unit})#cons'] = np.ones( - len_years) - # co2_emissions[f'Solid {Carbon.name} storage (Mt)'] = self.sub_consumption_dict[ - # GlossaryEnergy.carbon_storage][f'{Carbon.name} ({GlossaryEnergy.mass_unit})'].values - - ''' - The carbon stored by invest is limited by the carbon previously captured to be stored - for gaseous and solid carbon storage - Solid carbon storage is taken into account into carbon storage - need to delete it before using the minimum - ''' - # if abs (min(a,b)-a) = 0.0 then the minimum is a else the minimum is b - # and the sign of abs (min(a,b)-a) is one - mini1 = np.minimum( - co2_emissions[f'{GlossaryEnergy.carbon_capture} to be stored (Mt)'].values, - co2_emissions[f'{GlossaryEnergy.carbon_storage} ({GlossaryEnergy.mass_unit})'].values - co2_emissions[ - f'Solid {Carbon.name} storage (Mt)'].values * Carbon.data_energy_dict[GlossaryEnergy.CO2PerUse]) - limit_is_storage = np.maximum(0.0, np.sign(np.abs( - mini1 - co2_emissions[f'{GlossaryEnergy.carbon_capture} to be stored (Mt)'].values))) - limit_is_to_be_stored = np.ones(len_years) - limit_is_storage - - # if abs (min(a,b)-a) = 0.0 then the minimum is a else the minimum is b - # and the sign of abs (min(a,b)-a) is one - mini2 = np.minimum( - co2_emissions[f'{Carbon.name} to be stored (Mt)'].values, - co2_emissions[f'Solid {Carbon.name} storage (Mt)'].values) * Carbon.data_energy_dict[GlossaryEnergy.CO2PerUse] - - limit_is_solid_storage = np.maximum(0.0, np.sign(np.abs( - mini2 - co2_emissions[f'{Carbon.name} to be stored (Mt)'].values * Carbon.data_energy_dict[GlossaryEnergy.CO2PerUse]))) - limit_is_solid_to_be_stored = np.ones( - len_years) - limit_is_solid_storage - new_key = f'{GlossaryEnergy.carbon_storage} Limited by capture (Mt)' - key_dep_tuple_list = [(f'{GlossaryEnergy.carbon_capture} to be stored (Mt)', limit_is_to_be_stored), - (f'{Carbon.name} to be stored (Mt)', - limit_is_solid_to_be_stored * Carbon.data_energy_dict[GlossaryEnergy.CO2PerUse]), - (f'{GlossaryEnergy.carbon_storage} ({GlossaryEnergy.mass_unit})', limit_is_storage), - (f'Solid {Carbon.name} storage (Mt)', - (-limit_is_storage + limit_is_solid_storage) * Carbon.data_energy_dict[GlossaryEnergy.CO2PerUse])] - dtot_CO2_emissions = update_new_gradient( - dtot_CO2_emissions, key_dep_tuple_list, new_key) - - dtot_final_CO2_emissions = dtot_CO2_emissions.copy() - for key, gradient in dtot_CO2_emissions.items(): - - if key.startswith(f'{GlossaryEnergy.carbon_storage} Limited by capture (Mt)'): - new_key = key.replace( - f'{GlossaryEnergy.carbon_storage} Limited by capture (Mt)', 'Carbon storage constraint') - dtot_final_CO2_emissions[new_key] = - \ - gradient / self.carbonstorage_constraint_ref - - return dtot_final_CO2_emissions - - -def update_new_gradient(grad_dict, key_dep_tuple_list, new_key): - ''' - Update new gradient which are dependent of old ones by simple sum or difference - ''' - new_grad_dict = grad_dict.copy() - for key in grad_dict: - for old_key, factor in key_dep_tuple_list: - if key.startswith(old_key): - # the grad of old key is equivalent to the new key because its - # a sum - new_grad_key = key.replace(old_key, new_key) - if new_grad_key in new_grad_dict: - new_grad_dict[new_grad_key] = new_grad_dict[new_grad_key] + \ - grad_dict[key] * factor - else: - new_grad_dict[new_grad_key] = grad_dict[key] * factor - - return new_grad_dict + ccs_price = self.inputs_dict[f'{GlossaryEnergy.carbon_capture}.{GlossaryEnergy.StreamPricesValue}'][GlossaryEnergy.carbon_capture].values +\ + self.inputs_dict[f'{GlossaryEnergy.carbon_storage}.{GlossaryEnergy.StreamPricesValue}'][GlossaryEnergy.carbon_storage].values + self.outputs_dict['CCS_price'] = pd.DataFrame({ + GlossaryEnergy.Years: self.years, + 'ccs_price_per_tCO2': ccs_price + }) + + def grad_co2_emissions_ccus_Gt(self): + carbon_capture_from_energy_mix = self.inputs_dict['carbon_capture_from_energy_mix'][f'{GlossaryEnergy.carbon_capture} from energy mix (Gt)'].values + co2_emissions_needed_by_energy_mix = self.inputs_dict['co2_emissions_needed_by_energy_mix'][f'{GlossaryEnergy.carbon_capture} needed by energy mix (Gt)'].values + co2_for_food = self.co2_for_food[f'{GlossaryEnergy.carbon_capture} for food (Mt)'].values + + carbon_capture_prod = self.inputs_dict[f"{GlossaryEnergy.carbon_capture}.{GlossaryEnergy.EnergyProductionValue}"][GlossaryEnergy.carbon_capture].values + carbon_storage_prod = self.inputs_dict[f"{GlossaryEnergy.carbon_storage}.{GlossaryEnergy.EnergyProductionValue}"][GlossaryEnergy.carbon_storage].values + + jac_carbon_capture_from_cc_prod, jac_carbon_capture_from_cs_prod, jac_carbon_capture_from_energy_mix, jac_co2_emissions_needed_by_energy_mix =\ + compute_carbon_storage_limited_by_capture_gt_der( + carbon_capture_prod=carbon_capture_prod, + carbon_storage_prod=carbon_storage_prod, + carbon_capture_from_energy_mix=carbon_capture_from_energy_mix, + co2_emissions_needed_by_energy_mix=co2_emissions_needed_by_energy_mix, + co2_for_food=co2_for_food, + scaling_factor_energy_production=self.scaling_factor_energy_production + ) + # input_name : (column_name, grad value) + out = { + 'carbon_capture_from_energy_mix': [(f'{GlossaryEnergy.carbon_capture} from energy mix (Gt)', jac_carbon_capture_from_energy_mix)], + 'co2_emissions_needed_by_energy_mix': [(f'{GlossaryEnergy.carbon_capture} needed by energy mix (Gt)', jac_co2_emissions_needed_by_energy_mix)], + f'{GlossaryEnergy.carbon_storage}.{GlossaryEnergy.EnergyProductionValue}': [(GlossaryEnergy.carbon_storage, jac_carbon_capture_from_cs_prod)], + f'{GlossaryEnergy.carbon_capture}.{GlossaryEnergy.EnergyProductionValue}': [(GlossaryEnergy.carbon_capture, jac_carbon_capture_from_cc_prod)] + } + + return out + + +def compute_carbon_storage_limited_by_capture_gt( + carbon_capture_prod: np.ndarray, + carbon_storage_prod: np.ndarray, + carbon_capture_from_energy_mix: np.ndarray, + co2_emissions_needed_by_energy_mix: np.ndarray, + co2_for_food: np.ndarray, + scaling_factor_energy_production: float + ): + '''The carbon stored by invest is limited by the carbon to stored''' + + carbon_storage = carbon_storage_prod * scaling_factor_energy_production + carbon_capture_from_cc_technos = carbon_capture_prod * scaling_factor_energy_production + + carbon_capture_to_be_stored = ( + carbon_capture_from_cc_technos + + carbon_capture_from_energy_mix * 1e3 - + co2_emissions_needed_by_energy_mix * 1e3 - + co2_for_food + ) + + carbon_storage_limited_by_capture_mt = np.minimum(carbon_capture_to_be_stored, carbon_storage) + return carbon_capture_to_be_stored, carbon_storage_limited_by_capture_mt / 1e3, carbon_storage, carbon_capture_from_cc_technos + + +def compute_carbon_storage_limited_by_capture_gt_der( + carbon_capture_prod: np.ndarray, + carbon_storage_prod: np.ndarray, + carbon_capture_from_energy_mix: np.ndarray, + co2_emissions_needed_by_energy_mix: np.ndarray, + co2_for_food: np.ndarray, + scaling_factor_energy_production: float + ): + args = (carbon_capture_prod, + carbon_storage_prod, + carbon_capture_from_energy_mix, + co2_emissions_needed_by_energy_mix, + co2_for_food, + scaling_factor_energy_production,) + + jac_carbon_capture_from_cc_prod = jacobian(lambda *args: compute_carbon_storage_limited_by_capture_gt(*args)[1], 0) + jac_carbon_capture_from_cs_prod = jacobian(lambda *args: compute_carbon_storage_limited_by_capture_gt(*args)[1], 1) + jac_carbon_capture_from_energy_mix = jacobian(lambda *args: compute_carbon_storage_limited_by_capture_gt(*args)[1], 2) + jac_co2_emissions_needed_by_energy_mix = jacobian(lambda *args: compute_carbon_storage_limited_by_capture_gt(*args)[1], 3) + + return jac_carbon_capture_from_cc_prod(*args), jac_carbon_capture_from_cs_prod(*args), jac_carbon_capture_from_energy_mix(*args), jac_co2_emissions_needed_by_energy_mix(*args) \ No newline at end of file diff --git a/energy_models/core/ccus/ccus_disc.py b/energy_models/core/ccus/ccus_disc.py index c17e862c..87ebfa1d 100644 --- a/energy_models/core/ccus/ccus_disc.py +++ b/energy_models/core/ccus/ccus_disc.py @@ -29,7 +29,6 @@ ) from energy_models.core.ccus.ccus import CCUS -from energy_models.core.energy_mix.energy_mix import EnergyMix from energy_models.core.stream_type.carbon_models.carbon_capture import CarbonCapture from energy_models.glossaryenergy import GlossaryEnergy @@ -49,12 +48,8 @@ class CCUS_Discipline(SoSWrapp): 'version': '', } + ccs_list = [GlossaryEnergy.carbon_capture, GlossaryEnergy.carbon_storage] DESC_IN = { - GlossaryEnergy.ccs_list: {'type': 'list', 'subtype_descriptor': {'list': 'string'}, - 'possible_values': CCUS.ccs_list, - 'visibility': SoSWrapp.SHARED_VISIBILITY, 'namespace': 'ns_energy_study', - 'editable': False, - 'structuring': True}, GlossaryEnergy.YearStart: ClimateEcoDiscipline.YEAR_START_DESC_IN, GlossaryEnergy.YearEnd: {'type': 'int', 'unit': 'year', 'visibility': 'Shared', 'namespace': 'ns_public', 'range': [2000,2300]}, @@ -84,16 +79,12 @@ class CCUS_Discipline(SoSWrapp): DESC_OUT = { 'co2_emissions_ccus': {'type': 'dataframe', 'unit': 'Mt'}, - 'carbon_storage_by_invest': {'type': 'array', 'unit': 'Mt'}, + 'carbon_storage_capacity': {'type': 'dataframe', 'unit': 'Mt'}, 'co2_emissions_ccus_Gt': {'type': 'dataframe', 'unit': 'Gt', 'visibility': SoSWrapp.SHARED_VISIBILITY, 'namespace': GlossaryEnergy.NS_CCS}, 'CCS_price': {'type': 'dataframe', 'unit': '$/tCO2', 'visibility': SoSWrapp.SHARED_VISIBILITY, 'namespace': 'ns_energy_study'}, - EnergyMix.CARBON_STORAGE_CONSTRAINT: {'type': 'array', 'unit': '', - 'visibility': SoSWrapp.SHARED_VISIBILITY, - 'namespace': GlossaryEnergy.NS_FUNCTIONS}, - } def __init__(self, sos_name, logger: logging.Logger): @@ -110,31 +101,27 @@ def setup_sos_disciplines(self): dynamic_inputs = {} dynamic_outputs = {} - if GlossaryEnergy.ccs_list in self.get_data_in(): - ccs_list = self.get_sosdisc_inputs(GlossaryEnergy.ccs_list) - # self.update_default_ccs_list() - if ccs_list is not None: - for ccs_name in ccs_list: - dynamic_inputs[f'{ccs_name}.{GlossaryEnergy.StreamConsumptionValue}'] = { - 'type': 'dataframe', 'unit': 'PWh', 'visibility': SoSWrapp.SHARED_VISIBILITY, - 'namespace': GlossaryEnergy.NS_CCS, - "dynamic_dataframe_columns": True} - dynamic_inputs[f'{ccs_name}.{GlossaryEnergy.StreamConsumptionWithoutRatioValue}'] = { - 'type': 'dataframe', 'unit': 'PWh', 'visibility': SoSWrapp.SHARED_VISIBILITY, - 'namespace': GlossaryEnergy.NS_CCS, - "dynamic_dataframe_columns": True} - dynamic_inputs[f'{ccs_name}.{GlossaryEnergy.EnergyProductionValue}'] = { - 'type': 'dataframe', 'unit': 'PWh', 'visibility': SoSWrapp.SHARED_VISIBILITY, - 'namespace': GlossaryEnergy.NS_CCS, - 'dynamic_dataframe_columns': True} - dynamic_inputs[f'{ccs_name}.{GlossaryEnergy.StreamPricesValue}'] = { - 'type': 'dataframe', 'unit': '$/MWh', 'visibility': SoSWrapp.SHARED_VISIBILITY, - 'namespace': GlossaryEnergy.NS_CCS, - 'dynamic_dataframe_columns': True} - dynamic_inputs[f'{ccs_name}.{GlossaryEnergy.LandUseRequiredValue}'] = { - 'type': 'dataframe', 'unit': 'Gha', 'visibility': SoSWrapp.SHARED_VISIBILITY, - 'namespace': GlossaryEnergy.NS_CCS, - "dynamic_dataframe_columns": True} + for ccs_name in self.ccs_list: + dynamic_inputs[f'{ccs_name}.{GlossaryEnergy.StreamConsumptionValue}'] = { + 'type': 'dataframe', 'unit': 'PWh', 'visibility': SoSWrapp.SHARED_VISIBILITY, + 'namespace': GlossaryEnergy.NS_CCS, + "dynamic_dataframe_columns": True} + dynamic_inputs[f'{ccs_name}.{GlossaryEnergy.StreamConsumptionWithoutRatioValue}'] = { + 'type': 'dataframe', 'unit': 'PWh', 'visibility': SoSWrapp.SHARED_VISIBILITY, + 'namespace': GlossaryEnergy.NS_CCS, + "dynamic_dataframe_columns": True} + dynamic_inputs[f'{ccs_name}.{GlossaryEnergy.EnergyProductionValue}'] = { + 'type': 'dataframe', 'unit': 'PWh', 'visibility': SoSWrapp.SHARED_VISIBILITY, + 'namespace': GlossaryEnergy.NS_CCS, + 'dynamic_dataframe_columns': True} + dynamic_inputs[f'{ccs_name}.{GlossaryEnergy.StreamPricesValue}'] = { + 'type': 'dataframe', 'unit': '$/MWh', 'visibility': SoSWrapp.SHARED_VISIBILITY, + 'namespace': GlossaryEnergy.NS_CCS, + 'dynamic_dataframe_columns': True} + dynamic_inputs[f'{ccs_name}.{GlossaryEnergy.LandUseRequiredValue}'] = { + 'type': 'dataframe', 'unit': 'Gha', 'visibility': SoSWrapp.SHARED_VISIBILITY, + 'namespace': GlossaryEnergy.NS_CCS, + "dynamic_dataframe_columns": True} self.update_default_values() self.add_inputs(dynamic_inputs), @@ -154,215 +141,28 @@ def update_default_values(self): self.update_default_value('co2_for_food', 'in', default_co2_for_food) def run(self): - # -- get inputs inputs_dict = self.get_sosdisc_inputs() - # -- configure class with inputs - # - self.ccus_model.configure_parameters_update(inputs_dict) - - self.ccus_model.compute_CO2_emissions() - self.ccus_model.compute_CCS_price() - - self.ccus_model.compute_carbon_storage_constraint() - outputs_dict = { - 'co2_emissions_ccus': self.ccus_model.total_co2_emissions, - 'carbon_storage_by_invest': self.ccus_model.total_carbon_storage_by_invest, - 'co2_emissions_ccus_Gt': self.ccus_model.total_co2_emissions_Gt, - 'CCS_price': self.ccus_model.CCS_price, - EnergyMix.CARBON_STORAGE_CONSTRAINT: self.ccus_model.carbon_storage_constraint, - } - - self.store_sos_outputs_values(outputs_dict) + self.ccus_model.configure_parameters(inputs_dict) + self.ccus_model.compute() + self.store_sos_outputs_values(self.ccus_model.outputs_dict) def compute_sos_jacobian(self): - - inputs_dict = self.get_sosdisc_inputs() - - years = np.arange(inputs_dict[GlossaryEnergy.YearStart], - inputs_dict[GlossaryEnergy.YearEnd] + 1) - ccs_list = inputs_dict[GlossaryEnergy.ccs_list] - scaling_factor_energy_production = inputs_dict['scaling_factor_energy_production'] - scaling_factor_energy_consumption = inputs_dict['scaling_factor_energy_consumption'] - - sub_production_dict, sub_consumption_dict = {}, {} - for ccs in ccs_list: - sub_production_dict[ccs] = inputs_dict[f'{ccs}.{GlossaryEnergy.EnergyProductionValue}'] * \ - scaling_factor_energy_production - sub_consumption_dict[ccs] = inputs_dict[f'{ccs}.{GlossaryEnergy.StreamConsumptionValue}'] * \ - scaling_factor_energy_consumption - - # -------------------------------# - # ---Resource Demand gradients---# - # -------------------------------# - resource_list = EnergyMix.RESOURCE_LIST - for ccs in ccs_list: - for resource in inputs_dict[f'{ccs}.{GlossaryEnergy.StreamConsumptionValue}']: - if resource in resource_list: - self.set_partial_derivative_for_other_types(('All_Demand', resource), ( - f'{ccs}.{GlossaryEnergy.StreamConsumptionValue}', resource), - scaling_factor_energy_consumption * np.identity( - len(years))) - - # --------------------------------# - # -- New CO2 emissions gradients--# - # --------------------------------# - - co2_emissions = self.get_sosdisc_outputs('co2_emissions_ccus') - self.ccus_model.configure_parameters_update(inputs_dict) - dtot_co2_emissions = self.ccus_model.compute_grad_CO2_emissions(co2_emissions) - - for key, value in dtot_co2_emissions.items(): - co2_emission_column = key.split(' vs ')[0] - - energy_prod_info = key.split(' vs ')[1] - energy = energy_prod_info.split('#')[0] - last_part_key = energy_prod_info.split('#')[1] - if co2_emission_column in co2_emissions.columns and energy in ccs_list: - - if last_part_key == 'prod': - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus', co2_emission_column), - (f'{energy}.{GlossaryEnergy.EnergyProductionValue}', energy), - np.identity(len(years)) * scaling_factor_energy_production * value) - elif last_part_key == 'cons': - for energy_df in ccs_list: - list_columnsenergycons = list( - inputs_dict[f'{energy_df}.{GlossaryEnergy.StreamConsumptionValue}'].columns) - if f'{energy} ({GlossaryEnergy.energy_unit})' in list_columnsenergycons: - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus', co2_emission_column), - (f'{energy_df}.{GlossaryEnergy.StreamConsumptionValue}', f'{energy} ({GlossaryEnergy.energy_unit})'), - np.identity(len(years)) * scaling_factor_energy_consumption * value) - elif last_part_key == 'co2_per_use': - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus', co2_emission_column), (f'{energy}.{GlossaryEnergy.CO2PerUse}', GlossaryEnergy.CO2PerUse), - np.identity(len(years)) * value) - - else: - very_last_part_key = energy_prod_info.split('#')[2] - - if very_last_part_key == 'prod': - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus', co2_emission_column), - (f'{energy}.{GlossaryEnergy.EnergyProductionValue}', last_part_key), - np.identity(len(years)) * scaling_factor_energy_production * value) - elif very_last_part_key == 'cons': - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus', co2_emission_column), - (f'{energy}.{GlossaryEnergy.StreamConsumptionValue}', last_part_key), - np.identity(len(years)) * scaling_factor_energy_production * value) - - ''' - CO2 emissions Gt - ''' - if co2_emission_column == f'{GlossaryEnergy.carbon_storage} Limited by capture (Mt)': - co2_emission_column_upd = co2_emission_column.replace( - '(Mt)', '(Gt)') - if last_part_key == 'prod': - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus_Gt', co2_emission_column_upd), - (f'{energy}.{GlossaryEnergy.EnergyProductionValue}', energy), - np.identity(len(years)) * scaling_factor_energy_production * value / 1.0e3) - elif last_part_key == 'cons': - for energy_df in ccs_list: - list_columnsenergycons = list( - inputs_dict[f'{energy_df}.{GlossaryEnergy.StreamConsumptionValue}'].columns) - if f'{energy} ({GlossaryEnergy.energy_unit})' in list_columnsenergycons: - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus_Gt', co2_emission_column_upd), - (f'{energy_df}.{GlossaryEnergy.StreamConsumptionValue}', f'{energy} ({GlossaryEnergy.energy_unit})'), - np.identity(len(years)) * scaling_factor_energy_consumption * value / 1.0e3) - elif last_part_key == 'co2_per_use': - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus_Gt', co2_emission_column_upd), (f'{energy}.{GlossaryEnergy.CO2PerUse}', GlossaryEnergy.CO2PerUse), - np.identity(len(years)) * value / 1.0e3) - elif energy_prod_info.startswith(f'{GlossaryEnergy.carbon_capture} for food (Mt)'): - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus_Gt', co2_emission_column_upd), - ('co2_for_food', f'{GlossaryEnergy.carbon_capture} for food (Mt)'), np.identity(len(years)) * value / 1.0e3) - - elif energy_prod_info.startswith(f'{GlossaryEnergy.carbon_capture} from energy mix (Mt)'): - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus_Gt', co2_emission_column_upd), - ('carbon_capture_from_energy_mix', f'{GlossaryEnergy.carbon_capture} from energy mix (Gt)'), - np.identity(len(years)) * value) - - elif energy_prod_info.startswith(f'{GlossaryEnergy.carbon_capture} needed by energy mix (Mt)'): - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus_Gt', co2_emission_column_upd), - ('co2_emissions_needed_by_energy_mix', f'{GlossaryEnergy.carbon_capture} needed by energy mix (Gt)'), - np.identity(len(years)) * value) - - else: - very_last_part_key = energy_prod_info.split('#')[2] - if very_last_part_key == 'prod': - - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus_Gt', co2_emission_column_upd), - (f'{energy}.{GlossaryEnergy.EnergyProductionValue}', last_part_key), - np.identity(len(years)) * scaling_factor_energy_production * value / 1.0e3) - elif very_last_part_key == 'cons': - self.set_partial_derivative_for_other_types( - ('co2_emissions_ccus_Gt', co2_emission_column_upd), - (f'{energy}.{GlossaryEnergy.StreamConsumptionValue}', last_part_key), - np.identity(len(years)) * scaling_factor_energy_production * value / 1.0e3) - - ''' - Carbon storage constraint - ''' - elif co2_emission_column == 'Carbon storage constraint': - - if last_part_key == 'prod' and energy in ccs_list: - self.set_partial_derivative_for_other_types( - (EnergyMix.CARBON_STORAGE_CONSTRAINT,), - (f'{energy}.{GlossaryEnergy.EnergyProductionValue}', energy), - scaling_factor_energy_production * value) - elif last_part_key == 'cons' and energy in ccs_list: - for energy_df in ccs_list: - list_columnsenergycons = list( - inputs_dict[f'{energy_df}.{GlossaryEnergy.StreamConsumptionValue}'].columns) - if f'{energy} ({GlossaryEnergy.energy_unit})' in list_columnsenergycons: - self.set_partial_derivative_for_other_types( - (EnergyMix.CARBON_STORAGE_CONSTRAINT,), - (f'{energy_df}.{GlossaryEnergy.StreamConsumptionValue}', f'{energy} ({GlossaryEnergy.energy_unit})'), - scaling_factor_energy_consumption * value) - elif last_part_key == 'co2_per_use': - self.set_partial_derivative_for_other_types( - (EnergyMix.CARBON_STORAGE_CONSTRAINT,), (f'{energy}.{GlossaryEnergy.CO2PerUse}', GlossaryEnergy.CO2PerUse), value) - elif energy_prod_info.startswith(f'{GlossaryEnergy.carbon_capture} for food (Mt)'): - self.set_partial_derivative_for_other_types( - (EnergyMix.CARBON_STORAGE_CONSTRAINT,), ('co2_for_food', f'{GlossaryEnergy.carbon_capture} for food (Mt)'), value) - - elif energy_prod_info.startswith(f'{GlossaryEnergy.carbon_capture} from energy mix (Mt)'): - self.set_partial_derivative_for_other_types( - (EnergyMix.CARBON_STORAGE_CONSTRAINT,), - ('carbon_capture_from_energy_mix', f'{GlossaryEnergy.carbon_capture} from energy mix (Gt)'), value * 1e3) - - elif energy_prod_info.startswith(f'{GlossaryEnergy.carbon_capture} needed by energy mix (Mt)'): - self.set_partial_derivative_for_other_types( - (EnergyMix.CARBON_STORAGE_CONSTRAINT,), - ('co2_emissions_needed_by_energy_mix', f'{GlossaryEnergy.carbon_capture} needed by energy mix (Gt)'), - value * 1e3) - - else: - very_last_part_key = energy_prod_info.split('#')[2] - if very_last_part_key == 'prod': - self.set_partial_derivative_for_other_types( - (EnergyMix.CARBON_STORAGE_CONSTRAINT,), - (f'{energy}.{GlossaryEnergy.EnergyProductionValue}', last_part_key), - scaling_factor_energy_production * value) - elif very_last_part_key == 'cons': - self.set_partial_derivative_for_other_types( - (EnergyMix.CARBON_STORAGE_CONSTRAINT,), - (f'{energy}.{GlossaryEnergy.StreamConsumptionValue}', last_part_key), - scaling_factor_energy_production * value) - - if GlossaryEnergy.carbon_capture in ccs_list: + year_start, year_end = self.get_sosdisc_inputs([GlossaryEnergy.YearStart, GlossaryEnergy.YearEnd]) + years = np.arange(year_start, year_end + 1) + d_co2_emissions = self.ccus_model.grad_co2_emissions_ccus_Gt() + for input_var, list_gradients in d_co2_emissions.items(): + for column, grad_value in list_gradients: + self.set_partial_derivative_for_other_types( + ('co2_emissions_ccus_Gt', f'{GlossaryEnergy.carbon_storage} Limited by capture (Gt)'), + (input_var, column), + grad_value) + + if GlossaryEnergy.carbon_capture in self.ccs_list: self.set_partial_derivative_for_other_types( ('CCS_price', 'ccs_price_per_tCO2'), (f'{GlossaryEnergy.carbon_capture}.{GlossaryEnergy.StreamPricesValue}', GlossaryEnergy.carbon_capture), np.identity(len(years))) - if GlossaryEnergy.carbon_storage in ccs_list: + if GlossaryEnergy.carbon_storage in self.ccs_list: self.set_partial_derivative_for_other_types( ('CCS_price', 'ccs_price_per_tCO2'), (f'{GlossaryEnergy.carbon_storage}.{GlossaryEnergy.StreamPricesValue}', GlossaryEnergy.carbon_storage), @@ -371,8 +171,7 @@ def compute_sos_jacobian(self): def get_chart_filter_list(self): chart_filters = [] - chart_list = ['CCS price', 'Carbon storage constraint', - 'CO2 storage limited by capture', 'CO2 emissions captured, used and to store'] + chart_list = ['CCS price', 'CO2 storage limited by capture', 'CO2 emissions captured, used and to store'] chart_filters.append(ChartFilter( 'Charts', chart_list, chart_list, 'charts')) @@ -402,11 +201,6 @@ def get_post_processing_list(self, filters=None): if chart_filter.filter_key == 'charts': charts = chart_filter.selected_values - if 'Carbon storage constraint' in charts: - new_chart = self.get_chart_carbon_storage_constraint() - if new_chart is not None: - instanciated_charts.append(new_chart) - if 'CCS price' in charts: new_chart = self.get_chart_CCS_price() if new_chart is not None: @@ -496,7 +290,7 @@ def get_chart_co2_limited_storage(self): ''' chart_name = 'CO2 emissions storage limited by CO2 to store' co2_emissions = self.get_sosdisc_outputs('co2_emissions_ccus') - carbon_storage_by_invest = self.get_sosdisc_outputs('carbon_storage_by_invest') + carbon_storage_by_invest = self.get_sosdisc_outputs('carbon_storage_capacity') new_chart = TwoAxesInstanciatedChart(GlossaryEnergy.Years, 'CO2 emissions (Gt)', chart_name=chart_name) @@ -508,7 +302,7 @@ def get_chart_co2_limited_storage(self): serie = InstanciatedSeries( x_serie_1, - (carbon_storage_by_invest / 1.0e3).tolist(), 'CO2 storage capacity') + (carbon_storage_by_invest['carbon_storage_capacity'] / 1.0e3).tolist(), 'CO2 storage capacity') new_chart.add_series(serie) serie = InstanciatedSeries( @@ -559,37 +353,3 @@ def get_chart_co2_emissions_sources(self): return new_chart - def get_chart_carbon_storage_constraint(self): - - co2_emissions = self.get_sosdisc_outputs('co2_emissions_ccus_Gt') - - carbon_storage_limit = self.get_sosdisc_inputs('carbonstorage_limit') - years = list(co2_emissions[GlossaryEnergy.Years]) - - chart_name = 'Cumulated carbon storage (Gt) ' - - year_start = years[0] - year_end = years[len(years) - 1] - - new_chart = TwoAxesInstanciatedChart(GlossaryEnergy.Years, 'Cumulated carbon storage (Gt)', - chart_name=chart_name) - - visible_line = True - - new_series = InstanciatedSeries( - years, list(co2_emissions[f'{GlossaryEnergy.carbon_storage} Limited by capture (Gt)'].cumsum().values), - 'cumulative sum of carbon capture (Gt)', 'lines', visible_line) - - new_chart.series.append(new_series) - - # Rockstrom Limit - - ordonate_data = [carbon_storage_limit / 1e3] * int(len(years) / 5) - abscisse_data = np.linspace( - year_start, year_end, int(len(years) / 5)) - new_series = InstanciatedSeries( - abscisse_data.tolist(), ordonate_data, 'Carbon storage limit (Gt)', 'scatter') - - new_chart.series.append(new_series) - - return new_chart diff --git a/energy_models/core/energy_mix/energy_mix.py b/energy_models/core/energy_mix/energy_mix.py index 81099ebe..457372b1 100644 --- a/energy_models/core/energy_mix/energy_mix.py +++ b/energy_models/core/energy_mix/energy_mix.py @@ -78,7 +78,6 @@ class EnergyMix(BaseStream): RESOURCE_LIST = ['natural_gas_resource', 'uranium_resource', 'coal_resource', 'oil_resource', 'copper_resource'] # , 'platinum_resource',] RESOURCE_CONSUMPTION_UNIT = ResourceGlossary.UNITS['consumption'] - CARBON_STORAGE_CONSTRAINT = 'carbon_storage_constraint' energy_class_dict = {GaseousHydrogen.name: GaseousHydrogen, LiquidFuel.name: LiquidFuel, HydrotreatedOilFuel.name: HydrotreatedOilFuel, diff --git a/energy_models/core/energy_mix/energy_mix_disc.py b/energy_models/core/energy_mix/energy_mix_disc.py index 7d96a50c..d2e81054 100644 --- a/energy_models/core/energy_mix/energy_mix_disc.py +++ b/energy_models/core/energy_mix/energy_mix_disc.py @@ -783,12 +783,6 @@ def compute_sos_jacobian(self): -scaling_factor_energy_consumption * np.identity( len(years)) / scaling_factor_energy_production * scaling_factor_energy_production * 0) - self.set_partial_derivative_for_other_types( - (GlossaryEnergy.StreamProductionDetailedValue, f'production {GlossaryEnergy.carbon_storage} ({GlossaryEnergy.unit_dicts[GlossaryEnergy.carbon_storage]})'), - (f'{GlossaryEnergy.carbon_storage}.{GlossaryEnergy.StreamConsumptionValue}', f'{GlossaryEnergy.carbon_capture} ({GlossaryEnergy.unit_dicts[GlossaryEnergy.carbon_capture]})'), - -scaling_factor_energy_consumption * np.identity( - len(years)) / scaling_factor_energy_production * scaling_factor_energy_production) - # -------------------------# # ---- Prices gradients----# # -------------------------# diff --git a/energy_models/core/techno_type/base_techno_models/carbon_storage_techno.py b/energy_models/core/techno_type/base_techno_models/carbon_storage_techno.py index 88153cf7..c2b4994a 100644 --- a/energy_models/core/techno_type/base_techno_models/carbon_storage_techno.py +++ b/energy_models/core/techno_type/base_techno_models/carbon_storage_techno.py @@ -28,9 +28,6 @@ def __init__(self, name): def compute_capital_recovery_factor(self, data_config): return 1 - def compute_other_streams_needs(self): - self.cost_details[f'{GlossaryEnergy.carbon_capture}_needs'] = 1. - def check_capex_unity(self, data_tocheck): """ Put all capex in $/kgCO2 diff --git a/energy_models/glossaryenergy.py b/energy_models/glossaryenergy.py index 54d5fce4..4489b42c 100644 --- a/energy_models/glossaryenergy.py +++ b/energy_models/glossaryenergy.py @@ -1128,13 +1128,13 @@ class GlossaryEnergy(GlossaryWitnessCore): WetCropResidues: [electricity], # transport fuel in stead of elec Geothermal: [f"{heat}.{mediumtemperatureheat}"], # just electricity BiomassBuryingFossilization: [biomass_dry], # add transport fuel - DeepOceanInjection: [carbon_capture], # add transport fuel - DeepSalineFormation: [carbon_capture], # add transport fuel - DepletedOilGas: [carbon_capture], # add transport fuel - EnhancedOilRecovery: [carbon_capture], # add transport fuel - GeologicMineralization: [carbon_capture], # add transport fuel - CarbonStorageTechno: [carbon_capture], - CropEnergy: [carbon_capture], + DeepOceanInjection: [], # add transport fuel + DeepSalineFormation: [], # add transport fuel + DepletedOilGas: [], # add transport fuel + EnhancedOilRecovery: [], # add transport fuel + GeologicMineralization: [], # add transport fuel + CarbonStorageTechno: [], + CropEnergy: [], } # dict of resources used by technos diff --git a/energy_models/sos_processes/energy/MDA/energy_process_v0/usecase.py b/energy_models/sos_processes/energy/MDA/energy_process_v0/usecase.py index 38b05f09..1e75f1e9 100644 --- a/energy_models/sos_processes/energy/MDA/energy_process_v0/usecase.py +++ b/energy_models/sos_processes/energy/MDA/energy_process_v0/usecase.py @@ -184,14 +184,6 @@ def setup_constraints(self): list_aggr_type.append(FunctionManager.AGGR_TYPE_SMAX) list_namespaces.append(GlossaryEnergy.NS_FUNCTIONS) - if GlossaryEnergy.carbon_storage in self.ccs_list: - list_var.extend(["carbon_storage_constraint"]) - list_parent.extend([""]) - list_ftype.extend([FunctionManagerDisc.INEQ_CONSTRAINT]) - list_weight.extend([0.0]) - list_aggr_type.append(FunctionManager.AGGR_TYPE_SMAX) - list_namespaces.append(GlossaryEnergy.NS_FUNCTIONS) - list_var.extend([EnergyMix.TOTAL_PROD_MINUS_MIN_PROD_CONSTRAINT_DF]) list_parent.extend(["Energy_constraints"]) list_ftype.extend([FunctionManagerDisc.INEQ_CONSTRAINT]) diff --git a/energy_models/tests/jacobian_pkls/jacobian_CCUS_disc.pkl b/energy_models/tests/jacobian_pkls/jacobian_CCUS_disc.pkl index 68674112..7dec4b14 100644 Binary files a/energy_models/tests/jacobian_pkls/jacobian_CCUS_disc.pkl and b/energy_models/tests/jacobian_pkls/jacobian_CCUS_disc.pkl differ diff --git a/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DeepOceanInjection.pkl b/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DeepOceanInjection.pkl index a7aaae2b..099e69d4 100644 Binary files a/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DeepOceanInjection.pkl and b/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DeepOceanInjection.pkl differ diff --git a/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DeepSalineFormation.pkl b/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DeepSalineFormation.pkl index 4aad318a..ff17d7df 100644 Binary files a/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DeepSalineFormation.pkl and b/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DeepSalineFormation.pkl differ diff --git a/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DepletedOilGas.pkl b/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DepletedOilGas.pkl index f9dd2337..ed1517cd 100644 Binary files a/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DepletedOilGas.pkl and b/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_DepletedOilGas.pkl differ diff --git a/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_EnhancedOilRecovery.pkl b/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_EnhancedOilRecovery.pkl index b2ce0629..b63db60c 100644 Binary files a/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_EnhancedOilRecovery.pkl and b/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_EnhancedOilRecovery.pkl differ diff --git a/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_GeologicMineralization.pkl b/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_GeologicMineralization.pkl index 67fdb4e2..91271435 100644 Binary files a/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_GeologicMineralization.pkl and b/energy_models/tests/jacobian_pkls/jacobian_carbon_storage_GeologicMineralization.pkl differ diff --git a/energy_models/tests/l1_test_gradient_ccus_disc.py b/energy_models/tests/l1_test_gradient_ccus_disc.py index 1bca56be..04eb493d 100644 --- a/energy_models/tests/l1_test_gradient_ccus_disc.py +++ b/energy_models/tests/l1_test_gradient_ccus_disc.py @@ -182,8 +182,7 @@ def test_01_Consumption_ccus_disciplinejacobian(self): f'{self.name}.{GlossaryEnergy.carbon_storage}.{GlossaryEnergy.LandUseRequiredValue}', ] coupled_outputs = [f'{self.name}.co2_emissions_ccus_Gt', - f'{self.name}.CCS_price', - f'{self.name}.carbon_storage_constraint'] + f'{self.name}.CCS_price',] self.check_jacobian(location=dirname(__file__), filename=f'jacobian_{self.model_name}.pkl', discipline=disc, step=1.0e-18, derr_approx='complex_step', threshold=1e-5,