Quick Tutorial
This notebook aims to show how to generate Life-Cycle Assessment (LCA) impact scores to be used in an Energy System Model (ESM). LCA impact scores can be used within modelling constraints, e.g., upper limit on life-cycle greenhouse gas emissions, or in the objective function, e.g., minimizing the total damage on human health. mescal applies several transformations on LCA data to ensure the alignement between your ESM and LCA models. For instance, mescal performs double-counting removal (to avoid the overestimation of flows that are already represented in your ESM, e.g., energy flows), technological parameters harmonization (e.g., lifetime, efficiency, capacity factors), and normalization (to ease the solving process in your ESM).
In this notebook, we illustrate the use of mescal with the core model of EnergyScope. We employed the ecoinvent LCA database, but the overall methodology is agnostic to the used LCA database. To replicate this example for your ESM, you should adapt the input data files accordingly, following the example ones.
We show how to:
create the ESM database (i.e., the database containing the datasets corresponding to the technologies and resources of the model) in your brightway2 project
perform life-cycle impact assessment and contribution analyses
create the .mod and .dat files for your ESM
visualise the results using mescal’s embodied plots
[ ]:
%pip install mescal==1.2.3
Project setup
[2]:
# Import the required libraries
from mescal import *
import pandas as pd
import bw2data as bd
[3]:
location = 'BE' # Set the ecoinvent location code corresponding to your ESM here (e.g., 'BE' for Belgium)
year = 2020 # Set the year here (e.g., 2050)
[4]:
# Set the name of your main LCI database (e.g., ecoinvent or premise database) here:
lca_db_name = "ecoinvent-3.10.1-cutoff"
[5]:
# Set the name of the new database with the ESM results
esm_db_name = f'EnergyScope_{location}_{year}'
[6]:
# Set the list of LCIA methods for which you want indicators (they must be registered in your brightway project)
lcia_methods=['EF v3.1']
[7]:
# Set up your Brightway project
bd.projects.set_current('ecoinvent3.10.1') # put the name of your brightway project here
[8]:
# Load the LCI database from your brightway project
lca_db = Database(lca_db_name, create_pickle=True)
2026-02-16 11:58:31,095 - Database - INFO - Loaded ecoinvent-3.10.1-cutoff from pickle!
Input data
[9]:
path_to_input_files = 'path/to/your/input/files/' # put here the path to the folder containing the input data files
Input data files can be retrieved from mescal GitHub repository
[10]:
mapping = pd.read_csv(path_to_input_files+'mapping.csv')
unit_conversion = pd.read_excel(path_to_input_files+'unit_conversion.xlsx')
mapping_esm_flows_to_CPC = pd.read_csv(path_to_input_files+'mapping_esm_flows_to_CPC.csv')
model = pd.read_csv(path_to_input_files+'model.csv')
technology_compositions = pd.read_csv(path_to_input_files+'technology_compositions.csv')
technology_specifics = pd.read_csv(path_to_input_files+'technology_specifics.csv')
lifetime = pd.read_csv(path_to_input_files+'lifetime.csv')
efficiency = pd.read_csv(path_to_input_files+'efficiency.csv')
impact_abbrev = pd.read_csv(path_to_input_files+'impact_abbrev.csv')
[11]:
mapping.Database = lca_db_name # set the database name in the mapping file
[12]:
# For this example notebook, we consider a sample of available technologies and resources
example_technologies = [
'GEOTHERMAL',
'HYDRO_RIVER',
'NUCLEAR',
'WIND_ONSHORE', 'WIND_ONSHORE_CONNECTION', 'WIND_ONSHORE_TURBINE',
'COAL',
'GAS',
'H2',
'URANIUM',
]
mapping = mapping[mapping.Name.isin(example_technologies)].reset_index()
model = model[model.Name.isin(example_technologies)].reset_index()
Initialize the ESM class
[13]:
esm = ESM(
mapping=mapping,
unit_conversion=unit_conversion,
model=model,
mapping_esm_flows_to_CPC_cat=mapping_esm_flows_to_CPC,
main_database=lca_db,
esm_db_name=esm_db_name,
esm_location=location,
technology_compositions=technology_compositions,
tech_specifics=technology_specifics,
lifetime=lifetime,
efficiency=efficiency,
regionalize_foregrounds=['Operation', 'Resource'], # types of LCI datasets that will be regionalized
locations_ranking=['BE', 'RER', 'GLO', 'RoW'], # order of preference for locations when regionalizing
results_path_file='lca_results/',
)
[14]:
esm.clean_inputs()
[15]:
# Update mapping dataframe with better locations
esm.change_location_mapping_file()
[16]:
esm.main_database.test_mapping_file(esm.mapping) # test the mapping file
2026-02-16 11:58:32,236 - Database - INFO - Mapping successfully linked to the database
[16]:
[]
[17]:
esm.check_inputs()
2026-02-16 11:58:32,322 - 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: ['GEOTHERMAL']
2026-02-16 11:58:32,325 - 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): ['ATM_CCS', 'BIOMETHANATION', 'BIO_HYDROLYSIS', 'DEC_DIRECT_ELEC', 'ELECTRICITY', 'ELEC_EXPORT', 'GASIFICATION_SNG', 'H2_ELECTROLYSIS', 'HABER_BOSCH', 'IND_DIRECT_ELEC', 'PV', 'PYROLYSIS_TO_FUELS', 'PYROLYSIS_TO_LFO', 'SYN_METHANATION']
Create the ESM database in your Brightway2 project
[18]:
esm.create_esm_database()
2026-02-16 11:58:32,911 - Mescal - INFO - Starting to remove double-counted flows
100%|██████████| 4/4 [00:00<00:00, 8.13it/s]
2026-02-16 11:58:34,356 - Mescal - INFO - Double-counting removal done in 1.4 seconds
2026-02-16 11:58:34,378 - Mescal - INFO - Starting to correct efficiency differences
2026-02-16 11:58:34,626 - Mescal - INFO - Efficiency differences corrected in 0.2 seconds
2026-02-16 11:58:34,705 - Mescal - INFO - Starting to write database
2026-02-16 11:58:34,915 - Database - INFO - Previous EnergyScope_BE_2020 will be overwritten!
Writing activities to SQLite3 database:
0% [#############] 100% | ETA: 00:00:00
Total time elapsed: 00:00:00
Title: Writing activities to SQLite3 database:
Started: 02/16/2026 11:58:35
Finished: 02/16/2026 11:58:35
Total time elapsed: 00:00:00
CPU %: 140.20
Memory %: 8.89
2026-02-16 11:58:35,805 - Database - INFO - EnergyScope_BE_2020 written to Brightway!
2026-02-16 11:58:35,808 - Mescal - INFO - Database written in 1.1 seconds
Compute LCA impact scores and perform contribution analysis
[19]:
impact_scores, contrib_analysis_res, _ = esm.compute_impact_scores(
methods=lcia_methods,
contribution_analysis='both',
)
Getting activity data
100%|██████████| 13/13 [00:00<?, ?it/s]
Adding exchange data to activities
100%|██████████| 494/494 [00:00<00:00, 14108.58it/s]
Filling out exchange data
100%|██████████| 13/13 [00:00<00:00, 19.86it/s]
2026-02-16 11:58:36,652 - Database - INFO - Loaded EnergyScope_BE_2020 from brightway!
13it [00:03, 3.61it/s]
[20]:
impact_scores.to_csv(esm.results_path_file+'impact_scores.csv', index=False)
contrib_analysis_res.to_csv(esm.results_path_file+'contribution_analysis.csv', index=False)
[21]:
impact_scores_direct_emissions, _, _ = esm.compute_impact_scores(
methods=lcia_methods,
assessment_type='direct emissions', # specific metrics for direct emissions during operation
)
Getting activity data
100%|██████████| 13/13 [00:00<?, ?it/s]
Adding exchange data to activities
100%|██████████| 494/494 [00:00<00:00, 25933.22it/s]
Filling out exchange data
100%|██████████| 13/13 [00:00<00:00, 63.34it/s]
2026-02-16 11:58:42,760 - Database - INFO - Loaded EnergyScope_BE_2020 from brightway!
2026-02-16 11:58:42,823 - Database - INFO - Previous EnergyScope_BE_2020_direct_emissions will be overwritten!
Writing activities to SQLite3 database:
0% [####] 100% | ETA: 00:00:00
Total time elapsed: 00:00:00
2026-02-16 11:58:43,303 - Database - INFO - EnergyScope_BE_2020_direct_emissions written to Brightway!
Title: Writing activities to SQLite3 database:
Started: 02/16/2026 11:58:43
Finished: 02/16/2026 11:58:43
Total time elapsed: 00:00:00
CPU %: 97.70
Memory %: 9.68
4it [00:00, 200.29it/s]
[22]:
impact_scores_direct_emissions.to_csv(esm.results_path_file+'impact_scores_direct_emissions.csv', index=False)
Create .dat and .mod files for AMPL integration
By default, the resulting .dat and .mod files are saved in the result directory specified in the ESM class initialization (here ‘lca_results/’).
We can select a few impact categories that we want to integrate in the model. Here, we select only two categories: TTHH (Total human health) and TTEQ (Total ecosystem quality). The abbreviations are defined in the impact_abbrev.csv file.
[23]:
specific_lca_abbrev = ['CC', 'MR', 'PMF', 'ETF']
[24]:
esm.normalize_lca_metrics(
R=impact_scores,
mip_gap=1e-6,
lcia_methods=lcia_methods,
specific_lcia_abbrev=specific_lca_abbrev,
impact_abbrev=impact_abbrev,
file_name='techs_lca',
)
[25]:
# specific for direct emissions metrics
esm.normalize_lca_metrics(
R=impact_scores,
R_direct=impact_scores_direct_emissions, # thus we specify this
mip_gap=1e-6,
lcia_methods=lcia_methods,
specific_lcia_abbrev=specific_lca_abbrev,
assessment_type='direct emissions', # and this
impact_abbrev=impact_abbrev,
file_name='techs_lca_direct',
)
[26]:
esm.generate_mod_file_ampl(
lcia_methods=lcia_methods,
impact_abbrev=impact_abbrev,
specific_lcia_abbrev=specific_lca_abbrev,
file_name='objectives_lca',
energyscope_version='core',
)
[27]:
# specific for direct emissions metrics
esm.generate_mod_file_ampl(
lcia_methods=lcia_methods,
specific_lcia_abbrev=specific_lca_abbrev,
assessment_type='direct emissions', # specify this
impact_abbrev=impact_abbrev,
file_name='objectives_lca_direct',
energyscope_version='core',
)
Visualize the LCA impact scores
[28]:
plot = Plot(
df_impact_scores=impact_scores,
lifetime=lifetime, # used to visualise infrastructure impacts per kW.year
)
[29]:
visualise_technologies = [tec for tec in example_technologies if tec not in ['WIND_ONSHORE_TURBINE', 'WIND_ONSHORE_CONNECTION']]
[30]:
plot.plot_indicators_of_technologies_for_one_impact_category(
technologies_list=visualise_technologies,
impact_category=(
'EF v3.1',
'climate change',
'global warming potential (GWP100)',
),
metadata={
'operation_unit': 'kWh',
'construction_unit': 'kW',
'technologies_type': 'electricity production',
},
)
[31]:
plot.plot_indicators_of_technologies_for_one_impact_category(
technologies_list=visualise_technologies,
impact_category=(
'EF v3.1',
'material resources: metals/minerals',
'abiotic depletion potential (ADP): elements (ultimate reserves)',
),
metadata={
'operation_unit': 'kWh',
'construction_unit': 'kW',
'technologies_type': 'electricity production',
},
)
[32]:
plot.plot_indicators_of_technologies_for_several_impact_categories(
technologies_list=visualise_technologies,
impact_categories_list=[
('EF v3.1', 'particulate matter formation', 'impact on human health'),
('EF v3.1', 'climate change', 'global warming potential (GWP100)'),
('EF v3.1', 'material resources: metals/minerals', 'abiotic depletion potential (ADP): elements (ultimate reserves)'),
]
)
[33]:
plot.plot_indicators_of_resources_for_several_impact_categories(
resources_list=['COAL', 'GAS', 'URANIUM'],
impact_categories_list=[
('EF v3.1', 'particulate matter formation', 'impact on human health'),
('EF v3.1', 'climate change', 'global warming potential (GWP100)'),
('EF v3.1', 'material resources: metals/minerals', 'abiotic depletion potential (ADP): elements (ultimate reserves)'),
]
)