Supply and Use Tables (SUT) are a standard accounting framework for industrial activities (http://unstats.un.org/unsd/statcom/doc08/SNA-Chapter14.pdf). They are the basis of input-output models and can be used in modeling product systems in life cycle assessment (Suh and Weidema 2010) The pySUT package offers a toolbox for modification of SUTs, and a comprehensive set of methods to allocate co-production to transform the SUT into an input-output model (Majeau-Bettez et al. 2014). This project is work in progress, and a first, unit-tested version is released on GitHub. Below you find a quick tutorial and demonstration of the central feastures of the class SupplyUseTable.
We provide two advanced examples for multiregional SUTs, one for the generalized version of the commodity technology construct and another one for the generalized version of the byproduct technology construct.
For more documentation, please check the source code.
Python 3.0 or later
numpy
scipy
matplotlib.pyplot
This is the easiest way of installing pySUT. Github hosts an installation package for pySUT, which can be downloaded directly from the command line using pip:
pip install pySUT
Pull the package via git pull or download as .zip file and unpack. Choose a convenient location (Here: 'C:\MyPythonPackages\'). Then open a console, change to the directory ../pySUT-master/, and install the package from the command line:
python setup.py install
This makes the package available to Python. At any other place in a system with the same python installation, pydsm is now ready to be imported simply by
import pysut
This setup also allows us to run the unit test:
import unittest
import pysut
import pysut.tests
unittest.main(pysut.tests, verbosity=2)
Or, to run a specific test
unittest.main(pysut.tests.test_allocations_constructs, verbosity=2)
Pull package via git pull or download as .zip file and unpack. Choose a convenient location (Here: 'C:\MyPythonPackages\'). The folder with the class is called pySUT-master, and there is another folder named pydsm inside. The latter one contains the actual class, you can see this from the presence of __init__.py Then the class needs to be imported. This can be done by adding the path of the class file pysut.py to the system path, and to import the class from there:
In [1]:
import sys
sys.path.append('C:\\MyPythonPackages\\pySUT-Master\\pysut\\')
Just put your own path in the command above, and use \ for subfolders. Again, the paths needs to point to the folder where the __init__.py is located. Now, the class can be imported:
In [2]:
from pysut import SupplyUseTable
Now, the class is ready to use. We first define a simple supply and use table:
In [3]:
import numpy as np
Y_Test = np.array([11,4,3,0,5])
V_Test = np.array([ [30,1,1,1,0], # in pySUT, the supply table is always in commodity by industry!
[1,5,1,0,0],
[2,0,3,1,0],
[0,0,1,0,0],
[0,0,0,0,8]])
U_Test = np.array([ [12,4,5,1,0],
[1,1,1,0,0],
[0,1,1,1,0],
[1,0,0,0,0],
[0,1,1,1,0]])
F_Test = np.array([ [30,5,18,22.03,7],
[0.4,0.04,1,0.98,0.8]])
We now create an instance of the SUT object and perform a number of operations.
In [4]:
TestSUT = SupplyUseTable(V = V_Test, U = U_Test, Y = Y_Test, F = F_Test)
TestSUT.dimension_check()
Out[4]:
An important quality check is the market balance B: $$ B = V\cdot e - U\cdot e - Y\cdot e$$
In [5]:
MarketBal = TestSUT.market_balance()
print(MarketBal)
With U, V, and F given, we can apply constructs to the SUT, for example, the industry technology construct (ITC):
In [6]:
A_ITC = TestSUT.Build_ITC_A_matrix_cxc()
print(A_ITC)
S_ITC = TestSUT.Build_ITC_cxc_S()
print(S_ITC);
In the printout above, the upper matrix is the A-matrix in the ITC construct, and below, the allocated emissions S are displayed.
Supply and use tables are commonly not square, and the order of products or industries may not be as desirable. The following 5x4 tables cannot by turned into an A-matrix without further assumptions, and we resolve this conflict by aggregating products 3 and 4. At the same time, we re-sort the products.
In [7]:
import numpy as np
V_Test_nonsquare = np.array([ # in pySUT, the supply table is always in commodity by industry!
[30,1,1,1,0],
[1,5,1,0,0],
[1,0,1,1,0],
[1,0,2,1,0],
[0,0,1,0,0],
[0,0,0,0,8]])
U_Test_nonsquare = np.array([
[12,4,5,1,0],
[1,1,1,0,0],
[0,1,0,1,0],
[0,0,1,0,0],
[1,0,0,0,0],
[0,1,1,1,0]])
NonSquareSUT = SupplyUseTable(V = V_Test_nonsquare, U = U_Test_nonsquare)
AggregationVector = np.array([0,1,2,2,3,4]) # Aggregate products 2 and 3 to 2
AggregationMatrix = NonSquareSUT.build_Aggregation_Matrix(AggregationVector)
ResortingVector = np.array([3,1,2,0,4]) # Swap positions of products and industries 0 and 3
ResortingMatrix = NonSquareSUT.build_Aggregation_Matrix(ResortingVector)
Both aggregation and resorting use the same routine for creating a matrix out of the aggregation/resorting vector/list. The difference is that the method aggregate_rearrange_products(PA, PR) applies the aggregation matrix only to the products, but the resorting is applied to both products and industries. We can now aggregate and resort the SUT, and apply the CTC construct, for example.
In [8]:
NonSquareSUT.aggregate_rearrange_products(AggregationMatrix,ResortingMatrix)
print(NonSquareSUT.V)
print(NonSquareSUT.U)
In [9]:
A_CTC = NonSquareSUT.Build_CTC_A_matrix_cxc()
print(A_CTC)
Under the alternative activity assumption, the 'production recipe' of an alternative activity is taken as proxy to disaggregate the coproducts from a given activity (Majeau-Bettez et al. 2014). In the example below, industry 1 has product 2 as byproduct, and industries 2 and 3 have product 2 as their main product. The Gamma matrix assigns an alternative producer to each commodity, and for product 2, this is industry 3. The 'production recipe' of industry 3 is then used as a proxy to allocate the requirements for the production of product 2 in industry 1. The E_bar matrix contains the information about the main products of each industry. The resulting A matrix has the size 3x3. This construct is the generalized version of the commodity technology assumption.
In [10]:
V = np.array([[2, 0, 0, 0], # Supply table, two suppliers that have commodity 2 as main product
[1, 1, 3, 0],
[0, 0, 0, 11]], dtype=float)
U = np.array([[ 0. , 0. , 0. , 0. ], # Use table, 3 products x 4 industries
[ 0. , 0. , 0. , 0.75],
[ 4. , 0.75, 2. , 0. ]])
# Define alternate activities, used in alternative activity allocation.
Gamma = np.array([ # industry by commodity table, each product has one alternative supplier
# i j k
[1, 0, 0], # I
[0, 0, 0], # J1
[0, 1, 0], # J2
[0, 0, 1] # K
])
# Identifies primary product of each industry
E_bar = np.array([
# I J1 J2 K
[1, 0, 0, 0], # i
[0, 1, 1, 0], # j
[0, 0, 0, 1], # k
])
SUT = SupplyUseTable(V =V, U =U, Gamma =Gamma, E_bar =E_bar)
A, __, __, __, Z, __ = SUT.aac_agg(keep_size=False)
print(A)
pySUT contains routines to estimate both the E_bar and the Gamma matrix. The estimation principles are documented in the source code. With the 3 region 3 products 2 industries supply table defined below, we can obtain the following matrix of main products. Note that the column for industry J in No is empty, because it does not produce anyting.
In [11]:
# Case 2: 3 regions, 2 industries, 3 products:
#--------------------------------------------
# No production of j in Canada
# No production of i or k in Norway
# No production of j in US
V_3r2i3p = np.array([
# Ca Ca No No US US
# I J I J I J
[ 4., 0., 0., 0., 0., 0., ], #i Ca
[ 0., 0., 0., 0., 0., 0., ], #j Ca
[ 0., 3., 0., 0., 0., 0., ], #k Ca
#
[ 0., 0., 0., 0., 0., 0., ], #i No
[ 0., 0., 3., 0., 0., 0., ], #j No
[ 0., 0., 0., 0., 0., 0., ], #k No
#
[ 0., 0., 0., 0., 8., 0., ], #i US
[ 0.13, 0.2, 0.45, 0., 0.5, 0.78, ], #j US
[ 0., 0., 0., 0., 0., 9., ]]) #k US
U_3r2i3p = np.array([
# Ca Ca No No US US
# I J I J I J
[ .0, .1, .5, 0, 0, 0, ], #i Ca
[ 0, 0, 0, 0, 0, 0, ], #j Ca
[ .2, .0, 0, 0, 0, 0, ], #k Ca
#
[ 0, 0, 0, 0, 0, 0, ], #i No
[ .3, 0, .0, 0, .5, 0, ], #j No
[ 0, 0, 0, 0, 0, 0, ], #k No
#
[ 0, 0, 0, 0, .0, .9, ], #i US
[ 0, 0, 0, 0, 0, 0, ], #j US
[ .1, 0, .4, 0, .5, .0, ]]) #k US
SUT = SupplyUseTable(V = V_3r2i3p, regions =3)
SUT.build_E_bar()
print(SUT.E_bar)
With the same instance of the class, we can also estimate the alternate activity matrix Gamma, and then build a multiregional A-matrix in the alternative activity construct (AAC), the generalized version of the commodity technology construct.
In [13]:
SUT_new = SupplyUseTable(V = V_3r2i3p, U = U_3r2i3p, E_bar = SUT.E_bar, regions =3)
SUT_new.build_mr_Gamma()
print(SUT_new.Gamma)
A, __, __, __, Z, __ = SUT_new.aac_agg(keep_size=True, res_tol = 1e-8)
print(A)
The principle behind the byproduct technology assumption is that the byproducts substitute for other products in the system. The question is, which product they substitute for and what is the ratio of substitution? For the BTC to work, each product needs to have a main supplier, which in the logic of the BTC is also the marginal supplier. To prepare application of the BTC or its generalized version, the product substitution construct (Majeau-Bettez et al. 2014), one needs to study the supply table to understand whether exclusive byproducts are present. For this method to work, one needs to make sure that the supposed main product is on the diagonal of the supply table. If this is not the case, one can use the E_bar method to estimate the main products.
In [14]:
V_Test = np.array([ [30,1,1,1,0,0], # in pySUT, the supply table is always in commodity by industry!
[1,0,1,0,0,0],
[2,0,3,1,0,0],
[0,0,0,0,0,0],
[0,0,0,0,8,0],
[0,0,0,0,0,0]])
SUT = SupplyUseTable(V = V_Test)
SupplyDiag_Eval = SUT.supply_diag_check()
print(SupplyDiag_Eval)
A 1 in the first column means that the sector produces its presumed main product. (Normal case) A 1 in the second column means that the presumed main producer does not supply this product. (Exclusive byproduct) A 1 in the third column means that the product is only produced by other sectors, this sector does not produce anything. A 1 in the forth column means that the product is not produced by any sector, apparent main sector produces only other products. A 1 in the fifth column means that the product is not produced by any sector and the apparent main sector does not produce anyting.
The rightmost column shows the total supply of each industry (g). The second column from the right shows the total supply of each commodity (q).
The exclusive byproducts can be removed or aggregated with other sectors for the BTC construct to work. For this purpose, pySUT contains the 'clear_non_diag_supply' and 'add_ones_to_diagonal' methods. The first removes all production from the SUT, where there is no main producer, and adds a dummy '1' to the diagonal element of that sector. This methods allows users to apply the BTC in a 'brute force' manner. For 'cleaner' application, the build_E_bar and psc_agg methods are recommended, and we provide an example for the multiregional case:
For multiregional SU-tables, one can assume that the exclusive byproducts of one region substitute the output of main/marginal suppliers in other regions. In the simplest case, this substitution occurs according to the market share of the main suppliers on the world market. This estimation was used in the build_mr_Xi method, which builds a substitution table for the (exclusive) by-products (Majeau-Bettez et al. 2014).
In [15]:
V_3r2i3p = np.array([
# Ca Ca No No US US
# I J I J I J
[ 4., 0., 0., 0., 0., 0., ], #i Ca
[ 0., 0., 0., 0., 0., 0., ], #j Ca
[ 0., 3., 0., 0., 0., 0., ], #k Ca
#
[ 0., 0., 0., 0., 0., 0., ], #i No
[ 0., 0., 3., 0., 0., 0., ], #j No
[ 0., 0., 0., 0., 0., 0., ], #k No
#
[ 0., 0., 0., 0., 8., 0., ], #i US
[ 0.13, 0.2, 0.45, 0., 0.5, 0.78, ], #j US
[ 0., 0., 0., 0., 0., 9., ]]) #k US
U_3r2i3p = np.array([
# Ca Ca No No US US
# I J I J I J
[ .0, .1, .5, 0, 0, 0, ], #i Ca
[ 0, 0, 0, 0, 0, 0, ], #j Ca
[ .2, .0, 0, 0, 0, 0, ], #k Ca
#
[ 0, 0, 0, 0, 0, 0, ], #i No
[ .3, 0, .0, 0, .5, 0, ], #j No
[ 0, 0, 0, 0, 0, 0, ], #k No
#
[ 0, 0, 0, 0, .0, .9, ], #i US
[ 0, 0, 0, 0, 0, 0, ], #j US
[ .1, 0, .4, 0, .5, .0, ]]) #k US
SUT = SupplyUseTable(V = V_3r2i3p, U = U_3r2i3p, regions =3)
SUT.build_E_bar()
SUT.build_mr_Xi()
print(SUT.Xi)
This Xi matrix is matrix-multiplied to the by-products of the supply table to assign them to main products that are substituted by the byproduct flows.
Finally, the A matrix in product substitution allocation, the general version of the byproduct technology construct, can be determined:
In [16]:
A, __, __, __, __, __, Z, __ = SUT.psc_agg()
print(A)
A salient feature of the BTC/PSC is that it allows for re-constructing the unallocated supply and use table to obtain the byproduct flows before substitution. A balances and technically meaningful description of the industrial processes can be obtained for any final demand. The equations are given for the BTC case, the more general PSC case is analog. Here, $\hat{V}$ denotes the diagonal of the supply table (the main products), and $\check{V}$ denotes the off-diagonal elements of the supply table (the by-products). $$ A_{BTC} = (U-\check{V})\cdot \hat{V} = U\cdot \hat{V} - \check{V}\cdot \hat{V} = A_{main} - A_{byprod}$$
With any given $x$ $$ x = L\cdot y = (I-A_{main} + A_{byprod})^{-1}\cdot y$$ one can reconstruct the new SUT: $$\hat{V}_{new} = \hat{x}$$ $$U_{new} = A_{main}\cdot\hat{x}$$ $$\check{V}_{new} = A_{byprod}\cdot\hat{x}$$
We provide an example with the psc_agg method. For the simpler BTC case, the methods Build_BTC_Am_matrix and Build_BTC_Ab_matrix can be used.
In [17]:
V_Test_byprod = np.array([[30, 1, 1, 1, 0],
[1, 5, 1, 0, 0],
[2, 0, 3, 1, 0],
[0, 0, 1, 2, 0],
[0, 0, 0, 0, 8]])
U_Test_byprod = np.array([[12, 4, 5, 1, 0],
[1, 1, 1, 0, 0],
[0, 1, 1, 1, 0],
[1, 0, 0, 0, 0],
[0, 1, 1, 1, 0]])
sut = SupplyUseTable(U =U_Test_byprod, V= V_Test_byprod)
sut.build_E_bar() # E_bar is a unit matrix with the given example
sut.build_mr_Xi() # Xi is a unit matrix with the given example
print(sut.E_bar)
print(sut.Xi)
In [18]:
A, Am, Ab, __, __, __, Z, F_con = sut.psc_agg(keep_size=False)
print(Am)
print(Ab)
print(A)
In [19]:
%matplotlib inline
# this is only for the IPython notebook, not part of the python script!
import matplotlib.pyplot as plt
plt.imshow(Am,interpolation='nearest')
plt.title('Technical coefficients for main products')
plt.show();
plt.imshow(-Ab,interpolation='nearest')
plt.title('Technical coefficients for byproducts')
plt.show();
This is all for the moment. Comments are welcome!