Combining the inventories of two studies using the ArdaInventoryHybridizer class

This little script documents the combination of two distinct studies:

Ellingsen, Linda Ager-Wick, Guillaume Majeau-Bettez, Bhawna Singh, Akhilesh Kumar Srivastava, Lars Ole Valøen, et Anders Hammer Strømman. 2013. « Life Cycle Assessment of a Lithium-Ion Battery Vehicle Pack. » Journal of Industrial Ecology 18 (1): 113‑124. doi:10.1111/jiec.12072.

and

Hawkins, Troy R., Bhawna Singh, Guillaume Majeau-Bettez, et Anders Hammer Strømman. 2013. « Comparative Environmental Life Cycle Assessment of Conventional and Electric Vehicles. » Journal of Industrial Ecology 17 (1) (février 4): 53‑64. doi:10.1111/j.1530-9290.2012.00532.x.

Hawkins, Troy R, Bhawna Singh, Guillaume Majeau-Bettez, et Anders Hammer Strømman. 2013. « Corrigendum to: Hawkins, T. R., B. Singh, G. Majeau-Bettez, and A. H. Strømman. 2012. Comparative environmental life cycle assessment of conventional and electric vehicles. Journal of Industrial Ecology DOI: 10.1111/j.1530-9290.2012.00532.x. » Journal of Industrial Ecology 17 (1) (février 16): 158‑160. doi:10.1111/jiec.12011.

These two inventories were compiled in matrix form, for calculation in Matlab.

Imports


In [1]:
# import all standard modules
import numpy as np
import pandas as pd
import scipy.io as sio
import sys

In [2]:
# import the ArdaInventoryHybridizer class
sys.path.append('/home/bill/software/arda_inventory_hybridizer/')
import ArdaInventoryHybridizer

In [3]:
# import module to facilitate visual inspection of matrices
sys.path.append('/home/bill/software/Python/Modules/')
import matrix_view as mtv

Read data from Matlab .mat files


In [4]:
eco_orig = sio.loadmat('/home/bill/data/ecoinvent.2013-07-23.13.57/2.2/Ecoinvent_2_2_ReCiPe_H.mat')
eco_new = sio.loadmat('/home/bill/documents/arda/dev_arda_client/data/ecoinvent/2.2/Ecoinvent22_ReCiPe108_H.mat')
ecar_dict = sio.loadmat('/home/bill/documents/ecar/fullVehicle_Troy/A_matrix/ecarShortSystem4_arda.mat')
battery_dict = sio.loadmat('/home/bill/documents/ecar/batteryProductionLinda_final/flightCheck/batteryWithEco22_Recipe108H_Foreground.mat')

Define two ArdaInventoryHybridizer objects, one for each inventory


In [5]:
ecar2 = ArdaInventoryHybridizer.ArdaInventoryHybridizer(1)
battery = ArdaInventoryHybridizer.ArdaInventoryHybridizer()

Update the background for the e-car inventory by Hawkins et al.

Read in the original background and the original ecar foreground, using the extract_background_from_matdict() and the extract_foreground_from_matdict() methods of the ArdaInventoryHybridizer object.


In [6]:
ecar2.extract_background_from_matdict(eco_orig)
ecar2.extract_foreground_from_matdict(ecar_dict)

Read in the new ecoinvent matrix with improved characterisation factors, and match foreground to this new background (using the match_foreground_to_background() method of ArdaInventoryHybridizer object)


In [7]:
ecar2.extract_background_from_matdict(eco_new)
ecar2.match_foreground_to_background()

Remove the Li-ion battery from the e-car inventory to make room for the battery inventory by Ellingsen et al.


In [8]:
# Identify all processes with a battery
PRO_f = pd.DataFrame(ecar2.PRO_f)
bo_remove = PRO_f.ix[:,0].str.contains('.*attery.*')

# Get an idea of what processes would be removed
print(np.sum(bo_remove))
remove = PRO_f.ix[bo_remove,:]

# Do NOT remove inventories for Lead-acid battery
bo_notremove = remove.ix[:,0].str.contains('.*PbA.*')

