ALIGNED project: Model uncertainty tutorial¶
Aligning Life Cycle Assessment methods and bio-based sectors for improved environmental performance
Horizon Europe grant agreement N° 101059430. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Research Executive Agency.
WP1 Shared modelling framework and learnings¶
Task 1.4 Framework for interpreting uncertainty¶
Deliverable 1.2 Description of scientific methods¶
Tutorial for performing the analysis of model uncertainty¶
Massimo Pizzol, Aalborg University (AAU), 2024¶
This notebook show how to perform a uncertainty analysis for a example product system of a biobased product, and quantify the uncertainty due to a specific modelling choice, in this case the choice of electricity mix.
# importing packages
import brightway2 as bw
import pandas as pd
import numpy as np
from scipy import stats
from lci_to_bw2 import * # import all the functions of this module
# open a project with ecoinvent v.3.9.1 consequential system model
bw.projects.set_current('advlca23')
We start by importing a dummy prodcut system of a biobased product
We start by importing data about a fictional ("dummy") product system for a biobased product.
# Import the dummy product system
# import data from csv
mydata = pd.read_csv('ALIGNED-LCI-biobased-product-dummy.csv', header = 0, sep = ",") # using csv file avoids encoding problem
mydata.head()
# keep only the columns not needed
mydb = mydata[['Activity database','Activity code','Activity name','Activity unit','Activity type',
'Exchange database','Exchange input','Exchange amount','Exchange unit','Exchange type',
'Exchange uncertainty type','Exchange loc','Exchange scale','Exchange negative',
'Simapro name', 'Simapro unit', 'Simapro type']].copy()
mydb = mydata.copy()
mydb['Exchange uncertainty type'] = mydb['Exchange uncertainty type'].fillna(0).astype(int) # uncertainty as integers
# Note: to avoid having both nan and values in the uncertainty column I use zero as default
# Create dictionary in bw format and write database to disk.
# Shut down all other notebooks using the same project before doing this
bw2_db = lci_to_bw2(mydb) # a function from the lci_to_bw2 module
# write database
bw.Database('ALIGNED-biob-prod-dummy').write(bw2_db)
# check what foreground activities are included
for act in bw.Database('ALIGNED-biob-prod-dummy'):
print(act, act['code'])
Writing activities to SQLite3 database: 0% [#####] 100% | ETA: 00:00:00 Total time elapsed: 00:00:00
Title: Writing activities to SQLite3 database: Started: 03/14/2024 20:18:08 Finished: 03/14/2024 20:18:08 Total time elapsed: 00:00:00 CPU %: 92.80 Memory %: 1.25 'Biobased-product-eol' (kilogram, None, None) c8301e73-d521-4a89-998b-30b7e7751011 'Biobased-product-use' (year, None, None) f9eabf64-b899-40c0-9f9f-2009dbb0a0b2 'Biomass-processing' (kilogram, None, None) 403a5c32-c769-46fc-8b9a-74b8eb3c79d1 'Biobased-product-manufacturing' (kilogram, None, None) a37d149a-6508-4563-8af6-e5a39b4176df 'Biomass-growth' (kilogram, None, None) a7d34649-9c10-4423-bac3-ecab9b43b20c
# More info
myact = bw.Database('ALIGNED-biob-prod-dummy').get('f9eabf64-b899-40c0-9f9f-2009dbb0a0b2') # Biobased-product-use
myact._data
{'name': 'Biobased-product-use', 'unit': 'year', 'type': 'process', 'database': 'ALIGNED-biob-prod-dummy', 'code': 'f9eabf64-b899-40c0-9f9f-2009dbb0a0b2'}
We calculate a static climate impact score for the fictional biobased product, to be used for reference later on.
# calculation of static LCA score
mymethod = ('IPCC 2013', 'climate change', 'global warming potential (GWP100)')
myact = bw.Database('ALIGNED-biob-prod-dummy').get('f9eabf64-b899-40c0-9f9f-2009dbb0a0b2') # Biobased-product-use
functional_unit = {myact: 1}
LCA = bw.LCA(functional_unit, mymethod)
LCA.lci()
LCA.lcia()
print("The static Global Warming impact score is", LCA.score, bw.methods[mymethod]['unit'])
The static Global Warming impact score is 121.67148851736351 kg CO2-Eq
Perform uncertainty analysis on a specific modelling choice¶
One might for example lack specific knowledge regarding where a product is produced or used and thus be interested in changing the electricity mix to understant what is the range of results that can be obtained due to this choice or spatial veriability.
In this example, the LCA is run with different electricity market mixes randmoly chosen from ecoinvent instead of just one.
In the product system under analysis, electricity is used in the manufacturing process.
# Select the manufacturing activity.
myact = bw.Database('ALIGNED-biob-prod-dummy').get('a37d149a-6508-4563-8af6-e5a39b4176df') # Biobased-product-manufacturing
myact._data
{'name': 'Biobased-product-manufacturing', 'unit': 'kilogram', 'type': 'process', 'database': 'ALIGNED-biob-prod-dummy', 'code': 'a37d149a-6508-4563-8af6-e5a39b4176df'}
Specifically, it is electricity from the Danish market, medium voltage.
for exc in list(myact.exchanges()):
print(exc)
Exchange: 1.0 kilogram 'Biobased-product-manufacturing' (kilogram, None, None) to 'Biobased-product-manufacturing' (kilogram, None, None)> Exchange: 0.5 kilogram 'Biomass-growth' (kilogram, None, None) to 'Biobased-product-manufacturing' (kilogram, None, None)> Exchange: 0.5 kilogram 'Biomass-processing' (kilogram, None, None) to 'Biobased-product-manufacturing' (kilogram, None, None)> Exchange: 0.5 kilowatt hour 'market for electricity, medium voltage' (kilowatt hour, DK, None) to 'Biobased-product-manufacturing' (kilogram, None, None)>
Step 1¶
First make a list of electricity markets we want to test.
el_markets = [('ecoinvent 3.9 conseq', i['code'])
for i in bw.Database("ecoinvent 3.9 conseq").search('market electricity medium voltage', limit = 100)]
el_markets[1:5] # prints the first four of them
[('ecoinvent 3.9 conseq', '09b0dd783c942c69761ef2a217c01ded'), ('ecoinvent 3.9 conseq', 'b37d3cf1a8f3864d2964238a3e4f04ae'), ('ecoinvent 3.9 conseq', 'cb6281a8f1132aa84b3bc1ce09f5a83a'), ('ecoinvent 3.9 conseq', '01b5e56b9411d3de1f151d5d83eb1a57')]
Now we substitute these in the product system instead of the current electricity
# Get the activity, substitute the background process, save, and calculate
el_loc_results = [] # empty list that will contain all the results of the local SA
bb_manuf = bw.Database('ALIGNED-biob-prod-dummy').get('a37d149a-6508-4563-8af6-e5a39b4176df')
for m in el_markets[0:30]: # I am taking 30 different electticity markets
exc = list(bb_manuf.exchanges())[3] # select the first exchange in the activity, i.e. the input from ecoinvent
exc['input'] = m
exc.save() # important or the changes won't be maintained
lca = bw.LCA(functional_unit, mymethod)
lca.lci()
lca.lcia()
el_loc_results.append(lca.score)
Let's look at the results
el_loc_results[1:10]
[133.76717397723388, 133.77911165332955, 133.75913297460792, 122.50364482966519, 133.853580535514, 133.6078928681361, 121.67148851736351, 133.7511953871373, 133.8080454889203]
We can get quick descriptive statistics of the distribution of results including a median value (50% percentile) and a quantitative measure of uncertainty in terms of standard deviation (std)
pd.DataFrame(el_loc_results, columns = ['GWI scores']).describe()
GWI scores | |
---|---|
count | 30.000000 |
mean | 132.056870 |
std | 7.197142 |
min | 121.007039 |
25% | 127.404368 |
50% | 133.763153 |
75% | 133.816001 |
max | 152.848582 |
Note that the median is higher than the static value calculated in the beginning, a sign that the default system using Danish electricity mix is in the lower end of the potential range of impacts.
We can also have a visual look at the uncertainty.
from matplotlib import pyplot as plt
# Using matplotlib package
plt.boxplot(el_loc_results)
plt.ylabel(bw.methods[mymethod]['unit'])
plt.xlabel('Use of biobased product')
Text(0.5, 0, 'Use of biobased product')