Advanced Tutorial
mescal is a Brightway-powered Python package that helps you to integrate Life-Cycle Assessment (LCA) in your energy system model
Set up
[1]:
%load_ext autoreload
%autoreload 2
[2]:
# Import the required libraries
from mescal import *
import pandas as pd
import bw2data as bd
[3]:
ecoinvent_version = '3.10.1' # choose the ecoinvent version you wish to use
esm_location = 'CH' # choose the version of energyscope for which you want to generate metrics
esm_year = 2050 # choose the year of the energyscope snapshot model or transition pathway time-step
spatialized_database = True # set to True if you want to use your spatialized version of ecoinvent
regionalize_foregrounds = ['Operation', 'Resource'] # set to 'all' if you want to regionalize the foreground inventories of all types of LCI datasets
premise_iam = 'image' # choose the IAM to which the premise database is linked
premise_ssp_rcp = 'SSP2-RCP26' # choose the SSP/RCP scenario to which the premise database is linked
[4]:
# Set the name of your main LCI database (e.g., ecoinvent or premise database) here:
name_premise_db = f"ecoinvent_cutoff_{ecoinvent_version}_{premise_iam}_{premise_ssp_rcp}_{esm_year}"
name_biosphere_db = 'biosphere3'
if spatialized_database:
name_premise_db += ' regionalized'
name_spatialized_biosphere_db = 'biosphere3_spatialized_flows'
else:
name_spatialized_biosphere_db = None
[5]:
# Set the name of the new database with the ESM results
esm_db_name = f'EnergyScope_{esm_location}_{esm_year}'
[6]:
# Main version of ecoinvent (without .1 if any)
ecoinvent_main_version = '.'.join(ecoinvent_version.split('.')[:2])
[7]:
# Set the list of LCIA methods for which you want indicators (they must be registered in your brightway project)
if spatialized_database:
lcia_methods=[
f'IMPACT World+ Midpoint 2.1_regionalized for ecoinvent v{ecoinvent_main_version}',
f'IMPACT World+ Damage 2.1_regionalized for ecoinvent v{ecoinvent_main_version}',
]
else:
lcia_methods=[
f'IMPACT World+ Midpoint 2.1 for ecoinvent v{ecoinvent_main_version}',
f'IMPACT World+ Damage 2.1 for ecoinvent v{ecoinvent_main_version}',
f'IMPACT World+ Footprint 2.1 for ecoinvent v{ecoinvent_main_version}',
]
Note: you can download IMPACT World+ methods in your brightway project following this notebook. Regionalized versions of ecoinvent and IMPACT World+ methods can be downloaded from this repository.
[8]:
# Set up your Brightway project
bd.projects.set_current(f'ecoinvent{ecoinvent_version}') # put the name of your brightway project here
Load databases
[9]:
premise_db = Database(name_premise_db, create_pickle=True)
2025-12-26 12:17:08,346 - Database - INFO - Loaded ecoinvent_cutoff_3.10.1_image_SSP2-RCP26_2050 regionalized from pickle!
[10]:
if regionalize_foregrounds is not None and spatialized_database:
spatialized_biosphere_db = Database(name_spatialized_biosphere_db)
else:
spatialized_biosphere_db = None
Getting activity data
100%|██████████| 110559/110559 [00:01<00:00, 85887.84it/s]
Adding exchange data to activities
0it [00:00, ?it/s]
Filling out exchange data
100%|██████████| 110559/110559 [00:00<00:00, 3465962.51it/s]
2025-12-26 12:17:14,936 - Database - INFO - Loaded biosphere3_spatialized_flows from brightway!
Your data
For mescal to understand the structure of your energy system model, you need to provide it with a set of dataframes.
Mandatory dataframes
[11]:
mapping = pd.read_csv(f'../dev/energyscope_data/{esm_location}/mapping.csv')
unit_conversion = pd.read_csv(f'../dev/energyscope_data/{esm_location}/unit_conversion.csv')
mapping_esm_flows_to_CPC = pd.read_csv(f'../dev/energyscope_data/{esm_location}/mapping_esm_flows_to_CPC.csv')
model = pd.read_csv(f'../dev/energyscope_data/{esm_location}/model.csv')
A mapping between the energy technologies and resources of the energy system model, and Life-Cycle Inventories datasets (LCI) from an LCI database (e.g., ecoinvent). The mapping should be provided in a dataframe with the following columns:
Name: the name of the energy technology or resource in the energy modelType: the type of the energy technology or resource (i.e., ‘Construction’, ‘Decommission’, ‘Operation’, ‘Resource’ or ‘Flow’)Product: the name of the product of the energy technology or resource in the LCI databaseActivity: the name of the activity of the energy technology or resource in the LCI databaseLocation: the name of the location of the energy technology or resource in the LCI databaseUnit: (optional) the physical unit of the energy technology or resource in the LCI databaseDatabase: the name of the database in your brightway project
If you wish to change the version of ecoinvent used in your mapping file, you can follow this notebook.
[12]:
mapping.head()
[12]:
| Name | Type | Product | Activity | Location | Database | |
|---|---|---|---|---|---|---|
| 0 | ALKALINE_ELECTROLYSIS | Operation | hydrogen, gaseous, 20 bar | hydrogen production, gaseous, 20 bar, from AEC... | CH | ecoinvent_cutoff_3.10.1_image_SSP2-RCP26_2050 ... |
| 1 | ALKALINE_ELECTROLYSIS_PLANT | Construction | electrolyzer, 1MWe, AEC, Balance of Plant | electrolyzer production, 1MWe, AEC, Balance of... | RER | ecoinvent_cutoff_3.10.1_image_SSP2-RCP26_2050 ... |
| 2 | ALKALINE_ELECTROLYSIS_PLANT | Decommission | used fuel cell balance of plant, 1MWe, AEC | treatment of fuel cell balance of plant, 1MWe,... | RER | ecoinvent_cutoff_3.10.1_image_SSP2-RCP26_2050 ... |
| 3 | ALKALINE_ELECTROLYSIS_STACK | Construction | electrolyzer, 1MWe, AEC, Stack | electrolyzer production, 1MWe, AEC, Stack | RER | ecoinvent_cutoff_3.10.1_image_SSP2-RCP26_2050 ... |
| 4 | ALKALINE_ELECTROLYSIS_STACK | Decommission | used fuel cell stack, 1MWe, AEC | treatment of fuel cell stack, 1MWe, AEC | RER | ecoinvent_cutoff_3.10.1_image_SSP2-RCP26_2050 ... |
A set of unit conversion factors between the energy system model and the LCI database. The conversion factors should be provided in a dataframe with the following columns:
Name: the name of the energy technology or resource in the energy system modelType: the type of the energy technology or resource (i.e., ‘Construction’, ‘Decommission’, ‘Operation’, ‘Resource’, or ‘Other’). Other types can be added to fit your needs. The ‘Other’ category is meant for the unit conversion that are not specific to a technology or resource, but rather a generic type of product, e.g., conversion from kilogram to kilowatt hour for natural gas.Value: the numerical value of the conversion factor that will multiply the impact scores. It actually denotes the conversion from Impact / LCA unit to Impact / ESM unit.ESM: the unit of the energy technology or resource in the ESMLCA: the target unit of the energy technology or resource in the LCA databaseAssumptions & Sources(optional): additional information on the conversion factor.
The LCA and ESM columns should respect the ecoinvent naming convention. You may use the ecoinvent_unit_convention function to convert the unit naming convention you’ve used to the one of ecoinvent.
[13]:
unit_conversion.head()
[13]:
| Name | Type | Value | LCA | ESM | |
|---|---|---|---|---|---|
| 0 | ACETIC_ACID | Resource | 0.247423 | kilogram | kilowatt hour |
| 1 | ACETONE | Resource | 0.121655 | kilogram | kilowatt hour |
| 2 | ALKALINE_ELECTROLYSIS | Construction | 0.001556 | unit | kilowatt |
| 3 | ALKALINE_ELECTROLYSIS | Decommission | 0.001556 | unit | kilowatt |
| 4 | ALKALINE_ELECTROLYSIS | Operation | 0.030030 | kilogram | kilowatt hour |
A mapping between the energy system model flows and CPC categories. The mapping should be provided in a dataframe with the following columns:
Flow: the name of the flow in the ESMDescription: (optional) the description of the productCPC: the list of names of corresponding CPC categories
[14]:
mapping_esm_flows_to_CPC.head()
[14]:
| Flow | Description | CPC | |
|---|---|---|---|
| 0 | BENZENE | Benzene | ['33100: Coke and semi-coke of coal, of lignit... |
| 1 | BIO_DIESEL | Bio-diesel | ['35491: Biodiesel'] |
| 2 | CO2_A | Carbon dioxide (concentrated emissions) | ['34210b: Carbon dioxide and monoxide'] |
| 3 | CO2_C | Carbon dioxide (captured) | ['34210b: Carbon dioxide and monoxide'] |
| 4 | CO2_CS | Carbon dioxide (captured & stored) | ['34210b: Carbon dioxide and monoxide'] |
The input and output flows of energy technologies. For a given technology, the inputs and outputs should be given with the same unit. Also, the main output flow should have 1 as an amount, i.e., all other flows as scaled with respect to the main output. It should be provided in a dataframe with the following columns:
Name: the name of the energy technology in the energy system modelFlow: the name of the input or output flowAmount: the numerical value of the flow (negative if input, positive if output)
[15]:
model.head()
[15]:
| Name | Flow | Amount | |
|---|---|---|---|
| 0 | ACETIC_ACID | ACETIC_ACID | 1.00 |
| 1 | ACETONE | ACETONE | 1.00 |
| 2 | ALKALINE_ELECTROLYSIS | ELECTRICITY_HV | -1.72 |
| 3 | ALKALINE_ELECTROLYSIS | H2_MP | 1.00 |
| 4 | ALKALINE_ELECTROLYSIS | HEAT_LOW_T_DECEN | 0.26 |
Optional dataframes
[16]:
technology_compositions = pd.read_csv(f'../dev/energyscope_data/{esm_location}/technology_compositions.csv')
technology_specifics = pd.read_csv(f'../dev/energyscope_data/{esm_location}/technology_specifics.csv')
lifetime = pd.read_csv(f'../dev/energyscope_data/{esm_location}/lifetime.csv')
efficiency = pd.read_csv(f'../dev/energyscope_data/{esm_location}/efficiency.csv')
mapping_product_to_CPC = pd.read_csv('../mescal/data/mapping_new_products_to_CPC.csv')
impact_abbrev = pd.read_csv('../dev/lcia/impact_abbrev.csv')
technologies_to_remove_from_layers = pd.read_csv(f'../dev/energyscope_data/{esm_location}/technologies_to_remove_from_layers.csv')
new_end_use_types = pd.read_csv(f'../dev/energyscope_data/{esm_location}/new_end_use_types.csv')
results_from_esm = pd.read_csv(f'../dev/energyscope_data/{esm_location}/results_ES.csv')
A set of composition of technologies, i.e., if one technology or resource in the energy model should be represented by a combination of LCI datasets. The composition should be provided in a dataframe with the following columns:
Name: the name of the main energy technology or resource in the energy modelComponents: the list of names of subcomponentsType(optional): to distinguish between ‘Construction’ and ‘Decommission’ components (if not provided, all components are considered as ‘Construction’ components)
[17]:
technology_compositions.head()
[17]:
| Name | Components | Type | |
|---|---|---|---|
| 0 | AN_DIG_SI | ['AN_DIG_SI_PLANT', 'AN_DIG_SI_COGEN'] | Construction |
| 1 | DEC_COGEN_GAS | ['DEC_COGEN_GAS_CHP', 'DEC_COGEN_GAS_HEAT', 'D... | Construction |
| 2 | DEC_COGEN_OIL | ['DEC_COGEN_OIL_BOILER', 'DEC_COGEN_OIL_TURBIN... | Construction |
| 3 | DEC_COGEN_WOOD | ['DEC_COGEN_WOOD_BOILER', 'DEC_COGEN_WOOD_TURB... | Construction |
| 4 | DHN_COGEN_GAS | ['DHN_COGEN_GAS_CHP', 'DHN_COGEN_GAS_HEAT', 'D... | Construction |
A set of technologies with specific requirements. For instance, this stands for energy technologies without a construction phase, mobility technologies (if mismatch fuel in the LCI dataset), bio-processes (if mismatch fuel in the LCI dataset), etc. The requirements should be provided in a dataframe with the following columns:
Name: the name of the energy technology in the energy modelSpecifics: the type of requirement. Can be ‘Background search’ (i.e., double-counting removal is run n levels in the background, n being defined in Amount), ‘Mobility’ (i.e., EUD types for which associated technologies are characterized as a mobility mean, to further add a FUEL layer), ‘No background search’ (i.e., double-counting removal is not applied beyond the activity direct exchanges), ‘No double-counting removal’ (i.e., the double-counting removal step is skipped), ‘Process’ (i.e., the technology is characterized as an industrial bio-process, to further add a PROCESS_FUEL layer), ‘DAC’ (for premise DAC technologies, to change the biogenic carbon flow into a fossil one), ‘Biofuel’ (i.e., adapt direct emissions to the biofuel input: partially change fossil carbon emissions into biogenic ones), ‘Import/Export’ (these activities will keep their original locations and will not be regionalized), ‘Carbon flows’ (the direct carbon emissions of these activities will be multiplied by a factor), ‘Add CC’ (add a carbon capture process to a technology, and modifies its direct carbon dioxide emissions according to the capture efficiency), ‘Add CO2 (flow_type)’ (add a resource/emission flow of CO2 to an activity, flow_type can be ‘fossil’, ‘non-fossil’, ‘from soil or biomass stock’, ‘in air’, or ‘non-fossil, resource correction’).Amount: the numerical value of the requirement (if relevant)
[18]:
technology_specifics.head()
[18]:
| Name | Specifics | Amount | Comment | |
|---|---|---|---|---|
| 0 | CO2_TO_JETFUELS | Background search | 0.0 | NaN |
| 1 | CO2-To-Diesel | Background search | 2.0 | NaN |
| 2 | CROPS_TO_JETFUELS | Background search | 4.0 | NaN |
| 3 | FT | Background search | 3.0 | NaN |
| 4 | GASIFICATION_SNG | Background search | 3.0 | NaN |
Energy technologies lifetimes in the ESM and the LCI database. For composition of technologies (if any), the main technology should not have a LCA lifetime (no LCI dataset is associated to it), while the sub-components should not have an ESM lifetime (they do not have a proper technology in the ESM). The lifetimes should be provided in a dataframe with the following columns:
Name: the name of the energy technology in the energy system modelESM: the numerical value of the lifetime in the energy system model (if relevant)LCA: the numerical value of the lifetime in the LCI database (if relevant)Comment: (optional) additional information theAmountcomputation (if relevant)
[19]:
lifetime.head()
[19]:
| Name | ESM | LCA | |
|---|---|---|---|
| 0 | ALKALINE_ELECTROLYSIS | 10.0 | NaN |
| 1 | ALKALINE_ELECTROLYSIS_PLANT | NaN | 20.0 |
| 2 | ALKALINE_ELECTROLYSIS_STACK | NaN | 7.5 |
| 3 | AL_MAKING | 25.0 | 50.0 |
| 4 | AL_MAKING_HR | 25.0 | 50.0 |
If you want to account for the possible efficiency differences between the ESM and the LCI datasets, you can provide a set of (technologies, flow) couples for which efficiency differences will be corrected by the scaling the elementary flows. Taking the example of the diesel car, the couple should be something like (‘CAR_DIESEL’, ‘DIESEL’), the flow being the fuel of the technology responsible for direct emissions. Relevant biosphere flows will be the ratio between the LCA and ESM efficiencies. The (technology, flow) couples should be provided in a dataframe with the following columns:
Name: the name of the energy technology in the ESMFlow: the name of the flow in the ESMComment: (optional) additional information on the set ofFlows
[20]:
efficiency.head()
[20]:
| Name | Flow | Comment | |
|---|---|---|---|
| 0 | AN_DIG | ['WET_BIOMASS'] | NaN |
| 1 | AN_DIG_SI | ['WET_BIOMASS'] | NaN |
| 2 | BUS_CNG_STOICH | ['NG_HP'] | NaN |
| 3 | BUS_DIESEL | ['DIESEL'] | NaN |
| 4 | BUS_FC_HYBRID_CH4 | ['NG_HP'] | NaN |
In case you have a LCI database (partially) without CPC categories (which are necessary for the double-counting check), you can provide a mapping between the products and activities in the LCI database and the CPC categories. The mapping should be provided in a dataframe with the following columns:
Name: the full or partial name of the product or activity in the LCI databaseCPC: the number and name of the corresponding CPC categorySearch type: can be ‘equals’ if theNameentry is an exactly the name to look for, or ‘contains’ if it is contained in the full nameWhere: can be ‘Product’ or ‘Activity’, whether theNameentry is meant for products or activities
[21]:
mapping_product_to_CPC.head()
[21]:
| Name | CPC | Search type | Where | |
|---|---|---|---|---|
| 0 | amine-based silica | 35310: Organic surface active agents, except soap | equals | Product |
| 1 | biodiesel | 35491: Biodiesel | contains | Product |
| 2 | biogas | 17200: Coal gas, water gas, producer gas and s... | equals | Product |
| 3 | biomass, used as fuel | 31230: Wood in chips or particles | equals | Product |
| 4 | biomethane, from biogas upgrading, using amine... | 12020: Natural gas, liquefied or in the gaseou... | equals | Product |
An abbreviation scheme for the impact categories you aim to work with, to ease the readability in the ESM. The one of IMPACT World+ is available in this csv file. The abbreviations should be provided in a dataframe with the following columns:
Impact_category: the name of the impact category, expressed as a tuple following brightway conventionUnit: (optional) the unit of the impact categoryAbbrev: the abbreviation of the impact categoryAoP: the area of protection of the impact category. The normalization of indicators will be performed based of AoPs. If you do not have AoPs (e.g., midpoint-level indicators), set as many AoPs as you have impact categories.
[22]:
impact_abbrev.head()
[22]:
| Impact_category | Unit | Abbrev | AoP | |
|---|---|---|---|---|
| 0 | ('IMPACT World+ Damage 2.0.1', 'Ecosystem qual... | PDF.m2.yr | CCEQL | EQ |
| 1 | ('IMPACT World+ Damage 2.0.1', 'Ecosystem qual... | PDF.m2.yr | CCEQS | EQ |
| 2 | ('IMPACT World+ Damage 2.0.1', 'Ecosystem qual... | PDF.m2.yr | CCEQLB | EQ |
| 3 | ('IMPACT World+ Damage 2.0.1', 'Ecosystem qual... | PDF.m2.yr | CCEQSB | EQ |
| 4 | ('IMPACT World+ Damage 2.0.1', 'Human health',... | DALY | CCHHL | HH |
In case you want to remove some energy technologies from energy layers in the results LCI datasets to be added to the LCI database, you can provide a list of technologies to remove. The list should be provided in a dataframe with the following columns:
Layers: name of the layer(s). A layer is basically an energy vector, which is an output for some energy technologies and an input for some others.Technologies: the name of the energy technology to remove from the layer(s)Comment: (optional) a comment on the removal
[23]:
technologies_to_remove_from_layers.head()
[23]:
| Layers | Technologies | Comment | |
|---|---|---|---|
| 0 | ['ELECTRICITY_EHV', 'ELECTRICITY_HV'] | ['TRAFO_HE','TRAFO_EH'] | The high and extra high voltage electricity ar... |
| 1 | ['ELECTRICITY_LV'] | ['STO_ELEC'] | The storage technologies should be removed (pr... |
| 2 | ['NG_HP', 'NG_EHP'] | ['NG_EXP_EH', 'NG_EXP_EH_COGEN', 'NG_COMP_HE',... | The high and extra high pressure natural gas a... |
| 3 | ['H2_LP', 'H2_MP', 'H2_HP', 'H2_EHP'] | ['H2_COMP_HE', 'H2_COMP_MH', 'H2_COMP_LM', 'H2... | All pressure levels for hydrogen are merged in... |
| 4 | ['HEAT_HIGH_T', 'HEAT_LOW_T_DHN'] | ['HT_LT'] | The high and low heat production at the DHN le... |
If you want to reformat your end-use types (e.g., output layer) in order to better fit the LCI database, you can provide a list of new end-use types. The list should be provided in a dataframe with the following columns:
Name: name of technologies for which the end-use type should be changedSearch type: whether theNameentry is an exactly the name to look for (‘equals’), is contained in the full name (‘contains’), or is the beginning of the full name (‘startswith’)Old: the current end-use typeNew: the new end-use type
[24]:
new_end_use_types.head()
[24]:
| Name | Search type | Old | New | |
|---|---|---|---|---|
| 0 | BUS | startswith | MOB_PUBLIC | MOB_PUBLIC_BUS |
| 1 | SCHOOLBUS | startswith | MOB_PUBLIC | MOB_PUBLIC_SCHOOLBUS |
| 2 | COACH | startswith | MOB_PUBLIC | MOB_PUBLIC_COACH |
| 3 | TRAIN | startswith | MOB_PUBLIC | MOB_PUBLIC_TRAIN |
| 4 | CAR | startswith | MOB_PRIVATE | MOB_PRIVATE_CAR |
If you want to inject the results of your ESM back in the LCI database, you should provide the results in a dataframe with the following columns:
Name: the name of the energy technology in the ESMProduction: the annual production value of the energy technology in the ESM. All values should be provided with the same unit.Capacity: the installed capacity of the energy technology in the ESM. All values should be provided with the same unit.Year: the year of the ESM snapshot or transition pathway time-step.Year_inst: the year of installation of the energy technology. This is only required forPathwayESMifoperation_metrics_for_all_time_stepsis True.
[25]:
results_from_esm.head()
[25]:
| Name | Production | Capacity | Year | Year_inst | |
|---|---|---|---|---|---|
| 0 | CAR_BEV_MEDRANGE_LOCAL | 44880.00000 | 5.123288 | 2020 | 2020 |
| 1 | CAR_BEV_MEDRANGE_LONGD | 29920.00000 | 3.415525 | 2020 | 2020 |
| 2 | CCGT_CC | 1622.23145 | 0.217866 | 2020 | 2020 |
| 3 | COACH_EV | 17952.00000 | 2.049315 | 2020 | 2020 |
| 4 | COMMUTER_RAIL_ELEC | 44880.00000 | 5.123288 | 2020 | 2020 |
Optional geographical data
[26]:
# sufficient match within ecoinvent
if esm_location == 'CA-QC':
accepted_locations = ['CA-QC', 'CA']
elif esm_location == 'CH':
accepted_locations = ['CH']
elif esm_location == 'core':
accepted_locations = ['RER', 'WEU', 'EUR']
else:
accepted_locations = ['GLO', 'RoW']
[27]:
# Define the user-defined ranking
if esm_location == 'CA-QC':
my_ranking = [
'CA-QC', # Quebec
'CA', # Canada
'CAN', # Canada in IMAGE
'CAZ', # Canada - Australia - New Zealand in REMIND
'RNA', # North America
'US', # United States
'USA', # United States in REMIND and IMAGE
'GLO', # Global average
'RoW', # Rest of the world
]
elif esm_location == 'CH':
my_ranking = [
'CH',
'NEU',
'EUR',
'WEU',
'RER',
'IAI Area, EU27 & EFTA',
'GLO',
'RoW'
]
elif esm_location == 'core':
my_ranking = [
'RER',
'WEU',
'EUR',
'IAI Area, EU27 & EFTA',
'CH',
'BE',
'IT',
'GLO',
'RoW',
]
else:
my_ranking = [
'GLO',
'RoW',
]
Create a new database with additional CPC categories (optional)
In case you are working with a LCI database without CPC categories, you can create a new database with the CPC categories. The function create_new_database_with_CPC_categories takes as input the database with missing CPC categories, the name of the new database, and a mapping between the products and activities in the LCI database and the CPC categories. It creates a new database with the CPC categories. This step can take a few minutes depending on the size of the database.
[28]:
# If necessary, add missing CPC categories to the database
premise_db.add_CPC_categories(overwrite_existing_CPC=True)
Initialize the ESM database
[29]:
mapping.Database = name_premise_db
[30]:
esm = ESM(
# Mandatory inputs
mapping=mapping,
unit_conversion=unit_conversion,
model=model,
mapping_esm_flows_to_CPC_cat=mapping_esm_flows_to_CPC,
main_database=premise_db,
esm_db_name=esm_db_name,
biosphere_db_name=name_biosphere_db,
esm_location=esm_location if esm_location != 'core' else 'RER',
# Optional inputs
technology_compositions=technology_compositions,
tech_specifics=technology_specifics,
lifetime=lifetime,
efficiency=efficiency,
regionalize_foregrounds=regionalize_foregrounds,
accepted_locations=accepted_locations,
locations_ranking=my_ranking,
spatialized_biosphere_db=spatialized_biosphere_db,
results_path_file=f'results/energyscope_{esm_location}/{esm_year}/',
remove_double_counting_to=['Operation', 'Construction'],
extract_eol_from_construction=True,
stop_background_search_when_first_flow_found=True,
)
Add or replace the location column based on a user-defined ranking (optional)
Based on a user-defined ranking, the location column of the mapping dataframe can be updated. The function change_location_mapping_file takes as input the mapping dataframe, the user-defined ranking, and the base database. It returns the mapping dataframe with the location column updated.
[31]:
# Update mapping dataframe with better locations
esm.change_location_mapping_file()
[32]:
esm.main_database.test_mapping_file(esm.mapping)
2025-12-26 12:17:27,811 - Database - INFO - Mapping successfully linked to the database
[32]:
[]
Perform basic tests on input data
[33]:
esm.clean_inputs()
[34]:
esm.check_inputs()
2025-12-26 12:17:28,178 - Mescal - WARNING - List of technologies or resources that are in the model file but not in the mapping file. Their impact scores will be set to the default value: ['CARBON_CAPTURE', 'CO2_CS', 'CO2_E', 'DEC_RENOVATION', 'DHN_RENOVATION', 'DIESEL_S', 'ELEC_S', 'GASOLINE_S', 'H2_S', 'HT_LT', 'LT_DEC_WH', 'LT_DHN_WH', 'NG_S', 'PLANE', 'RES_GEO', 'RES_HYDRO', 'RES_SOLAR', 'RES_WIND', 'STO_CO2', 'STO_DIE', 'STO_ELEC', 'STO_GASO', 'STO_H2', 'STO_NG']
2025-12-26 12:17:28,195 - Mescal - WARNING - List of technologies or resources that are in the mapping file but not in the model file (this will not be a problem in the workflow): ['BATTERY', 'CAR_BEV_LOWRANGE', 'CAR_DME_D10_LOCAL', 'CAR_DME_D10_LONGD', 'DEC_TH_STORAGE', 'DHN_TH_STORAGE', 'EHP_H2_GRID', 'EHP_NG_GRID', 'EHV_GRID', 'HP_H2_GRID', 'HP_NG_GRID', 'HV_GRID', 'LP_H2_GRID', 'LP_NG_GRID', 'LV_GRID', 'MP_H2_GRID', 'MP_NG_GRID', 'MV_GRID']
2025-12-26 12:17:28,320 - Mescal - WARNING - List of flows that are in the mapping file but not in the unit conversion file. It might be an issue if unit conversions are required during the efficiency correction step: ['ALUMINUM', 'FOOD', 'HEAT_LOW_T_DECEN', 'HEAT_LOW_T_DHN', 'MOB_FREIGHT_LCV', 'MOB_FREIGHT_SEMI', 'MOB_FREIGHT_TRAIN', 'MOB_FREIGHT_TRUCK', 'MOB_PRIVATE_CAR', 'MOB_PRIVATE_SUV', 'MOB_PUBLIC_BUS', 'MOB_PUBLIC_COACH', 'MOB_PUBLIC_TRAIN', 'PAPER', 'STEEL', 'XYLENE']
2025-12-26 12:17:28,345 - Mescal - WARNING - Some technologies have no lifetime value for LCA in the lifetime file. Therefore, lifetime harmonization with the ESM will not be performed during the LCIA phase and capacity factor harmonization during the feedback of ESM results will not be performed either for those technologies: ['BATTERY', 'DEC_DIRECT_ELEC', 'DEC_SOLAR', 'DEEP_SALINE', 'DHN_DEEP_GEO', 'DIRECT_USAGE', 'DOGR', 'EHP_H2_GRID', 'ELEC_STO', 'EOR', 'GEOTHERMAL', 'H2_EXP_EH', 'H2_EXP_EH_COGEN', 'H2_EXP_HM', 'H2_EXP_HM_COGEN', 'H2_EXP_ML', 'H2_EXP_ML_COGEN', 'HP_H2_GRID', 'IND_DIRECT_ELEC', 'LP_H2_GRID', 'MINES_STORAGE', 'MP_H2_GRID', 'NG_COMP_HE', 'NG_COMP_LM', 'NG_COMP_MH', 'NG_EXP_EH', 'NG_EXP_EH_COGEN', 'NG_EXP_HM', 'NG_EXP_HM_COGEN', 'NG_EXP_ML', 'NG_EXP_ML_COGEN', 'TRAFO_EH', 'TRAFO_HE', 'TRAFO_HM', 'TRAFO_LM', 'TRAFO_MH', 'TRAFO_ML', 'UNMINEABLE_COAL_SEAM']
2025-12-26 12:17:28,367 - Mescal - WARNING - List of technologies that are in the tech_specifics file but not in the mapping file (this will not be a problem in the workflow): ['MOB_AVIATION', 'MOB_FREIGHT_RAIL', 'MOB_FREIGHT_ROAD', 'MOB_PRIVATE_LOCAL', 'MOB_PRIVATE_LONGD', 'MOB_PUBLIC_LOCAL', 'MOB_PUBLIC_LONGD']
Create ESM database after double-counting removal, efficiency harmonization, and lifetime harmonization
[35]:
esm.create_esm_database()
2025-12-23 15:26:47,629 - Mescal - INFO - Starting to remove double-counted flows
100%|██████████| 215/215 [00:00<00:00, 731.76it/s]
2025-12-23 15:26:53,584 - Mescal - WARNING - No location found in your ranking for (natural gas, liquefied, natural gas production, liquefied) in the database ecoinvent_cutoff_3.10.1_image_SSP2-RCP26_2050 regionalized. Have to keep the initial location: EG
100%|██████████| 181/181 [01:48<00:00, 1.68it/s]
2025-12-23 15:30:01,870 - Mescal - INFO - Double-counting removal done in 194.2 seconds
2025-12-23 15:30:02,582 - Mescal - INFO - Starting to correct efficiency differences
2025-12-23 15:30:03,021 - Mescal - WARNING - No flow found for type(s) ['NG_HP'] in BUS_FC_HYBRID_CH4. The efficiency of this technology cannot be adjusted.
2025-12-23 15:30:03,353 - Mescal - WARNING - No flow found for type(s) ['NG_HP'] in CAR_FC_CH4_LOCAL. The efficiency of this technology cannot be adjusted.
2025-12-23 15:30:03,433 - Mescal - WARNING - No flow found for type(s) ['NG_HP'] in CAR_FC_CH4_LONGD. The efficiency of this technology cannot be adjusted.
2025-12-23 15:30:04,240 - Mescal - WARNING - No flow found for type(s) ['NG_HP'] in COACH_FC_HYBRID_CH4. The efficiency of this technology cannot be adjusted.
2025-12-23 15:30:05,228 - Mescal - WARNING - No flow found for type(s) ['WASTE'] in DHN_COGEN_WASTE. The efficiency of this technology cannot be adjusted.
2025-12-23 15:30:05,701 - Mescal - WARNING - No flow found for type(s) ['WASTE'] in IND_BOILER_WASTE. The efficiency of this technology cannot be adjusted.
2025-12-23 15:30:05,844 - Mescal - WARNING - No flow found for type(s) ['WASTE'] in IND_COGEN_WASTE. The efficiency of this technology cannot be adjusted.
2025-12-23 15:30:06,097 - Mescal - WARNING - No flow found for type(s) ['WOOD'] in PYROLYSIS. The efficiency of this technology cannot be adjusted.
2025-12-23 15:30:06,297 - Mescal - WARNING - No flow found for type(s) ['NG_HP'] in TRAIN_FREIGHT_NG. The efficiency of this technology cannot be adjusted.
2025-12-23 15:30:06,361 - Mescal - WARNING - No flow found for type(s) ['NG_HP'] in TRAIN_NG. The efficiency of this technology cannot be adjusted.
2025-12-23 15:30:09,783 - Mescal - INFO - Efficiency differences corrected in 7.2 seconds
2025-12-23 15:30:16,960 - Mescal - WARNING - Several names possible for the same type of flow in POLYPROPYLENE_PP: {'propylene', 'ethylene'}. Kept the first one.
2025-12-23 15:30:18,367 - Mescal - INFO - Starting to write database
Writing activities to SQLite3 database:
0% [##############################] 100% | ETA: 00:00:00
Total time elapsed: 00:00:00
Title: Writing activities to SQLite3 database:
Started: 12/23/2025 15:30:41
Finished: 12/23/2025 15:30:42
Total time elapsed: 00:00:00
CPU %: 89.10
Memory %: 7.13
2025-12-23 15:31:59,168 - Database - INFO - EnergyScope_CH_2050_Test written to Brightway!
2025-12-23 15:31:59,825 - Mescal - INFO - Database written in 101.5 seconds
Computing the LCA metrics
[36]:
R_long_direct_emissions, _, _ = esm.compute_impact_scores(
methods=lcia_methods,
assessment_type='direct emissions',
overwrite=True,
)
Getting activity data
100%|██████████| 880/880 [00:00<00:00, 87082.40it/s]
Adding exchange data to activities
100%|██████████| 14620/14620 [00:04<00:00, 3287.59it/s]
Filling out exchange data
100%|██████████| 880/880 [00:01<00:00, 477.34it/s]
2025-12-23 15:32:10,326 - Database - INFO - Loaded EnergyScope_CH_2050_Test from brightway!
Writing activities to SQLite3 database:
0% [##############################] 100% | ETA: 00:00:00
Total time elapsed: 00:00:00
Title: Writing activities to SQLite3 database:
Started: 12/23/2025 15:32:33
Finished: 12/23/2025 15:32:33
Total time elapsed: 00:00:00
CPU %: 78.70
Memory %: 12.36
2025-12-23 15:33:33,577 - Database - INFO - EnergyScope_CH_2050_Test_direct_emissions written to Brightway!
181it [00:01, 116.03it/s]
[37]:
R_long_direct_emissions.to_csv(f'results/energyscope_{esm_location}/{esm_year}/impact_scores_direct_emissions.csv', index=False)
[38]:
R_long_direct_emissions.head()
[38]:
| Impact_category | New_code | Value | Name | Type | Impact_category (level 0) | Impact_category (level 1) | Impact_category (level 2) | Impact_category_unit | Functional unit | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | (IMPACT World+ Midpoint 2.1_regionalized for e... | bhststsh7mmysi4fyiexddlpcxqvck5g | 0.0 | ALKALINE_ELECTROLYSIS | Operation | IMPACT World+ Midpoint 2.1_regionalized for ec... | Midpoint | Climate change, long term | kg CO2 eq (long) | kilowatt hour |
| 1 | (IMPACT World+ Midpoint 2.1_regionalized for e... | bhststsh7mmysi4fyiexddlpcxqvck5g | 0.0 | ALKALINE_ELECTROLYSIS | Operation | IMPACT World+ Midpoint 2.1_regionalized for ec... | Midpoint | Climate change, short term | kg CO2 eq (short) | kilowatt hour |
| 2 | (IMPACT World+ Midpoint 2.1_regionalized for e... | bhststsh7mmysi4fyiexddlpcxqvck5g | 0.0 | ALKALINE_ELECTROLYSIS | Operation | IMPACT World+ Midpoint 2.1_regionalized for ec... | Midpoint | Fossil and nuclear energy use | MJ deprived | kilowatt hour |
| 3 | (IMPACT World+ Midpoint 2.1_regionalized for e... | bhststsh7mmysi4fyiexddlpcxqvck5g | 0.0 | ALKALINE_ELECTROLYSIS | Operation | IMPACT World+ Midpoint 2.1_regionalized for ec... | Midpoint | Freshwater acidification | kg SO2 eq | kilowatt hour |
| 4 | (IMPACT World+ Midpoint 2.1_regionalized for e... | bhststsh7mmysi4fyiexddlpcxqvck5g | 0.0 | ALKALINE_ELECTROLYSIS | Operation | IMPACT World+ Midpoint 2.1_regionalized for ec... | Midpoint | Freshwater ecotoxicity | CTUe | kilowatt hour |
[39]:
R_long, contrib_analysis_res, _ = esm.compute_impact_scores(
methods=lcia_methods,
contribution_analysis='both',
)
Getting activity data
100%|██████████| 880/880 [00:00<00:00, 124271.49it/s]
Adding exchange data to activities
100%|██████████| 14620/14620 [00:00<00:00, 35325.79it/s]
Filling out exchange data
100%|██████████| 880/880 [00:01<00:00, 658.70it/s]
2025-12-23 15:33:39,074 - Database - INFO - Loaded EnergyScope_CH_2050_Test from brightway!
596it [05:54, 1.68it/s]
[40]:
R_long.to_csv(f'results/energyscope_{esm_location}/{esm_year}/impact_scores.csv', index=False)
contrib_analysis_res.to_csv(f'results/energyscope_{esm_location}/{esm_year}/contribution_analysis.csv', index=False)
[41]:
R_long.head()
[41]:
| Impact_category | New_code | Value | Name | Type | Impact_category (level 0) | Impact_category (level 1) | Impact_category (level 2) | Impact_category_unit | Functional unit | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | (IMPACT World+ Midpoint 2.1_regionalized for e... | djsx4kf89i179omk6lwr1hqptygdo6g2 | 239.149972 | AL_MAKING | Construction | IMPACT World+ Midpoint 2.1_regionalized for ec... | Midpoint | Climate change, long term | kg CO2 eq (long) | kilogram per hour |
| 1 | (IMPACT World+ Midpoint 2.1_regionalized for e... | djsx4kf89i179omk6lwr1hqptygdo6g2 | 260.880150 | AL_MAKING | Construction | IMPACT World+ Midpoint 2.1_regionalized for ec... | Midpoint | Climate change, short term | kg CO2 eq (short) | kilogram per hour |
| 2 | (IMPACT World+ Midpoint 2.1_regionalized for e... | djsx4kf89i179omk6lwr1hqptygdo6g2 | 3472.254845 | AL_MAKING | Construction | IMPACT World+ Midpoint 2.1_regionalized for ec... | Midpoint | Fossil and nuclear energy use | MJ deprived | kilogram per hour |
| 3 | (IMPACT World+ Midpoint 2.1_regionalized for e... | djsx4kf89i179omk6lwr1hqptygdo6g2 | 1.672163 | AL_MAKING | Construction | IMPACT World+ Midpoint 2.1_regionalized for ec... | Midpoint | Freshwater acidification | kg SO2 eq | kilogram per hour |
| 4 | (IMPACT World+ Midpoint 2.1_regionalized for e... | djsx4kf89i179omk6lwr1hqptygdo6g2 | 143450.673137 | AL_MAKING | Construction | IMPACT World+ Midpoint 2.1_regionalized for ec... | Midpoint | Freshwater ecotoxicity | CTUe | kilogram per hour |
[42]:
contrib_analysis_res.head()
[42]:
| score | amount | code | database | impact_category | act_database | act_code | contribution_type | act_name | act_type | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 118.979814 | 118.979814 | 349b29d1-3e58-4c66-98b9-9d1a076efd2e | biosphere3 | (IMPACT World+ Midpoint 2.1_regionalized for e... | EnergyScope_CH_2050_Test | djsx4kf89i179omk6lwr1hqptygdo6g2 | emissions | AL_MAKING | Construction |
| 1 | 61.124035 | 61.124035 | f9749677-9c9f-4678-ab55-c607dfdc2cb9 | biosphere3 | (IMPACT World+ Midpoint 2.1_regionalized for e... | EnergyScope_CH_2050_Test | djsx4kf89i179omk6lwr1hqptygdo6g2 | emissions | AL_MAKING | Construction |
| 2 | 45.521741 | 45.521741 | aa7cac3a-3625-41d4-bc54-33e2cf11ec46 | biosphere3 | (IMPACT World+ Midpoint 2.1_regionalized for e... | EnergyScope_CH_2050_Test | djsx4kf89i179omk6lwr1hqptygdo6g2 | emissions | AL_MAKING | Construction |
| 3 | 5.670375 | 5.670375 | 70ef743b-3ed5-4a6d-b192-fb6d62378555 | biosphere3 | (IMPACT World+ Midpoint 2.1_regionalized for e... | EnergyScope_CH_2050_Test | djsx4kf89i179omk6lwr1hqptygdo6g2 | emissions | AL_MAKING | Construction |
| 4 | 2.566230 | 2.566230 | ba2f3f82-c93a-47a5-822a-37ec97495275 | biosphere3 | (IMPACT World+ Midpoint 2.1_regionalized for e... | EnergyScope_CH_2050_Test | djsx4kf89i179omk6lwr1hqptygdo6g2 | emissions | AL_MAKING | Construction |
[48]:
_, contrib_analysis_processes_res, _ = esm.compute_impact_scores(
methods=lcia_methods,
contribution_analysis='processes',
contribution_analysis_limit=100,
)
Getting activity data
100%|██████████| 880/880 [00:00<00:00, 196621.96it/s]
Adding exchange data to activities
100%|██████████| 14620/14620 [00:00<00:00, 18463.85it/s]
Filling out exchange data
100%|██████████| 880/880 [00:00<00:00, 960.51it/s]
2025-12-23 15:39:58,051 - Database - INFO - Loaded EnergyScope_CH_2050_Test from brightway!
596it [22:28, 2.26s/it]
[49]:
contrib_analysis_processes_res.to_csv(f'results/energyscope_{esm_location}/{esm_year}/contribution_analysis_processes.csv', index=False)
Convert the results in an AMPL format
[35]:
# To skip the previous steps
R_long = pd.read_csv(f'results/energyscope_{esm_location}/{esm_year}/impact_scores.csv')
R_long_direct_emissions = pd.read_csv(f'results/energyscope_{esm_location}/{esm_year}/impact_scores_direct_emissions.csv')
contrib_analysis_processes_res = pd.read_csv(f'results/energyscope_{esm_location}/{esm_year}/contribution_analysis_processes.csv')
C:\Users\matth\AppData\Local\Temp\ipykernel_35040\263815357.py:4: DtypeWarning: Columns (7) have mixed types. Specify dtype option on import or set low_memory=False.
contrib_analysis_processes_res = pd.read_csv(f'results/energyscope_{esm_location}/{esm_year}/contribution_analysis_processes.csv')
[52]:
# Tou can specify which specific LCIA methods or impact categories you wish to use using one of the three following options:
# specific_lcia_methods = [f'IMPACT World+ Footprint 2.1 for ecoinvent v{ecoinvent_main_version}'] # selects the specific methods via their names
# specific_lcia_categories = ['Total ecosystem quality', 'Total human health'] # selects the specific impact categories via their names
specific_lcia_abbrev = ['TTHH', 'TTEQ', 'm_CCS'] # selects the specific impact categories via their abbreviations
[53]:
# Additional information that can be added at the beginning of the AMPL .mod and .dat files
metadata = {
'ecoinvent_version': ecoinvent_version,
'year': esm_year,
'spatialized': spatialized_database,
'regionalized': regionalize_foregrounds,
'iam': premise_iam,
'ssp_rcp': premise_ssp_rcp,
'lcia_methods': lcia_methods,
}
Normalize LCA indicators and create the .dat file
[58]:
# Life-cycle impacts
esm.normalize_lca_metrics(
R=R_long,
mip_gap=1e-6,
lcia_methods=lcia_methods,
specific_lcia_abbrev=specific_lcia_abbrev,
impact_abbrev=impact_abbrev,
path=f'results/energyscope_{esm_location}/{esm_year}/',
metadata=metadata,
)
[59]:
# Direct impacts
esm.normalize_lca_metrics(
R=R_long,
R_direct=R_long_direct_emissions,
mip_gap=1e-6,
lcia_methods=lcia_methods,
specific_lcia_abbrev=specific_lcia_abbrev,
assessment_type='direct emissions',
impact_abbrev=impact_abbrev,
path=f'results/energyscope_{esm_location}/{esm_year}/',
metadata=metadata,
file_name='techs_lcia_direct',
)
[60]:
# Territorial impacts
esm.normalize_lca_metrics(
R=R_long,
contrib_processes=contrib_analysis_processes_res,
mip_gap=1e-6,
lcia_methods=lcia_methods,
specific_lcia_abbrev=specific_lcia_abbrev,
assessment_type='territorial emissions',
impact_abbrev=impact_abbrev,
path=f'results/energyscope_{esm_location}/{esm_year}/',
metadata=metadata,
file_name='techs_lcia_territorial',
)
Getting activity data
100%|██████████| 880/880 [00:00<00:00, 48886.60it/s]
Adding exchange data to activities
100%|██████████| 14620/14620 [00:01<00:00, 10273.36it/s]
Filling out exchange data
100%|██████████| 880/880 [00:01<00:00, 489.38it/s]
2025-12-26 12:41:32,926 - Database - INFO - Loaded EnergyScope_CH_2050_Test from brightway!
Create the .mod file
[61]:
# Life-cycle impacts
esm.generate_mod_file_ampl(
lcia_methods=lcia_methods,
specific_lcia_abbrev=specific_lcia_abbrev,
impact_abbrev=impact_abbrev,
path=f'results/energyscope_{esm_location}/{esm_year}/',
metadata=metadata,
energyscope_version='core' if esm_location == 'core' else 'epfl',
)
[62]:
# Direct impacts
esm.generate_mod_file_ampl(
lcia_methods=lcia_methods,
specific_lcia_abbrev=specific_lcia_abbrev,
assessment_type='direct emissions',
impact_abbrev=impact_abbrev,
path=f'results/energyscope_{esm_location}/{esm_year}/',
metadata=metadata,
file_name='objectives_direct',
energyscope_version='core' if esm_location == 'core' else 'epfl',
)
[63]:
# Territorial impacts
esm.generate_mod_file_ampl(
lcia_methods=lcia_methods,
specific_lcia_abbrev=specific_lcia_abbrev,
assessment_type='territorial emissions',
impact_abbrev=impact_abbrev,
path=f'results/energyscope_{esm_location}/{esm_year}/',
metadata=metadata,
file_name='objectives_territorial',
energyscope_version='core' if esm_location == 'core' else 'epfl',
)
Integrate the ESM results back in the LCI database
[ ]:
esm.create_new_database_with_esm_results(
esm_results=results_from_esm,
new_end_use_types=new_end_use_types,
tech_to_remove_layers=technologies_to_remove_from_layers,
write_database=True,
remove_background_construction_flows=True,
harmonize_with_esm=False,
)
[ ]:
esm.connect_esm_results_to_database(create_new_db=True)