# Remove neither processes marked as "nonbattery" nor the top process "Battery_Li_NCM"
# This top process will be used as "pointer" to link to the new battery inventory
bo_notremove = bo_notremove | remove.ix[:,0].str.contains('.*nonbattery.*') | remove.ix[:,0].str.contains('Battery_Li_NCM')

# Get an idea of what processes would be removed
bo_remove = bo_remove & ~ bo_notremove
print(np.sum(bo_remove))
# Get Ids of processes to remove
id_begone = PRO_f.ix[bo_remove,1].tolist()
print(id_begone)


58
54
[30012, 30031, 30040, 20040, 20041, 20042, 20070, 20071, 20072, 20073, 20074, 20075, 20076, 20077, 20479, 20480, 20482, 20483, 20485, 20487, 20489, 20490, 20492, 20493, 20494, 20495, 10205, 10206, 10207, 10208, 10209, 10210, 10211, 10212, 10526, 10528, 10529, 10530, 10531, 10532, 10533, 10535, 10536, 10537, 10538, 10541, 10546, 10549, 10550, 10551, 10552, 10553, 10554, 10555]

Delete these processes (using the delete_process_foreground() method of the ArdatInventoryHybridizer object)


In [9]:
ecar2.delete_processes_foreground(id_begone)

Adapt the Ellingsen battery inventory

Read in the original background and the original battery foreground using the extract_background_from_matdict() and the extract_foreground_from_matdict() methods of the ArdaInventoryHybridizer object.


In [10]:
battery.extract_background_from_matdict(eco_new)
battery.extract_foreground_from_matdict(battery_dict)

Avoid id conflicts between the two inventories, change the Ids using the increase_foreground_process_ids() of the ArdaInventoryHybridizer object.


In [11]:
battery.increase_foreground_process_ids(70000)

Combine the two inventories

Combine the ecar and the battery foregrounds using the append_to_foreground() method of the ArdaInventoryHybridizer object.


In [12]:
ecar2.append_to_foreground(battery)

Make the link between the two foregrounds


In [13]:
# 30030: 'Battery_Li_NCM'
# 40001: 'EV Li-NCM Euro'
# 80001: The complete s+orm battery from Ellingsen et al.

# Make sure that each Ev_Li-NCM requires 253 kg of Battery_Li_NCM
ecar2.A_ff.ix[30030, 40001] = 253

# Make sure that each kg of Battery_Li_NCM requires 1 kg of s+orm
ecar2.A_ff.ix[80001,30030] = 1

Visually inspect the battery row, to see what ecar processes use this battery


In [14]:
mtv.inspect_row(ecar2.A_ff, 30030, labels=ecar2.PRO_f.iloc[:,0], keep_index=False)


[['EV Li-NCM Euro' 253.0]
 ['EV Li-NCM NG' 214.0]
 ['EV Li-NCM C' 214.0]]
Out[14]:
array([['EV Li-NCM Euro', 253.0],
       ['EV Li-NCM NG', 214.0],
       ['EV Li-NCM C', 214.0]], dtype=object)

Visually inspect the column, to see which processes the battery uses


In [15]:
mtv.inspect_col(ecar2.A_ff, 30030, labels=ecar2.PRO_f.iloc[:,0], keep_index=False)


[['s+orm' 1.0]]
Out[15]:
array([['s+orm', 1.0]], dtype=object)

Good, the connection seems to have been well made.

Estimate mass of components, save mass metadata in labels

Many processes in the foreground are not recorded relative to a standard unit (kg, MJ, etc.) but rather "per unit". Though this makes sense (one car, one bumper, etc.), it can make it more difficult to relate it to prices expressed in $/kg, notably in an effort to hybridize the inventory.

Luckily, the inventory is structured such that the mass of each component can easily be estimated. All inputs that contribute to the mass of the product are recorded with indexes in the 10000-20000 range.


In [16]:
# For visual inspection, get inputs that contribute to the mass of Body_and_Doors (process 20006):
pd.DataFrame(mtv.inspect_vect(ecar2.A_ff[20006].values, ecar2.PRO_f, verbose=False))


