Comparative static LCA in Brightway2

Contributed by Pascal Lesage, who is exploring Brightway2

This Notebook was not produced by Chris Mutel, and probably presents some techniques that could have been done more efficiently. Any comments/suggestions are absolutely welcome and should be sent to Pascal.

Objective:

This notebook explores how to conduct comparative static (or deterministic) LCA, i.e. LCA that ignores uncertainty.
Precisely, the objectives are to:

  1. Calculate the life cycle inventory of Danish and European milk as modeled in the FORWAST database.
  2. Compare the life cycle indicator results for the two products using ReCiPe (H,A) endpoints methods (totals for the three damage categories).
  3. Present the results using a table that normalizes results to the larger score.

Step 1: Initial stuff.

To get started, some initiating stuff, which is described in Brightway2's official Introduction notebook:

  • Importing Brightway2
  • Creating a project (see the docs to understand what a project is in Brightway2)
  • Importing a list of methods (Methods) and elementary flows (actually activities in Brightway2, in the Biosphere3 database)
  • Importing the FORWAST database

The first steps are probably common to all projects starting from scratch:

In [1]:
import brightway2 as bw #I like importing "as BW" to better keep track of Brightway's methods and functions
bw.projects.current = "Comparative static LCA" #Creating the project
bw.bw2setup() #Importing elementary flows, LCIA methods and some other data
Biosphere database already present!!! No setup is needed
Warning: bad escape \s

The following is specific to importing the FORWAST database, as explained in both the "Getting started" and the specific "Importing FORWAST" Notebooks found on Brightway2's site

In [2]:
import zipfile
import os
from bw2data.utils import download_file

if "forwast" not in bw.databases:
    filepath = download_file("forwast.bw2package.zip", url="http://lca-net.com/wp-content/uploads/")
    dirpath = os.path.dirname(filepath)
    zipfile.ZipFile(filepath).extractall(dirpath)
    bw.BW2Package.import_file(os.path.join(dirpath, "forwast.bw2package"))

Let's pull in our databases so we can use them, and print their types, just to remember what these actually are.

In [3]:
bioDB = bw.Database("biosphere3")
forwastDB = bw.Database("forwast")
print("The forwast DB type is ", type(forwastDB), "\nThe biosphere DB type is ", type(bioDB), "\njust so you know." )
The forwast DB type is  <class 'bw2data.backends.peewee.database.SQLiteBackend'> 
The biosphere DB type is  <class 'bw2data.backends.peewee.database.SQLiteBackend'> 
just so you know.

Step 2: Finding my two products

For my problem, I want to compare Danish and European milks.
I'll make my FORWAST database searchable, and then search for "dairy".

In [4]:
forwastDB.make_searchable()
dairy = forwastDB.search("dairy")
dairy
This database is already searchable
Warning: DisplayFormatter._ipython_display_formatter_default is deprecated: use @default decorator instead.
Warning: DisplayFormatter._formatters_default is deprecated: use @default decorator instead.
Warning: PlainTextFormatter._deferred_printers_default is deprecated: use @default decorator instead.
Warning: PlainTextFormatter._singleton_printers_default is deprecated: use @default decorator instead.
Warning: PlainTextFormatter._type_printers_default is deprecated: use @default decorator instead.
Warning: PlainTextFormatter._singleton_printers_default is deprecated: use @default decorator instead.
Warning: PlainTextFormatter._type_printers_default is deprecated: use @default decorator instead.
Warning: PlainTextFormatter._deferred_printers_default is deprecated: use @default decorator instead.
Out[4]:
['_20 Dairy products, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003']),
 '_20 Dairy products, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003'])]

I have found my two products. Let me create a variable for each, and then store them together in a list:

In [5]:
DKmilk = dairy[0]
EUmilk = dairy[1]
print("I will compare\n{} and\n{}".format(DKmilk, EUmilk))
myMilks = [DKmilk, EUmilk]
I will compare
'_20 Dairy products, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003']) and
'_20 Dairy products, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003'])

Of course, in this case, myMilks is exactly the same as dairy(my search result), but this is a fluke. For example, there could have been more dairy in the database.

Step 3: Selecting my LCIA methods

There are many methods that were imported during bw.bwsetup(). How many? Let's find out.

In [6]:
len(bw.methods)
Out[6]:
665

I just want methods that contain "ReCiPe" and (H, A) in the first element of the tuple, and "total" in the third.
Let's also exclude "single scores" (i.e. methods where "total" is the second element in the tuple) and the "without longterm" methods.
So let's create my list of methods:

In [7]:
myMethods = [method for method in bw.methods
             if "ReCiPe" in method[0] 
             and "(H,A)" in method[0] 
             and "w/o" not in method[0] 
             and "total" not in method[1]
             and "total" in method[2]]
myMethods
Out[7]:
[('ReCiPe Endpoint (H,A)', 'ecosystem quality', 'total'),
 ('ReCiPe Endpoint (H,A)', 'human health', 'total'),
 ('ReCiPe Endpoint (H,A)', 'resources', 'total')]

Step 4: Calculating results and storing them in a table (actually, a Pandas dataframe)

The table I want should have one column per scenario (in this case, Danish and European milk) and one row per impact category (in this case, the three ReCiPe Endpoint (H,A) methods found in myMethods.

In [8]:
# Note: Requires bw2calc version >= 1.2.1 (Released 2016-03-14) - run bw2update.bat on Windows!

results = []
for milk in myMilks:
    # Building the LCA objet is relaively expensive so it is better to reuse it
    lca = bw.LCA({milk:1})
    lca.lci()
    for method in myMethods:
        lca.switch_method(method)
        lca.lcia()
        results.append((milk["name"].replace("_20", "").strip(), method[1].title(), lca.score))
results
Out[8]:
[('Dairy products, DK', 'Ecosystem Quality', 0.4613055084918115),
 ('Dairy products, DK', 'Human Health', 0.9275230140902033),
 ('Dairy products, DK', 'Resources', 0.430265702086426),
 ('Dairy products, EU27', 'Ecosystem Quality', 0.25562829018917177),
 ('Dairy products, EU27', 'Human Health', 0.4607963671165396),
 ('Dairy products, EU27', 'Resources', 0.23386690905283167)]
In [9]:
import pandas as pd
results_df = pd.DataFrame(results, columns=["Name", "Method", "Score"])
results_df = pd.pivot_table(results_df, index = "Name", columns = "Method", values = "Score")
results_df
Out[9]:
Method Ecosystem Quality Human Health Resources
Name
Dairy products, DK 0.461306 0.927523 0.430266
Dairy products, EU27 0.255628 0.460796 0.233867

Let's normalise the results.

In [10]:
NormResults_df = results_df / results_df.max()
NormResults_df
Out[10]:
Method Ecosystem Quality Human Health Resources
Name
Dairy products, DK 1.000000 1.000000 1.000000
Dairy products, EU27 0.554141 0.496803 0.543541

Next steps:

What would follow would be:

  • Contribution analyses
    • EFs to categories
    • Processes to results
    • Process-EF coupl to results
  • Some nice graphics
    Anyone willing to add these is welcome, I'm moving on to uncertainty analyses, where the real action is!