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 model

  • Type: 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 database

  • Activity: the name of the activity of the energy technology or resource in the LCI database

  • Location: the name of the location of the energy technology or resource in the LCI database

  • Unit: (optional) the physical unit of the energy technology or resource in the LCI database

  • Database: 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 model

  • Type: 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 ESM

  • LCA: the target unit of the energy technology or resource in the LCA database

  • Assumptions & 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 ESM

  • Description: (optional) the description of the product

  • CPC: 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 model

  • Flow: the name of the input or output flow

  • Amount: 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 model

  • Components: the list of names of subcomponents

  • Type (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 model

  • Specifics: 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 model

  • ESM: 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 the Amount computation (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 ESM

  • Flow: the name of the flow in the ESM

  • Comment: (optional) additional information on the set of Flows

[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 database

  • CPC: the number and name of the corresponding CPC category

  • Search type: can be ‘equals’ if the Name entry is an exactly the name to look for, or ‘contains’ if it is contained in the full name

  • Where: can be ‘Product’ or ‘Activity’, whether the Name entry 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 convention

  • Unit: (optional) the unit of the impact category

  • Abbrev: the abbreviation of the impact category

  • AoP: 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 changed

  • Search type: whether the Name entry 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 type

  • New: 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 ESM

  • Production: 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 for PathwayESM if operation_metrics_for_all_time_steps is 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)