Out[16]:
0 1 2 3 4 5 6 7 8 9
0 Body_and_Doors: EAF steel/kg 10002 EAF steel Body_and_Doors [[]] 0 [[]] 0 kg 4.21671
1 Body_and_Doors: Plastic/kg 10015 Plastic Body_and_Doors [[]] Polymers [[]] 0 kg 3.424658
2 Body_and_Doors: Scrap, EAF steel/kg 10018 Scrap, EAF steel Body_and_Doors [[]] 0 [[]] 0 kg 0.8859578

Compile list of component masses by summing their input materials


In [17]:
mass_lists = []
for i in ecar2.A.index:
    if (i >= 20000) and (i < 30000) :
        materials = pd.DataFrame(mtv.inspect_vect(ecar2.A_ff[i].values, ecar2.PRO_f, verbose=False))
        bo_mass = (materials.iloc[:,-2] == 'kg').values
        mass = materials.iloc[bo_mass, -1].sum()
        mass_lists.append([i, mass])
masses = pd.DataFrame(mass_lists)
masses = masses.set_index(0, True)
masses.columns=['MASS']

Integrate these masses as meta-data in the labels


In [18]:
tmp = pd.concat([ecar2.PRO_f.iloc[:, 0:-1], masses, ecar2.PRO_f.iloc[:,-1]], axis=1)
tmp = tmp.reindex_axis(ecar2.PRO_f.index, 0).fillna('')
ecar2.PRO_f = tmp.copy()

Clean up!


In [19]:
# Remove any calculate masses from use phase or end of life. Not applicable.
bo = (ecar2.PRO_f.FULLNAME.str.contains('Use_Phase') |
      ecar2.PRO_f.FULLNAME.str.contains('EOL'))
ecar2.PRO_f.ix[bo, 'MASS'] = 0.0

# Fill all other masses with zeros
for i, row in ecar2.PRO_f.iterrows():
    try:
        int(row.MASS)
    except:
        row.MASS = 0.0

Export new combined foreground to matlab file

Peform the export using the to_matfile() foreground and background export method of the ArdaInventoryHybridizer object


In [20]:
# save memory, clean up
import gc
del ecar_dict, eco_new, eco_orig, PRO_f, battery, battery_dict, masses, materials, remove, tmp
gc.collect()


Out[20]:
395

In [21]:
ecar2.to_matfile('/home/bill/documents/ecar/hybridized_study/ecar_combined_eco22Recipe108H.mat', foreground=True, background=False)

Check validity of combination with a back-of-the-envelope calculation

Calculate the expected lifecycle GWP based on published numbers, and then calculate this impact with the new combined inventory.


In [22]:
# Expected approximate lifecycle GWP values for whole system, considering published values

gwp_corrigendum =   27165.31             # for FU=150 000 km, i.e. 181 gCO2eq/km
gwp_MajeauBettez_battery = 214 * 22      # 214 kg battery pack initially in ecar, at 22 kg of CO2 per kg battery
gwp_Ellingsen_battery =  4580            # for battery pack of 253 kg, i.e., ca. 18 kg CO2 per kg battery

print('gwp of car lifecycle without battery: ', gwp_corrigendum - gwp_MajeauBettez_battery)
expected_gwp = gwp_corrigendum - gwp_MajeauBettez_battery + gwp_Ellingsen_battery
print('expected lifecycle gwp of car with new battery: ', expected_gwp)
print('')


gwp of car lifecycle without battery:  22457.31
expected lifecycle gwp of car with new battery:  27037.31

Calculate lifecycle impact of combined system using calc_lifecycle() method of ArdaInventoryHybridizer object


In [27]:
d = ecar2.calc_lifecycle('impacts')
d.ix[20,:].values


Out[27]:
array([ 26518.74836374])

Calculated value within 1% of approximated value. Can be explained by different rounding and by use of different characterisation factor matching in the background. Combination of the two inventories is considered successful.