Getting Started with Brightway2

This is an introduction to Brightway2, an open source framework for Life Cycle Assessment. This notebook will cover the basics of databases and activities and looking at LCI databases and LCIA methods.

At the end of this notebook, you will be able to:

  • Import basic data like the biosphere database
  • Import and explore the FORWAST database

If you finish the notebook, you get a kitten.

This introduction is written in an Jupyter notebook, an online scientific notebook which combines, text, data, images, and programming. It is amazing, and could be a fantastic way to do and communicate advanced LCA work. See the documentation and a list of awesome examples. Please see the Brightway2 documentation for a complete list of example Brightway2 notebooks.

You should download this notebook and run it cell by cell - don't just read it on the web!

Starting at the beginning

Import brightway2.

In [ ]:
from brightway2 import *

If you get a warning like Warning: bad escape \s, don't worry! You can just ignore it. It is coming from an imported dependency.

Python 2 and 3

Brightway2 is compatible with both Python 2 and 3. If you are using Python 2, I strongly recommend you execute the following cell, which will make all your text string unicode without have to type the letter "u" in front of each text string. On Python 3, the following doesn't do anything - your text strings are unicode by default.

I won't include this cell in future notebooks, but you should copy and paste it if you will use Python 2.

In [ ]:
from __future__ import unicode_literals, print_function

Projects

In Brightway2, a project is a separate directory with its own copies of LCI databases, LCIA methods, and any other data you use. Each research project or article should probably be its own project, so that any changes you want to make will not interfere with your other work.

The default project is called default:

In [ ]:
projects.current

Each project is stored in a separate directory in a place in your filesystem reserved for application data. It varies depending on the operating system; on OS X, this is the Library directory in your user home directory:

In [ ]:
projects.dir

However, you really need to care about the specifics.

We can create a new project:

In [ ]:
projects.set_current("BW2 introduction")

And list the available projects:

In [ ]:
projects

Getting basic data

Let's import some basic data - a database of elementary flows, some LCIA methods, and some metadata used for importing other databases:

In [ ]:
bw2setup()

The iPython notebook by default prints all logged messages. On your machine, there might be messages tracking how long it took to download and process the biosphere and methods packages.

A biosphere dataset

The biosphere3 database was installed. It is called the biosphere3 database because elementary flow names are normalized to the ecoinvent 3 standard.

Let's see how many flows there are in this database, and then look at one random flow:

In [ ]:
db = Database("biosphere3")
print("Number of flows in `biosphere3`:", len(db))
random_flow = db.random()
print(random_flow)
In [ ]:
print(random_flow['name'])
print(random_flow['unit'])
print(random_flow['categories'])

Brightway2 uses keys to identify datasets. Each dataset is identified by a combination of its database and some unique code. The code can be anything - a number, a UUID, or just a name. All of the following would be valid keys:

("biosphere", "f66d00944691d54d6b072310b6f9de37")
("my new database", "building my dream house")
("skynet", 14832)
In [ ]:
random_flow.key

An LCIA method dataset

We also installed a large number of LCIA methods:

In [ ]:
len(methods)

Because LCIA methods have many different impact categories, they are identified not by a single label, but by a list of labels. Let's look at an example:

In [ ]:
method_key = methods.random()
method_key

In this case, the LCIA method has three levels of specificity, from the general name (first level) to the specific impact category (last level). There is nothing magic about three levels - you could have one, or one hundred - but Brightway2 expects that LCIA methods will be a list of text labels ('like', 'this').

Note that method identifiers need to be a special kind of list that uses () instead of []. These are called tuples. To create a tuple with only one element, you need to add a comma, to distinguish it from a set of parentheses:

In [ ]:
print (1 + 2)           # This is not a tuple
print (1,), type((1,))  # This is a tuple with one element

We can load the method data, show a sample.

Method data has the format:

biosphere flow, numeric value, location

Where:

  • biosphere flow is a dataset from any database which is used as a biosphere flow.
  • numeric value can be either a fixed number or an uncertainty distribution.
  • location is optional; the default value is that this characterization factor is valid everywhere.

The method data format is pretty flexible, and the following are all acceptable:

[('biosphere', 'CO2'), 1.0],                                             # Default location
[('biosphere', 'CO2'), 1.0, 'Australia, mate!'],                         # Custom location
[('biosphere', 'CO2'), 1.0, ('Population density', 'Raster cell 4,2')],  # Location inside a geocollection
[('biosphere', 'CO2'), {'amount': 1.0, 'uncertainty type': 0}],          # Uncertain characterization factor

Geocollections are needed for regionalized LCA.

If you are wondering why we need to identify biosphere flows like ('biosphere', '2fe885840cebfcc7d56b607b0acd9359'), this is a good question! The short answer is that there is no single field that uniquely identifies biosphere flows or activities. The longer answer is in the manual.

Brightway2 is designed to be flexible enough for many different problems. Therefore, there are no limits on what constitutes a biosphere flow. Rather, anything that is linked to in a biosphere exchange will be put in the biosphere matrix. We installed a database called biosphere3, but you can define new flows in a database alongside process datasets, or create your own biosphere database.

In [ ]:
method_data = Method(method_key).load()
print("Number of CFs:", len(method_data))
method_data[:20]

Importing the FORWAST LCI database

We will use the FORWAST database, as it is both a high quality, comprehensive LCI database, and freely available. FORWAST is a physical MRIO table for Europe. It can be downloaded directly from the 2.-0 website.

The following cell will download and install the FORWAST database. Note that an internet connection is required.

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

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

Searching datasets

By default, every database is added to a search engine powered by Whoosh. Searching covers the following data fields:

  • name
  • comment
  • product
  • categories
  • location

Searching is done by using the Database.search method.

In [ ]:
Database("forwast").search("food")

Searches can also be filtered (where only the results that meet the specified criteria are included) or masked (where results that meet the specified criteria are excluded).

By default we return 25 search results, but this can be changed by specifying the limit keyword argument.

You can also use * wild cards in search queries:

In [ ]:
Database("biosphere3").search("carb*", limit=10)

You can specify inclusion or exclusion criteria for fields with filter and mask:

In [ ]:
Database("biosphere3").search("carbon", filter={"categories": 'forestry'})
In [ ]:
Database("biosphere3").search("carbon", limit=10, mask={"categories": 'forestry'})

Finally, you can facet search results by another field. This is a bit complicated, so test your queries before assuming certain behaviour.

In [ ]:
sr = Database("biosphere3").search("carbon", facet="categories")
for key, value in sr.items():
    print("Facet term:", key, "\n\t", value[:3], "\n")

Changing iteration order

You can also change the way that processes are iterated over in the database. The default order is random:

In [ ]:
db = Database("forwast")

def print_10(db):
    for i, x in enumerate(db):
        if i < 10:
            print(x)
        else:
            break
            
print_10(db)
In [ ]:
print_10(db)

You can sort by location, name, product (reference product), or type, by specifying the order_by property.

In [ ]:
db.order_by = "name"
print_10(db)

If the above seems wrong, remember that the names start with 100, 101, etc.

Set .order_by = None to remove any ordering.

In [ ]:
db.order_by = None

Because accessing activities in the database is quite fast, you can also filter the activities you want by just iterating over the entire database:

In [ ]:
my_activities = [x for x in db if 'Electr' in x['name']]
my_activities

Basic LCA calculations

Let's pick and random process and LCIA method:

In [ ]:
process = Database("forwast").random()
process

A brief review of LCA calculations:

In matrix-based LCA, we construct a technosphere matrix, which describes the inputs needed to produce different products (e.g. cars need metal and electricity), and a biosphere matrix, which describes the emissions and resource consumption associated with the production of each product (e.g. car manufacturing releases air emissions). These two matrices come from the life cycle inventory database(s). We also have a functional unit, which is what we are trying to assess, e.g. one car. We then calculate the life cycle inventory (LCI) by first solving the linear system of the technosphere matrix and the functional unit, and then by multiplying the biosphere matrix.

To do life cycle impact assessment (LCIA), we multiply the life cycle inventory by a matrix of characterization factors, which tell how bad different emissions and resource consumptions are.

For more details on the math, see the manual.

So, our first step is to specify the functional unit, which is relatively easy:

In [ ]:
functional_unit = {process: 1}

We can then instantiate our LCA object.

In [ ]:
lca = LCA(functional_unit, method_key)

And do the LCI and LCIA calculations:

In [ ]:
lca.lci()
lca.lcia()

Finally, we can print the LCA score:

In [ ]:
lca.score

You can reuse the same matrices but change the functional unit by using the redo_lci and redo_lcia functions:

In [ ]:
new_process = Database("forwast").random()
print(new_process)
lca.redo_lcia({new_process: 1})
lca.score

Looking into the LCA object

Let's see what is in this LCA thing, anyway. Put your cursor in the following cell and hit tab:

In [ ]:
lca.

So, there is a lot. Let's look at a few things:

  • The technosphere matrix
In [ ]:
lca.technosphere_matrix
  • The biosphere matrix
In [ ]:
lca.biosphere_matrix
  • The characterization matrix
In [ ]:
lca.characterization_matrix

Graphing matrices

Sometimes it can be helpful to visualize both the inputs and calculation results.

In [ ]:
%matplotlib inline

If you get some warnings here, you can ignore them again.

In [ ]:
from bw2analyzer.matrix_grapher import SparseMatrixGrapher

First, let's look at the technosphere matrix.

You can pass in a filename to get a higher resolution figure saved as a file.

In [ ]:
SparseMatrixGrapher(lca.technosphere_matrix).ordered_graph()

Not so interesting - I am sure your inventory data will be much nicer. The problem is that this is an IO matrix, so there is a value at each point, even if it is small, and this graph only shows where values are or aren't zero. For example, here is the same graph for ecoinvent 3.2 (cutoff):

You can also graph the biosphere and any other LCA matrices.

Contribution analysis

We can calculate the most important activities and biosphere flows.

In [ ]:
from bw2analyzer import ContributionAnalysis

Most important activities

In [ ]:
ContributionAnalysis().annotated_top_processes(lca)

Most important biosphere flows

In [ ]:
ContributionAnalysis().annotated_top_emissions(lca)

Monte Carlo LCA

Unfortunately, the forwast database doesn't unclude uncertainty. Let's put some in anyways, using the utility function uncertainify.

In [ ]:
from bw2data.utils import uncertainify
from stats_arrays import NormalUncertainty
In [ ]:
uncertain_db = Database("forwast uncertain +")
uncertain_db.write(
    uncertain_db.relabel_data(
        uncertainify(
            Database("forwast").load(), 
            NormalUncertainty
        ), 
        "forwast uncertain +" 
    )
)

We can now calculate some Monte Carlo iterations for a random activity.

In [ ]:
mc = MonteCarloLCA(demand={uncertain_db.random(): 1}, method=method_key)
mc.load_data()
for x in range(10):
    print(next(mc))

That't it! Here is your promised kitten:

In [ ]:
from IPython.display import Image
import random
dimensions = sorted((int(random.random() * 600 + 200), int(random.random() * 600 + 200)))
Image(url="http://placekitten.com/{}/{}/".format(*dimensions))