# -*- coding: utf-8 -*-
"""
Created on Tue Jan  1 21:53:30 2013

@author: Alex
"""

import DataClasses as dc
import numpy as np
import copy

##################################
# Begin Electricity Built-ins
##################################
#Units are Ops (/MWh delivered) Const (/MW built) Fuel (/thermal MWh delivered)
def main():
    sources = [dc.Source('Coal',1,.35,.6)]
    sources[0].addOpsMode('Open-Loop',[-4,.4,.6,.8], [-4,.3,.35,.4])
    sources[0].setLCA(0,0,[
            [-4,580,830,1070], #NREL Harmonization
            [33.8,0,0,0], #MJ/MWh, CED based on ecoinvent US plant average sem inf & EVoF
            [1.1356,0,0,0], #m^3/MWh #from EPRI's 400 gal/MWh avg. + ReCiPe evaluation on RFC plants
            [0.39e-9,0,0,0], #km^2/MWh, F&K operations
            [49,0,0,0]]) #EIA Fixed O&M + Interest @7%,30yrs
    sources[0].addConstMode('Pulverized',4,500)
    sources[0].setLCA(1,0,[
            [-5,224134,50011,0],# #kg CO2-eq/MW cap, estimated from NG
            [-4,3.29e6,2.13e6,5.16e6],# #MJ/MW
            [-5,2268,623,0],# #m^3/MW  needs checking
            [-4,0.002904,.00118,.00679],# #km^2/MW, F&K based on 1000MW in 500acres (avg of their values)
            [-3,2.8e6,3.2e6,0]])# #$/MW installed, EIA Overnight capital costs from AEO 2011
    sources[0].addFuelMode('Surface')
    sources[0].setLCA(2,0,[
            [150,0,0,0],
            [247,0,0,0],
            [.08395,0,0,0],
            [-4,400e-9,43e-9,840e-9], ##F&K based on 35# CU
            [5,0,0,0]]) #EIA Fixed O&M + Interest @7%,30yrs
    sources[0].addFuelMode('Underground')
    sources[0].setLCA(2,1,[90,0,3.23e-5,0],0) #Water from Harvard Study
    sources[0].setLCA(2,1,[-4,67e-9,2.3e-9,200e-9],3)
    sources[0].addOpsMode('Closed-Loop',[-4,.2,.5,.8], [-4,.35,.3,.4])
    sources[0].setLCA(0,1,[1.817],2)

    sources.append(dc.Source('Natural Gas',1,.35,.35, v=2))
    sources[1].addOpsMode('CT',[-4,.15,.35,.8], [-4,.38,.3,.45])
    sources[1].setLCA(0,0,[
            [500,0,0,0], #kg/MWh, data from Jaramillo
            [48.4,0,0,0], #MJ/MWh, CED based on ecoinvent US plant average sem inf
            [-3,.681,1.13,0], #EPRI, range covering NGCC to CT with towers
            [30/1e9,0,0,0], #km^2/MWh, F&K (needs assumptions checked)
            [28,0,0,0]]) #EIA Fixed O&M + Interest @7%,30yrs
            #,0.379,1.817 for water alternatives
    sources[-1].addOpsMode('Closed-Loop',[-4,.2,.5,.8], [-4,.35,.3,.4])
    sources[-1].setLCA(0,1,[-3,1.1356,1.817,0],2)
    sources[1].addConstMode('CT', 3, 100)
    sources[1].setLCA(1,0,[
            [-5,4.74e4,1.76e4,0], #kg CO2-eq/MW, ecoinvent 300MWe plant
            [-5,5.59e6,2.2e5,0], #MJ/MW, ecoinvent 300MWe plant + CED 1.08
            [-5,281,76.7,0], #m^3/MW, ecoinvent 300MWe plant + ReCiPe H2O depletion
            [0.001023,0,0,0], #km^2/MW, F&K inference
            [-4,665e3,927e3,1003e3]]) #EIA Study
    sources[1].addFuelMode('pipeline')
    sources[1].setLCA(2,0,[
            [93.2,0,0,0],
            [180,0,0,0], #Based on EROI of 20:1
            [-4,0.037,0.018,0.054],
            [240e-9,0,0,0],
            [-5,3.8,.2,0]])
    sources[1].addFuelMode('Marcellus')
    sources[1].setLCA(2,1,[
            [-4,117,129,240], #Our Study, 37% efficient
            [-4,180,197.3,900], #Our study, 90% confidence
            [-4,0.028,0.057,0.117],#Harvard study
            [120e-9,0,0,0]],0) #500km of pipeline

        #Oil
    sources.append(dc.Source('Oil',1,.35,.35))
    sources[2].addOpsMode('CT',[-4,.1,.35,.4],[-4,.3,.35,.4])
    sources[2].addConstMode('CT',3,100)
    sources[2].addFuelMode('Pipeline')
    sources[2].setLCA(0,0,[
            [700,0,0,0], #kg/MWh, data from Jaramillo
            [630,0,0,0], #MJ/MWh, CED based on ecoinvent US plant average sem inf
            [-3,0.681,1.136,0], #EPRI, range covering NGCC to CT with towers
            [30/1e9,0,0,0], #km^2/MWh, F&K (needs assumptions checked)
            [30,0,0,0]]) #EIA Fixed O&M + Interest @7%,30yrs
    sources[-1].addOpsMode('Closed-Loop',[-4,.2,.5,.8], [-4,.35,.3,.4])
    sources[-1].setLCA(0,1,[-3,1.136,1.817,0],2)

    sources[2].setLCA(1,0,[
            [5e4,0,0,0], #kg CO2-eq/MW, estimated from NG
            [-5,7.56e5,2.2e5,0], #MJ/MW, ecoinvent 300MWe plant + CED 1.08
            [287,0,0,0], #m^3/MW, ecoinvent 300MWe plant + ReCiPe H2O depletion
            [0.001023,0,0,0], #km^2/MW, assumed same as NG from F&K
            [-4,665e3,974e3,1003e3]]) #EIA Study (No one will build an oil fired plant)

    sources[2].setLCA(2,0,[
            [93,0,0,0], #assume same as NG
            [328,0,0,0],
            [-4,0.9e-3,1.1e-3,1.3e-3],
            [0,0,0,0],
            [-5,27.73,1,0]])#estimates based on 75-91 $/bbl and .30 reference efficiency

        #Nuclear
    sources.append(dc.Source('Nuclear',1,.35,.85))
    sources[3].addOpsMode('Closed-Loop',[-4,.7,.85,.95],[.97,0,0,0])
    sources[3].setLCA(0,0,[
            [-4,8,4,15], #kg/MWh, from F&K, baseline matches with Hondo
            [23,0,0,0], #MJ/MWh, CED based on ecoinvent PWR sem inf and fuel
            [-5,2.725,.15,0], #m^3/MWh 400-720gal/MWh from EPRI
            [5/1e9,0,0,0], #km^2/MW, F&K, no fuel disposal
            [69,0,0,0]]) #$/MWh operations, via PNE Nuclear - 5000/kW, 10# discount
    sources[3].addOpsMode('Open-Loop',[-4,.7,.85,.95],[.97,0,0,0])
    sources[3].setLCABase(0,1,[1.514],2)
    sources[3].addConstMode('Normal',10,1200)
    sources[3].setLCA(1,0,[
            [-4,6.64e5,2.97e5,9.978e5], # Converted from F&K using 40yrs and .85
            [-5,1.13e7,4.98e6,0], #MJ/MW, ecoinvent 1000MWe PWR, US case
            [3460,0,0,0], #m^3/MW, ecoinvent 1000MWe PWR, US case
            [-4,0.00211,.00106,.00413], #km^2/MW, Angra 3 (benefits of shared inf.) F&K value of 0.016
            [-3,4.6e6,6e6,0]]) #$/MW installed, EIA overnight costs with range from PNE
    sources[3].addFuelMode('Diffusion')
    sources[3].setLCA(2,0,[
            [-4,15,11,27], #F&K range
            [129,0,0,0], #CED for 1MWh worth of fuel from ecoinvent
            [-4,.351,.185,.517], #Harvard Study
            [(30+10+3)/1e9,0,0,0], #F&K Mining and milling/GWh
            [6.68,0,0,0]]) #NEI/DOE data
    sources[3].addFuelMode('Centrifuge')
    sources[3].setLCABase(2,1,[32,0,0,6.8],1)

        #Hydro - combination of US and BR data
    sources.append(dc.Source('Hydro',1,refCF=.55, v=3))
    sources[-1].addFuelMode('Default')
    sources[4].addOpsMode('Boreal',[-4,.4,.55,.7],[.97,0,0,0])
    sources[4].addConstMode('Boreal',5,1000)
    sources[4].setLCA(0,0,[
            [-5,11.48,1.2,0],  #-4,29,4.3,77 kg/MWh, data from Fearnside and Rosa, 10# SD for uncertainty
            [0.54,0,0,0], #MJ/MWh, CED based on ecoinvent US plant average sem inf
            [-5,2.62,1,0], #m^3/MWh
            [0,0,0,0], #km^2/MW
            [42+2.5,0,0,0]]) #EIA Fixed O&M + Interest @7%,30yrs, + Variable O&M
    sources[4].setLCA(1,0,[
            [3.91e6,0,0,0], #kg CO2-eq/MW cap, based on Ribeiro LCI for Itaipu 6.9e5 for Chinese EIO-LCA based study
            [-5,1.52e7, 1e6,0], #MJ/MW, ecoinvent non-alpine dam (at 2500MW avg.), median Chinese, low Ribeiro, high ecoinvent
            [9682,0,0,0], #m^3/MW, ecoinvent non-alpine (@2500MW avg.), large wet gravel usage accounts for 63# of original
            [-5,0.3672,0.035,0], #km^2/MW weighted average of 23 BR Dams - Scenario_Data
            [-5,2.347e6,1e5,0]]) #$/MW installed, EIA Overnight costs

        #Biomass - Brazilian Data right now
    sources.append(dc.Source('Biomass',1,.35,.35, v=3))
    sources[5].addOpsMode('Upgrade',[-4,.2,.35,.5],[-3,.18,.25,0])
    sources[5].addConstMode('Upgrade',2,50)
    sources[5].setLCA(0,0,[
            [25,0,0,0], #kg/MWh, Arguments of allocation from Seabra, 25 in JEPO 2011, 7 from van den Broek
            [140,0,0,0], #MJ/MWh, 140 based on Mauritius study (about x2 NG from CED in ecoinvent)
            [-3,1.158,1.817,0], #From EPRI's 400gal/MWh avg. (224 for Mauritius, but that is likely irrigated)
            [9.16e-6,0,0,0], #from F&K (land is occupied even if it's not consumed) (.00203 from Mauritius - allocation?)
            [133,0,0,0]]) #$/MWh operations, Seabra & Macedo, allocated based on value of products

    sources[5].setLCA(1,0,[
            [4e4,0,0,0],
            [1e6,0,0,0],
            [300,0,0,0],
            [0.00203,0,0,0], #(.00203 from Mauritius - allocation?)
            [-4,100e3,200e3,700e3]])# USDA TechLine
    sources[5].addConstMode('New',3,50)
    sources[5].setLCA(1,1,[[-5,3.86e6,1e5,0]],4) #EIA overnight costs
    sources[5].addFuelMode('Wood')
    sources[5].setLCA(2,0,[
            [-3,48,54,0], #JEPO 2011
            [-1,np.log(10**2.0901),.25,0],
            [0,0,0,0], #Assumption that it's not irrigated
            [-4,1.43e-7,2.14e-7,4.29e-7], #FromUSFS study - White, 2010
            [5,0,0,0]]) #Varible O&M from EIA

    #Wind: impacts per turbine (/MW direct), with number of turbines
    #required coming from the interaction of scenario requirements.
    sources.append(dc.Source('Wind',1,refCF=.35,v=0))
    sources[-1].addFuelMode('Default')
    sources[6].addOpsMode('Onshore',[-4,.25,.32,.40],[.97,0,0,0])
    sources[6].addOpsMode('Offshore',[-4,.35,.45,.5],[.97,0,0,0])
    sources[6].addConstMode('Onshore',1,10)
    sources[6].setLCA(0,0,[
            [-4,1.3,.12,6.7], #kg/MWh
            [4,0,0,0], #MJ/MWh, CED based on ecoinvent 800kW plant average sem inf
            [0,0,0,0], #m^3/MWh
            [0,0,0,0], #km^2/MW
            [73,0,0,0]]) #$/MWh, Investment costs from PNE for hydro at 10# discount. Itaipu contract in 2009 is 113, but operations costs via INL are $7/MWh
    sources[-1].setLCABase(0,1,[173],4)

    sources[6].setLCA(1,0,[
            [-4,620000,190000,2300000], #kg CO2-eq/MW, offshore from ecoinvent, needs more wires
            [-5,5.2e6,1e5,0], #MJ/MW, onshore from Lenzen & Wachsmann (BR)
            [8.08e3,0,0,0], #m^3/MW, onshore (2MW) from ecoinvent, ReCiPe
            [-4,0.15,.2,.25], #km^2/MW, from NREL estimates of 5MW/km2
            [-4,2e6,2.4e6,2.8e6]]) #$/MW installed, http://www.nrel.gov/docs/fy12osti/53510.pdf
    sources[-1].addConstMode('Offshore',2,40)
    sources[-1].setLCA(1,1,[
            [-4,620000,190000,2300000],
            [-5,9.72e6,1e5,0],
            [14.04e3,0,0,0],
            [0,0,0,0],
            [-4,3.59e6,4.25e6,5.9e6]]) #ARUP report on future costs

        #Solar
        #7.19 $/MWh for hybrid plant

    sources.append(dc.Source('Solar',1,refCF=.35,v=0))
    sources[-1].addFuelMode('Default')
    sources[-1].addOpsMode('PV',[-4,.18,.12,.22],[.97,0,0,0])
    sources[-1].addOpsMode('CST0',[-4,.30,.25,.35],[-4,.37,.32,.39])
    sources[7].addOpsMode('CST6',[-4,.45,.4,.5],[-4,.37,.32,.39])
    sources[7].addOpsMode('CST12',[-4,.60,.5,.65],[-4,.37,.32,.39])
    sources[-1].addConstMode('PV',1,0.05)
    sources[-1].addConstMode('CST',2,1)
    sources[7].addConstMode('CST6',2,1)
    sources[7].addConstMode('CST12',2,1)

    ##CST Data
    sources[-1].setLCA(0,'CST',[
            [10.55,0,0,0], #kg/MWh
            [156,0,0,0], #MJ/MWh, CED based on ecoinvent US plant average sem inf
            [4.17,0,0,0], #m^3/MWh CST
            [0,0,0,0], #km^2/MW
            [143,0,0,0]]) #$/MWh
    sources[-1].setLCA(1,'CST',[
            [1.29e6,0,0,0], #kg CO2-eq/MW, Heath
            [2.13e7,0,0,0], #MJ/MW
            [4.32e4,0,0,0], #m^3/MW
            [0.00926,0,0,0], # From NREL paper by Denholm and Margolis, at 70W/m^2 including spacing for roads
            [-5,4.7e6,2e5,0]]) #$/MW installed, PDE wind section (2000 costs adjusted by US CPI)

    #PV Data
    sources[7].setLCA(0,'PV',[
            [2,0,0,0], #kg/MWh
            [0,0,0,0], #MJ/MWh, CED based on ecoinvent US plant average sem inf
            [0.12,0,0,0], #m^3/MWh PV
            [0,0,0,0], #km^2/MW
            [129,0,0,0]]) #$/MWh, Investment costs from PNE for hydro at 10# discount. Itaipu contract in 2009 is 113, but operations costs via INL are $7/MWh
    sources[7].setLCA(1,'PV',[
            [-1,6.2227,1,0], #kg CO2-eq/MW, Based on NREL Harmonization & SimaPro
            [-5,3.23e7,1e6,0], #MJ/MW
            [-3,236,1971,0], #m^3/MW
            [0.014,0,0,0], # From NREL paper by Denholm and Margolis, at 70W/m^2 including spacing for roads
            [-5,4.8e6,2e5,0]]) #$/MW installed, EIA Overnight Cost

    sources[7].setLCABase(1,'CST6',[1.7e6,2.71e7,6.25e4])
    sources[7].setLCABase(1,'CST12',[2.12e6,3.29e7,8.18e4])
    sources[7].setLCABase(0,'Hybrid',[7.19],4)

    sources.append(dc.Source('Geothermal',1,refCF=.8,v=0))
    sources[-1].addFuelMode('Default')
    sources[-1].addOpsMode('Standard',[-4,.7,.8,.9],[.97,0,0,0])
    sources[-1].setLCA(0,0,[
            [-3,50,70,0],
            [141,0,0,0],
            [-3,1.136,1.817,0],
            [0,0,0,0],
            [54,0,0,0]]) #EIA Fixed O&M + Interest @7%,30yrs + variable O&M
    sources[-1].addConstMode('Standard',1,10)
    sources[-1].setLCABase(1,0,[4.141e6],4)
    sources[-1].setLCA(1,0,[-4,.84,1.16,1.69],3)

    #######################################
    # End of Electricity Built-ins
    #
    # Start of Transport Built-ins
    #TODO split transit into passenger and freght?
    #######################################
    # Units are Ops (/MJ delivered) Const (/MJ/hr built) Fuel (/MJ delivered)
    # Reference efficiency for all sources is 4.71 MJ/km (8.06 MJ/hr for a 15k
    # driving cycle, and 17.09 mi/gge), which is assigned a
    # capacity factor of 1. This excludes air and rail transport, which
    # account for ~10% of total energy.

    sources.append(dc.Source('Petroleum',2, v=2))
    sources[-1].addOpsMode('ICE',[-4,.542,.62,.72],[.97,0,0,0]) #Cfs for 31.5, 27.5 (CAFE), and 23.5 (CAFE trucks)
    sources[-1].setLCABase(0,0,[.074,0.0753,0,0,.015]) #gwp, CED from Notter ES&T
    sources[-1].addConstMode('ICE',1,5) #5 MJ/hr is energy requirement for 30mpg car at 10k miles/yr
    sources[-1].setLCABase(1,0,[1360,24600,0,0,3350]) #GWP & Energy data from NOtter, ES&T 2010
    sources[-1].setLCA(1,0,[-4,12.925,15.6,20.725],2) #Based on Volkswagon figures and 5 MJ/hr
    sources[-1].addOpsMode('Hyb',[-4,.33,.542,.62],[1.3,0,0,0]) #1.3 on efficiency to account for less gasoline usage
    sources[-1].addConstMode('Hyb',1,2.63)
    sources[-1].setLCABase(1,1,[5990,27200,0,0,3450]) #GWP & Energy data from NOtter, ES&T 2010
    sources[-1].addFuelMode('Conventional US')
    sources[-1].setLCABase(2,0,[.0205,.233,2.9e-5,2e-10]) #GREET, Scown
    sources[-1].setLCA(2,0,[-5,.016,.003,0],4)

    sources.append(dc.Source('Ethanol',2, v=3))
    sources[-1].addOpsMode('ICE',[-4,.542,.62,.72],[.97,0,0,0])
    sources[-1].setLCABase(0,0,[.073,0.075,0,0,.018]) #Greet cost from $2000/yr operating costs for car
    sources[-1].addConstMode('ICE',1,3.828)
    sources[-1].setLCABase(1,0,[1360,24600,0,0,3350])
    sources[-1].setLCA(1,0,[-4,12.925,15.6,20.725],2) #Based on Volkswagon figures and 5 MJ/hr
    sources[-1].addFuelMode('Corn')
    sources[-1].setLCA(2,0,[
            [-.0084,0,0,0],
            [-3,1.08,1.38,0],
            [-4,3.0e-4,4.1e-3,1.4e-2],
            [-5,1.25e-7,1.9e-8,0],
            [0.023,0,0,0]],0) #Greet

    sources.append(dc.Source('Biodiesel',2, v=3))
    sources[-1].addOpsMode('ICE',[-4,.542,.62,.72],[.97,0,0,0])
    sources[-1].setLCABase(0,0,[.072,0.075,0,0,.018])
    sources[-1].addConstMode('ICE',1,3.828)
    sources[-1].setLCABase(1,0,[1360,24600,0,0,3350])
    sources[-1].setLCA(1,0,[-4,12.925,15.6,20.725],2) #Based on Volkswagon figures and 5 MJ/hr
    sources[-1].addFuelMode('Soybean')
    sources[-1].setLCABase(2,0,[-.0054,.35],0) #Greet for GWP, energy from Mulder
    sources[-1].setLCA(2,0,[
            [-3,2.2e-2,3.4e-2,0], #Mulder 2009
            [-5,3.3e-7,1.2e-8,0],#Dominguez-Faus 2009
            [0.0178,0,0,0]],2) #Greet cost

    sources[-1].addFuelMode('Soybean-NoIrr')
    sources[-1].setLCA(2,1,[-3,2.2e-4,3.4e-4,0],2) #Mulder 2009/100

    sources[-1].addFuelMode('Algae')
    sources[-1].setLCA(2,2,[
            [-4,.156,.3,.56], #Sander + Mulder, system expansion
            [-3,.033,.036,0], #Guieysse 2013, WF = water demanded. Yes, it's ridiculously high
            [-5,2.5e-8,.2e-8,0]],1)

    sources.append(dc.Source('CNG',2))
    sources[-1].addOpsMode('ICE',[-4,.542,.62,.72],[.95,0,0,0])
    sources[-1].setLCABase(0,0,[.058,0.075,0,0,7.8e-3]) #Energy from GREET
    sources[-1].addConstMode('ICE',1,3.828)
    sources[-1].setLCABase(1,0,[1360,24600,0,0,3450]) #TODO assumed similar as gasoline for construction
    sources[-1].setLCA(1,0,[-4,12.925,15.6,20.725],2) #Based on Volkswagon figures and 5 MJ/hr
    sources[-1].addFuelMode('Conventional US')
    sources[-1].setLCABase(2,0,[.028,.177,0,2.67e-11,0])
    sources[-1].setLCA(2,0,[-4,2.3e-3,4.7e-3,7.6e-3],4) #cost is origin-blind
    sources[-1].addFuelMode('Marcellus')
    sources[-1].setLCABase(2,1,[.0137,.177,5.02e-6],0)

    sources.append(dc.Source('Hydrogen',2))
    sources[-1].addOpsMode('ICE',[-4,.542,.62,.72],[1.2,0,0,0])
    sources[-1].setLCABase(0,0,[.001],0)
    sources[-1].addConstMode('ICE',1,3.828)
    sources[-1].setLCABase(1,0,[1360,24600,0,0,3600]) # Costs from RAND study
    sources[-1].setLCA(1,0,[-4,12.925,15.6,20.725],2) #Based on Volkswagon figures and 5 MJ/hr
    sources[-1].addFuelMode('Conventional US')
    sources[-1].setLCABase(2,0,[.142,.820,7.27e-4,0,.0272]) #cost data in operation, GWP,CED from GREET

    sources.append(dc.Source('Electric',2))
    sources[-1].addOpsMode('Elec',[-4,.15,.17,.2],[1.2,0,0,0])
    sources[-1].setLCABase(0,0,[.001],0)
    sources[-1].addConstMode('ICE',1,3.828)
    sources[-1].setLCABase(1,0,[1360,24600,0,0,3600]) # Costs from RAND study
    sources[-1].setLCA(1,0,[-4,12.925,15.6,20.725],2) #Based on Volkswagon figures and 5 MJ/hr
    sources[-1].addFuelMode('Default')

    #######################################
    # End of Transport Built-ins
    #
    # Start of Heating Built-ins
    #######################################
    # Heating differentiated using capacity factor normalized by a reference unit
    # with 80% AFUE as a national average. Seasonal effects can be ignored - we're comparing capacity
    # based on a power level from an annual average. Unlike electricity and
    # transport, no simple metric for the service provided exists,, so we use
    # capacity factor to transform efficiency into th operational side
    #Units are: Ops (/MJ delivered) Const (/MJ/hr built) Fuel (/MJ produced)

    sources.append(dc.Source('Coal',3))
    sources[-1].addOpsMode('Combustion',[1,0,0,0],[.99,0,0,0])
    sources[-1].setLCABase(0,0,[.089,0,0,0,.0045]) #EIA coal costs
    sources[-1].addConstMode('Furnace',1,1) #arbitrarily low mincap
    sources[-1].setLCABase(1,0,[4.22,76,31,0,63.19]) #TODO Same as NG for now
    #TODO get coal furnace data
    sources[-1].addFuelMode('Surface')
    sources[-1].setLCABase(2,0,[0.0145,.024,8.16e-6,400e-13,.0039])
    sources[-1].addFuelMode('Underground')
    sources[-1].setLCABase(2,1,[.0087,.024,8.16e-6,67e-13,.0039])

    sources.append(dc.Source('Natural Gas',3, v=2))
    sources[-1].addOpsMode('Combustion',[-4,.84,.9,1],[.99,0,0,0]) #boiler or furnace, Ries
    sources[-1].setLCABase(0,0,[.0504,0,0,0,.0085])
    sources[-1].addConstMode('Furnace',1,1) #arbitrarily low mincap
    sources[-1].setLCABase(1,0,[4.22,76,31,0,63.19])
    sources[-1].addFuelMode('Conventional US')
    sources[-1].setLCABase(2,0,[.009,.065,0,2.67e-11,0])
    sources[-1].setLCA(2,0,[-5,5.72e-3,1.35e-3,0],4) #EIA Costs - 2010 average, stdev from 2006-2011
    sources[-1].addFuelMode('Marcellus')
    sources[-1].setLCABase(2,1,[0.0137,0.065,5.02e-6],0)

    sources.append(dc.Source('Oil',3))
    sources[-1].addOpsMode('Combustion',[-4,.84,.9,1],[.99,0,0,0])
    sources[-1].setLCABase(0,0,[.0706,0,0,0,.008])
    sources[-1].addConstMode('Furnace',1,1) #arbitrarily low mincap
    sources[-1].setLCABase(1,0,[4.22,76,31,0,63.19]) #Ecoinvent w/ CED and BEES
    sources[-1].addFuelMode('Conventional')
    sources[-1].setLCABase(2,0,[0.02,.208,.00027,2e-14,0])
    sources[-1].setLCA(2,0,[-5,.016,.003,0],4)

    sources.append(dc.Source('Biomass',3, v=3))
    sources[-1].addOpsMode('Combustion',[-4,1,1.1,1.6],[.97,0,0,0])
    sources[-1].setLCABase(0,0,[.0682,0,0,0])  #Modeled as identical to natral gas during combustion
    sources[-1].addConstMode('Furnace',1,1000)
    sources[-1].setLCABase(1,0,[4.22,76,31,0,190]) #USDA TechLine article
    sources[-1].addFuelMode('Woody Biomass')
    sources[-1].setLCABase(2,0,[-.0582,0,0,0,.0041]) #Modeled as identical to natural gas but negative, coproduct for land use
    sources[-1].setLCA(2,0,[-4,4.0e-11,5.9e-11,1.2e-10],3)

    #######################################
    # End of Heating Built-ins
    #
    # Start of Water Built-ins
    #######################################

    sources.append(dc.Source('Water-Surface',4, v=3))
    sources[-1].addOpsMode('Basic-Tmt',[-4,.85,.95,1],[.98,0,0,0])
    sources[-1].setLCABase(0,0,[0,1.22,0,0,.30]) #Cost from B. of Rec. WaTER model, assumed 1% loss
    sources[-1].addOpsMode('Adv-Tmt',[-4,.85,.95,1],[.98,0,0,0])
    sources[-1].setLCABase(0,1,[1.34,0,0,.39],1) #10% higher than baic
    sources[-1].addOpsMode('Non-Drinking',[-4,.5,.8,1],[.98,0,0,0])
    sources[-1].setLCABase(0,2,[.1684,0,0,.01],1) #EPRI for IW
    sources[-1].addConstMode('WW Plant',4,80)
    sources[-1].setLCA(1,0,[ #Assumed same as WWTP
                [-4,26915,38771,39892], #Simapro range for different classes
                [3.69e5,0,0,0],
                [391,0,0,0],
                [2.07e-6,0,0,0],
                [50,0,0,0]])
    sources[-1].addConstMode('Non-Drinking',2,10)
    sources[-1].setLCABase(1,0,[3880,3.69e4,39,0,5]) #Assumed 10% of full plant
    sources[-1].addFuelMode('Surface')
    sources[-1].setLCABase(2,0,[0,.116,0,2e-8,0])

    sources.append(copy.deepcopy(sources[-1]))
    sources[-1].name='Water-Ground'
    sources[-1].variable = 2
    sources[-1].Modes[2][0].name ='Ground'
    sources[-1].setLCABase(2,'Ground',[.3996],1)

    sources.append(copy.deepcopy(sources[-2]))   #skipping groundater to avoid default flag
    sources[-1].name='Water-Import'
    sources[-1].variable = 1
    sources[-1].Modes[2][0].name ='Import'
    sources[-1].setLCABase(2,'Import',[.05],2) #4000 kWh/af - CAP values
    sources[-1].setLCA(2,'Import',[-4,8.76,11.67,14.6],1)
    sources[-1].setLCA(2,'Import',[-3,.243,.405,0],4) #Shannon 2007 via Voinov

    sources.append(copy.deepcopy(sources[-1]))
    sources[-1].name='Water-Desal'
    sources[-1].Modes[2][0].name ='Desal'
    sources[-1].setLCABase(2,'Desal',[3.89],0)
    sources[-1].setLCA(2,'Desal',[
                [-4,10.4,12.,15.9],
                [-3,.1,.3,0], #arbitrary, interesting assumpion here - fits better for impaired but renewable sources in AZ
                [2e-7,0,0,0],
                [-4,.486,.583,.648]],1)

    #######################################
    # End of Water Built-ins
    #
    # Start of Wastewater Built-ins
    #######################################

    sources.append(dc.Source('WW-Trickling',5))
    sources[-1].addOpsMode('Plant',[-4,.75,.85,.95],[.98,0,0,0]) #boiler or furnace, Ries
    sources[-1].setLCA(0,0,[
                [-4,.95,1.05,1.11], #FIXME Source??
                [-4,0.64,0.81,1.72]]) #EPRI
    sources[-1].setLCABase(0,0,[1e-2,0,.11],2) #assumed 1% evaporation/solids loss
    sources[-1].addFuelMode('Default')
    sources[-1].addConstMode('WW Plant',4,80) #Mincap in m^3/hr (~.5MGD)
    sources[-1].setLCA(1,0,[
                [-4,26915,38771,39892],
                [3.69e5,0,0,0],
                [391,0,0,0],
                [2.07e-6,0,0,0],
                [50,0,0,0]])

    sources.append(copy.deepcopy(sources[-1]))
    sources[-1].name = 'WW-Aerated'
    sources[-1].setLCA(0,0,[-4,.979,1,2.13],1) ##epri
    sources[-1].setLCABase(0,0,[.5],4) #Cost data averaged from PA utilities
    sources[-2].setLCABase(1,0,[100],4)

    sources.append(copy.deepcopy(sources[-1]))
    sources[-1].name = 'WW-Adv-NoDeN'
    sources[-1].setLCA(0,0,[
                [-4,1.844,2.094,2.251],
                [-4,1.13,1.23,2.47]]) ##epri
    sources[-1].setLCABase(0,0,[1.14],4) #Cost data averaged from PA utilities
    sources[-1].setLCABase(1,0,[150],4)

    sources.append(copy.deepcopy(sources[-1]))
    sources[-1].name = 'WW-Adv-DeN'
    sources[-1].setLCA(0,0,[-4,1.48,1.52,2.81],1) ##epri
    sources[-1].setLCABase(0,0,[1.5],4) #Cost data averaged from PA utilities
    sources[-1].setLCABase(1,0,[210],4)

    return sources

def addSource(sources,name,classFlag,refEff=.97,v=1):
    sources.append(dc.Source(name,classFlag,refEff,v))
    sources[-1].addOpsMode('Default',[1,0,0,0],[1,0,0,0])
    sources[-1].addConstMode('Default',1,1)
    sources[-1].addFuelMode('Default')

if __name__=='__main__':
    sources = main()
    for n in sources:
        print n.name
        for stages in n.Modes:
            for m in stages:
                print m.name
        print ''


import support_functions as sf
import data_structures as ds
from model_socioeconomic import Socioeconomic
from model_ippu import IPPU
import pandas as pd
import numpy as np
import time

###########################
###                     ###
###     ENERGY MODEL    ###
###                     ###
###########################

class NonElectricEnergy:

    def __init__(self, attributes: ds.ModelAttributes):

        self.model_attributes = attributes
        self.required_dimensions = self.get_required_dimensions()
        self.required_subsectors, self.required_base_subsectors = self.get_required_subsectors()
        self.required_variables, self.output_variables = self.get_neenergy_input_output_fields()

        ##  set some model fields to connect to the attribute tables

        # Energy Fuel model variables
        self.modvar_enfu_ef_combustion_co2 = ":math:\\text{CO}_2 Combustion Emission Factor"
        self.modvar_enfu_ef_combustion_mobile_ch4 = ":math:\\text{CH}_4 Mobile Combustion Emission Factor"
        self.modvar_enfu_ef_combustion_mobile_n2o = ":math:\\text{N}_2\\text{O} Mobile Combustion Emission Factor"
        self.modvar_enfu_ef_combustion_stationary_ch4 = ":math:\\text{CH}_4 Stationary Combustion Emission Factor"
        self.modvar_enfu_ef_combustion_stationary_n2o = ":math:\\text{N}_2\\text{O} Stationary Combustion Emission Factor"
        self.modvar_enfu_volumetric_energy_density = "Volumetric Energy Density"

        # Industrial Energy model variables
        self.modvar_inen_demscalar = "Industrial Energy Demand Scalar"
        self.modvar_inen_emissions_ch4 = ":math:\\text{CH}_4 Emissions from Industrial Energy"
        self.modvar_inen_emissions_co2 = ":math:\\text{CO}_2 Emissions from Industrial Energy"
        self.modvar_inen_emissions_n2o = ":math:\\text{N}_2\\text{O} Emissions from Industrial Energy"
        self.modvar_inen_energy_demand_electricity = "Electrical Energy Demand from Industrial Energy"
        self.modvar_inen_energy_demand_electricity_agg = "Total Electrical Energy Demand from Industrial Energy"
        self.modvar_inen_energy_demand_total = "Energy Demand from Industrial Energy"
        self.modvar_inen_energy_demand_total_agg = "Total Energy Demand from Industrial Energy"
        self.modvar_inen_en_gdp_intensity_factor = "GDP Energy Intensity Factor"
        self.modvar_inen_en_prod_intensity_factor = "Production Energy Intesity Factor"
        self.modvar_inen_frac_en_coal = "Industrial Energy Fraction Coal"
        self.modvar_inen_frac_en_coke = "Industrial Energy Fraction Coke"
        self.modvar_inen_frac_en_diesel = "Industrial Energy Fraction Diesel"
        self.modvar_inen_frac_en_electricity = "Industrial Energy Fraction Electricity"
        self.modvar_inen_frac_en_furnace_gas = "Industrial Energy Fraction Furnace Gas"
        self.modvar_inen_frac_en_gasoline = "Industrial Energy Fraction Gasoline"
        self.modvar_inen_frac_en_hydrogen = "Industrial Energy Fraction Hydrogen"
        self.modvar_inen_frac_en_kerosene = "Industrial Energy Fraction Kerosene"
        self.modvar_inen_frac_en_natural_gas = "Industrial Energy Fraction Natural Gas"
        self.modvar_inen_frac_en_oil = "Industrial Energy Fraction Oil"
        self.modvar_inen_frac_en_pliqgas = "Industrial Energy Fraction Petroleum Liquid Gas"
        self.modvar_inen_frac_en_solar = "Industrial Energy Fraction Solar"
        self.modvar_inen_frac_en_solid_biomass = "Industrial Energy Fraction Solid Biomass"
        # fuel fractions to check summation over
        self.modvar_inen_list_fuel_fractions = [
            self.modvar_inen_frac_en_coal,
            self.modvar_inen_frac_en_coke,
            self.modvar_inen_frac_en_diesel,
            self.modvar_inen_frac_en_electricity,
            self.modvar_inen_frac_en_furnace_gas,
            self.modvar_inen_frac_en_gasoline,
            self.modvar_inen_frac_en_hydrogen,
            self.modvar_inen_frac_en_kerosene,
            self.modvar_inen_frac_en_natural_gas,
            self.modvar_inen_frac_en_oil,
            self.modvar_inen_frac_en_pliqgas,
            self.modvar_inen_frac_en_solar,
            self.modvar_inen_frac_en_solid_biomass
        ]

        # Transportation variables
        self.modvar_trns_average_vehicle_load_freight = "Average Freight Vehicle Load"
        self.modvar_trns_average_passenger_occupancy = "Average Passenger Vehicle Occupancy Rate"
        self.modvar_trns_electrical_efficiency = "Electrical Vehicle Efficiency"
        self.modvar_trns_ef_combustion_mobile_biofuels_ch4 = ":math:\\text{CH}_4 Biofuels Mobile Combustion Emission Factor"
        self.modvar_trns_ef_combustion_mobile_diesel_ch4 = ":math:\\text{CH}_4 Diesel Mobile Combustion Emission Factor"
        self.modvar_trns_ef_combustion_mobile_gasoline_ch4 = ":math:\\text{CH}_4 Gasoline Mobile Combustion Emission Factor"
        self.modvar_trns_ef_combustion_mobile_kerosene_ch4 = ":math:\\text{CH}_4 Kerosene Mobile Combustion Emission Factor"
        self.modvar_trns_ef_combustion_mobile_natural_gas_ch4 = ":math:\\text{CH}_4 Natural Gas Mobile Combustion Emission Factor"
        self.modvar_trns_ef_combustion_mobile_biofuels_n2o = ":math:\\text{N}_2\\text{O} Biofuels Mobile Combustion Emission Factor"
        self.modvar_trns_ef_combustion_mobile_diesel_n2o = ":math:\\text{N}_2\\text{O} Diesel Mobile Combustion Emission Factor"
        self.modvar_trns_ef_combustion_mobile_gasoline_n2o = ":math:\\text{N}_2\\text{O} Gasoline Mobile Combustion Emission Factor"
        self.modvar_trns_ef_combustion_mobile_kerosene_n2o = ":math:\\text{N}_2\\text{O} Kerosene Mobile Combustion Emission Factor"
        self.modvar_trns_ef_combustion_mobile_natural_gas_n2o = ":math:\\text{N}_2\\text{O} Natural Gas Mobile Combustion Emission Factor"
        self.modvar_trns_fuel_demand_biofuels_agg = "Total Fuel Demand Biofuels"
        self.modvar_trns_fuel_demand_diesel_agg = "Total Fuel Demand Diesel"
        self.modvar_trns_fuel_demand_gasoline_agg = "Total Fuel Demand Gasoline"
        self.modvar_trns_fuel_demand_hydrogen_agg = "Total Fuel Demand Hydrogen"
        self.modvar_trns_fuel_demand_kerosene_agg = "Total Fuel Demand Biofuels"
        self.modvar_trns_fuel_demand_natural_gas_agg = "Total Fuel Demand NaturalGas"
        self.modvar_trns_fuel_efficiency_biofuels = "Fuel Efficiency Biofuels"
        self.modvar_trns_fuel_efficiency_diesel = "Fuel Efficiency Diesel"
        self.modvar_trns_fuel_efficiency_gasoline = "Fuel Efficiency Gasoline"
        self.modvar_trns_fuel_efficiency_hydrogen = "Fuel Efficiency Hydrogen"
        self.modvar_trns_fuel_efficiency_kerosene = "Fuel Efficiency Kerosene"
        self.modvar_trns_fuel_efficiency_natural_gas = "Fuel Efficiency Natural Gas"
        self.modvar_trns_modeshare_freight = "Freight Transportation Mode Share"
        self.modvar_trns_modeshare_public_private = "Private and Public Transportation Mode Share"
        self.modvar_trns_modeshare_regional = "Regional Transportation Mode Share"
        self.modvar_trns_fuel_fraction_biofuels = "Transportation Mode Fuel Fraction Biofuels"
        self.modvar_trns_fuel_fraction_diesel = "Transportation Mode Fuel Fraction Diesel"
        self.modvar_trns_fuel_fraction_electricity = "Transportation Mode Fuel Fraction Electricity"
        self.modvar_trns_fuel_fraction_gasoline = "Transportation Mode Fuel Fraction Gasoline"
        self.modvar_trns_fuel_fraction_hydrogen = "Transportation Mode Fuel Fraction Hydrogen"
        self.modvar_trns_fuel_fraction_kerosene = "Transportation Mode Fuel Fraction Kerosene"
        self.modvar_trns_fuel_fraction_natural_gas = "Transportation Mode Fuel Fraction Natural Gas"
        self.modvar_tnrs_energy_demand_electricity = "Electrical Energy Demand from Transportation"
        self.modvar_tnrs_energy_demand_electricity_agg = "Total Electrical Energy Demand from Transportation"
        self.modvar_trns_emissions_ch4 = ":math:\\text{CH}_4 Emissions from Transportation"
        self.modvar_trns_emissions_co2 = ":math:\\text{CO}_2 Emissions from Transportation"
        self.modvar_trns_emissions_n2o = ":math:\\text{N}_2\\text{O} Emissions from Transportation"
        self.modvar_trns_vehicle_distance_traveled = "Total Vehicle Distance Traveled"

        # Transportation Demand variables
        self.modvar_trde_demand_scalar = "Transportation Demand Scalar"
        self.modvar_trde_elasticity_mtkm_to_gdp = "Elasticity of Megatonne-Kilometer Demand to GDP"
        self.modvar_trde_elasticity_pkm_to_gdp = "Elasticity of Passenger-Kilometer Demand per Capita to GDP per Capita"
        self.modvar_trde_demand_initial_mtkm = "Initial Megatonne-Kilometer Demand"
        self.modvar_trde_demand_initial_pkm_per_capita = "Initial per Capita Passenger-Kilometer Demand"
        self.modvar_trde_demand_mtkm = "Megatonne-Kilometer Demand"
        self.modvar_trde_demand_pkm = "Passenger-Kilometer Demand"

        # variables from other sectors
        self.modvar_ippu_qty_total_production = "Industrial Production"

        # add other model classes
        self.model_socioeconomic = Socioeconomic(self.model_attributes)
        self.model_ippu = IPPU(self.model_attributes)

        # optional integration variables (uses calls to other model classes)
        self.integration_variables = self.set_integrated_variables()

        ##  MISCELLANEOUS VARIABLES
        self.time_periods, self.n_time_periods = self.model_attributes.get_time_periods()
        self.enfu_fuel_electricity = self.get_electricity_fuel()

        # fuel variables dictionary for transportation
        self.dict_trns_fuel_categories_to_fuel_variables, self.dict_trns_fuel_categories_to_unassigned_fuel_variables = self.get_dict_trns_fuel_categories_to_fuel_variables()
        # some derivate lists of variables
        self.modvars_trns_list_fuel_fraction = self.model_attributes.get_vars_by_assigned_class_from_akaf(
            self.dict_trns_fuel_categories_to_fuel_variables,
            "fuel_fraction"
        )
        self.modvars_trns_list_fuel_efficiency = self.model_attributes.get_vars_by_assigned_class_from_akaf(
            self.dict_trns_fuel_categories_to_fuel_variables,
            "fuel_efficiency"
        )

    ##  FUNCTIONS FOR MODEL ATTRIBUTE DIMENSIONS

    def check_df_fields(self,
        df_neenergy_trajectories: pd.DataFrame,
        subsector: str = "All",
        var_type: str = "input",
        msg_prepend: str = None
    ):
        if subsector == "All":
            check_fields = self.required_variables
            msg_prepend = "Energy"
        else:
            self.model_attributes.check_subsector(subsector)
            if var_type == "input":
                check_fields, ignore_fields = self.model_attributes.get_input_output_fields(["Economy", "General", subsector])
            elif var_type == "output":
                ignore_fields, check_fields = self.model_attributes.get_input_output_fields([subsector])
            else:
                raise ValueError(f"Invalid var_type '{var_type}' in check_df_fields: valid types are 'input', 'output'")
            msg_prepend = msg_prepend if (msg_prepend is not None) else subsector
        sf.check_fields(df_neenergy_trajectories, check_fields, f"{msg_prepend} projection cannot proceed: fields ")

    def get_electricity_fuel(self):
        return self.model_attributes.get_categories_from_attribute_characteristic("Energy Fuels", {self.model_attributes.field_enfu_electricity_demand_category: 1})[0]

    def get_required_subsectors(self):
        ## TEMPORARY
        subsectors = ["Industrial Energy", "Energy Fuels", "Transportation", "Transportation Demand"]#self.model_attributes.get_setor_subsectors("Energy")
        subsectors_base = subsectors.copy()
        subsectors += ["Economy", "General"]
        return subsectors, subsectors_base

    def get_required_dimensions(self):
        ## TEMPORARY - derive from attributes later
        required_doa = [self.model_attributes.dim_time_period]
        return required_doa

    def get_neenergy_input_output_fields(self):
        required_doa = [self.model_attributes.dim_time_period]
        required_vars, output_vars = self.model_attributes.get_input_output_fields(self.required_subsectors)

        return required_vars + self.get_required_dimensions(), output_vars

    ##  function to set alternative sets of input variables; leave empty for now
    def get_neenergy_optional_switch_variables(self) -> dict:
        """
           get_neenergy_optional_switch_variables() defines dictionaries of lists of variables. Returns a nested dictionary specified in the class.

           Output Structure
           ----------------
           {
               "varset_1": {
                   "primary": [primary variables...],
                   "secondary": [primary variables...]
                },
               "varset_2": {
                   "primary": [primary variables...],
                   "secondary": [primary variables...]
                },
               ...
           }

           Notes
           -----
           - In each dictionary, variables from the "primary" key *or* variables from the "secondary" key must be defined. In general, "primary" variables are associated with integration. In the absence of these variables, secondary variables are generally calculated endogenously.
           - Each variable set represents a different approach
           - If all variables are defined in the input data frame, then the approach associated with "primary" variables is used.
        """

        return {}

    # variables required to integration
    def set_integrated_variables(self):
        # set the integration variables
        list_vars_required_for_integration = [
            self.modvar_ippu_qty_total_production
        ]

        # in Energy, update required variables
        for modvar in list_vars_required_for_integration:
            subsec = self.model_attributes.get_variable_subsector(modvar)
            new_vars = self.model_attributes.build_varlist(subsec, modvar)
            self.required_variables += new_vars

        # sot required variables and ensure no double counting
        self.required_variables = list(set(self.required_variables))
        self.required_variables.sort()

        return list_vars_required_for_integration

    ######################################
    #    SUBSECTOR SPECIFIC FUNCTIONS    #
    ######################################

    ##  transportation variables from fuel categories as specified by a matchstring
    def get_dict_trns_fuel_categories_to_fuel_variables(self):
        """
            use get_dict_trns_fuel_categories_to_fuel_variables to return a dictionary with fuel categories as keys based on the Transportation attribute table;
            {cat_fuel: {"fuel_efficiency": VARNAME_FUELEFFICIENCY, ...}}

            for each key, the dict includes variables associated with the fuel cat_fuel:

            - "fuel_efficiency"
            - "fuel_fraction"
            - "ef_ch4"
            - "ef_n2o"

        """

        dict_out = self.model_attributes.assign_keys_from_attribute_fields(
            "Transportation",
            "cat_fuel",
            {
                "Fuel Efficiency": "fuel_efficiency",
                "Fuel Fraction": "fuel_fraction",
                ":math:\\text{CH}_4": "ef_ch4",
                ":math:\\text{N}_2\\text{O}": "ef_n2o",
                "Total Fuel Demand": "total_fuel_demand"
            },
            "varreqs_partial",
            True
        )

        return dict_out

    ########################################
    ###                                  ###
    ###    PRIMARY PROJECTION METHODS    ###
    ###                                  ###
    ########################################

    ##  industrial energy model
    def project_industrial_energy(
        self,
        df_neenergy_trajectories: pd.DataFrame,
        vec_gdp: np.ndarray,
        dict_dims: dict = None,
        n_projection_time_periods: int = None,
        projection_time_periods: list = None
    ) -> pd.DataFrame:

        """
            project_industrial_energy can be called from other sectors to simplify calculation of industrial energy.

            Function Arguments
            ------------------
            df_neenergy_trajectories: pd.DataFrame of input variables

            vec_gdp: np.ndarray vector of gdp (requires len(vec_gdp) == len(df_neenergy_trajectories))

            dict_dims: dict of dimensions (returned from check_projection_input_df). Default is None.

            n_projection_time_periods: int giving number of time periods (returned from check_projection_input_df). Default is None.

            projection_time_periods: list of time periods (returned from check_projection_input_df). Default is None.

            Notes
            -----
            If any of dict_dims, n_projection_time_periods, or projection_time_periods are unspecified (expected if ran outside of Energy.project()), self.model_attributes.check_projection_input_df wil be run

        """

        # allows production to be run outside of the project method
        if type(None) in set([type(x) for x in [dict_dims, n_projection_time_periods, projection_time_periods]]):
            dict_dims, df_neenergy_trajectories, n_projection_time_periods, projection_time_periods = self.model_attributes.check_projection_input_df(df_neenergy_trajectories, True, True, True)

        ##  CATEGORY AND ATTRIBUTE INITIALIZATION
        pycat_enfu = self.model_attributes.get_subsector_attribute("Energy Fuels", "pycategory_primary")
        pycat_inen = self.model_attributes.get_subsector_attribute("Industrial Energy", "pycategory_primary")
        pycat_ippu = self.model_attributes.get_subsector_attribute("IPPU", "pycategory_primary")
        # attribute tables
        attr_enfu = self.model_attributes.dict_attributes[pycat_enfu]
        attr_inen = self.model_attributes.dict_attributes[pycat_inen]
        attr_ippu = self.model_attributes.dict_attributes[pycat_ippu]

        ##  OUTPUT INITIALIZATION

        df_out = [df_neenergy_trajectories[self.required_dimensions].copy()]

        ############################
        #    MODEL CALCULATIONS    #
        ############################

        # first, retrieve energy fractions and ensure they sum to 1
        dict_arrs_inen_frac_energy = self.model_attributes.get_multivariables_with_bounded_sum_by_category(
            df_neenergy_trajectories,
            self.modvar_inen_list_fuel_fractions,
            1,
            force_sum_equality = True,
            msg_append = "Energy fractions by category do not sum to 1. See definition of dict_arrs_inen_frac_energy."
        )

        ##  GET ENERGY INTENSITIES

        # get production-based emissions - start with production, energy demand
        arr_inen_prod = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_ippu_qty_total_production, True, "array_base", expand_to_all_cats = True)
        arr_inen_prod_energy_intensity = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_inen_en_prod_intensity_factor, True, "array_base", expand_to_all_cats = True)
        scalar_inen_prod_intensity_to_total_prod = self.model_attributes.get_mass_equivalent(
            self.model_attributes.get_variable_characteristic(self.modvar_inen_en_prod_intensity_factor, "$UNIT-MASS$"),
            self.model_attributes.get_variable_characteristic(self.modvar_ippu_qty_total_production, "$UNIT-MASS$")
        )
        # energy intensity due to production in terms of units self.modvar_ippu_qty_total_production
        arr_inen_energy_demand = arr_inen_prod*arr_inen_prod_energy_intensity*scalar_inen_prod_intensity_to_total_prod
        # gdp-based emissions - get intensity, multiply by gdp, and scale to match energy units of production
        arr_inen_gdp_energy_intensity = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_inen_en_gdp_intensity_factor, True, "array_base", expand_to_all_cats = True)
        scalar_inen_gdp_energy_to_prod_energy = self.model_attributes.get_energy_equivalent(
            self.model_attributes.get_variable_characteristic(self.modvar_inen_en_gdp_intensity_factor, "$UNIT-ENERGY$"),
            self.model_attributes.get_variable_characteristic(self.modvar_inen_en_prod_intensity_factor, "$UNIT-ENERGY$")
        )
        arr_inen_energy_demand += (arr_inen_gdp_energy_intensity.transpose() * vec_gdp).transpose()*scalar_inen_gdp_energy_to_prod_energy

        ##  GET EMISSION FACTORS

        # methane - scale to ensure energy units are the same
        arr_inen_ef_by_fuel_ch4 = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_enfu_ef_combustion_stationary_ch4, return_type = "array_units_corrected")
        arr_inen_ef_by_fuel_ch4 *= self.model_attributes.get_energy_equivalent(
            self.model_attributes.get_variable_characteristic(self.modvar_enfu_ef_combustion_stationary_ch4, "$UNIT-ENERGY$"),
            self.model_attributes.get_variable_characteristic(self.modvar_inen_en_prod_intensity_factor, "$UNIT-ENERGY$")
        )
        # carbon dioxide - scale to ensure energy units are the same
        arr_inen_ef_by_fuel_co2 = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_enfu_ef_combustion_co2, return_type = "array_units_corrected")
        arr_inen_ef_by_fuel_co2 *= self.model_attributes.get_energy_equivalent(
            self.model_attributes.get_variable_characteristic(self.modvar_enfu_ef_combustion_co2, "$UNIT-ENERGY$"),
            self.model_attributes.get_variable_characteristic(self.modvar_inen_en_prod_intensity_factor, "$UNIT-ENERGY$")
        )
        # nitrous oxide - scale to ensure energy units are the same
        arr_inen_ef_by_fuel_n2o = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_enfu_ef_combustion_stationary_n2o, return_type = "array_units_corrected")
        arr_inen_ef_by_fuel_n2o *= self.model_attributes.get_energy_equivalent(
            self.model_attributes.get_variable_characteristic(self.modvar_enfu_ef_combustion_stationary_n2o, "$UNIT-ENERGY$"),
            self.model_attributes.get_variable_characteristic(self.modvar_inen_en_prod_intensity_factor, "$UNIT-ENERGY$")
        )

        ##  CALCULATE EMISSIONS AND ELECTRICITY DEMAND

        # initialize electrical demand to pass and output emission arrays
        arr_inen_demand_electricity = 0.0
        arr_inen_demand_electricity_total = 0.0
        arr_inen_demand_total = 0.0
        arr_inen_demand_total_total = 0.0
        arr_inen_emissions_ch4 = 0.0
        arr_inen_emissions_co2 = 0.0
        arr_inen_emissions_n2o = 0.0
        # loop over fuels to
        for var_ener_frac in self.modvar_inen_list_fuel_fractions:
            # retrive the fuel category
            cat_fuel = ds.clean_schema(self.model_attributes.get_variable_attribute(var_ener_frac, pycat_enfu))
            # get the demand for the current fuel
            arr_inen_endem_cur_fuel = dict_arrs_inen_frac_energy[var_ener_frac].copy()
            arr_inen_endem_cur_fuel *= arr_inen_energy_demand
            # get the category value index and
            index_cat_fuel = attr_enfu.get_key_value_index(cat_fuel)
            arr_inen_emissions_ch4 += arr_inen_endem_cur_fuel.transpose()*arr_inen_ef_by_fuel_ch4[:, index_cat_fuel]
            arr_inen_emissions_co2 += arr_inen_endem_cur_fuel.transpose()*arr_inen_ef_by_fuel_co2[:, index_cat_fuel]
            arr_inen_emissions_n2o += arr_inen_endem_cur_fuel.transpose()*arr_inen_ef_by_fuel_n2o[:, index_cat_fuel]
            # add electricity demand and total energy demand
            arr_inen_demand_electricity += arr_inen_endem_cur_fuel if (cat_fuel == self.enfu_fuel_electricity) else 0.0
            arr_inen_demand_electricity_total += arr_inen_endem_cur_fuel.sum(axis = 1) if (cat_fuel == self.enfu_fuel_electricity) else 0.0
            arr_inen_demand_total += arr_inen_endem_cur_fuel
            arr_inen_demand_total_total += arr_inen_endem_cur_fuel.sum(axis = 1)

        # transpose outputs
        arr_inen_emissions_ch4 = arr_inen_emissions_ch4.transpose()
        arr_inen_emissions_co2 = arr_inen_emissions_co2.transpose()
        arr_inen_emissions_n2o = arr_inen_emissions_n2o.transpose()
        # set energy data frames
        scalar_energy = self.model_attributes.get_scalar(self.modvar_inen_en_prod_intensity_factor, "energy")

        ##  BUILD OUTPUT DFs

        df_out += [
            self.model_attributes.array_to_df(arr_inen_emissions_ch4, self.modvar_inen_emissions_ch4, False, True),
            self.model_attributes.array_to_df(arr_inen_emissions_co2, self.modvar_inen_emissions_co2, False, True),
            self.model_attributes.array_to_df(arr_inen_emissions_n2o, self.modvar_inen_emissions_n2o, False, True),
            self.model_attributes.array_to_df(arr_inen_demand_electricity*scalar_energy, self.modvar_inen_energy_demand_electricity, False, True),
            self.model_attributes.array_to_df(arr_inen_demand_electricity_total*scalar_energy, self.modvar_inen_energy_demand_electricity_agg, False),
            self.model_attributes.array_to_df(arr_inen_demand_total*scalar_energy, self.modvar_inen_energy_demand_total, False, True),
            self.model_attributes.array_to_df(arr_inen_demand_total_total*scalar_energy, self.modvar_inen_energy_demand_total_agg, False)
        ]

        # concatenate and add subsector emission totals
        df_out = sf.merge_output_df_list(df_out, self.model_attributes, "concatenate")
        self.model_attributes.add_subsector_emissions_aggregates(df_out, ["Industrial Energy"], False)

        return df_out

    ##  transportation emissions
    def project_transportation(self,
        df_neenergy_trajectories: pd.DataFrame,
        vec_pop: np.ndarray,
        vec_rates_gdp: np.ndarray,
        vec_rates_gdp_per_capita: np.ndarray,
        dict_dims: dict = None,
        n_projection_time_periods: int = None,
        projection_time_periods: list = None
    ) -> pd.DataFrame:

        """
            project_transportation can be called from other sectors to simplify calculation of transportation emissions and associated metrics. Requires NonElectricEnergy.project_transportation_demand() and all variables from the transportation demand sector

            Function Arguments
            ------------------
            df_neenergy_trajectories: pd.DataFrame of input variables

            vec_pop: np.ndarray vector of population (requires len(vec_rates_gdp) == len(df_neenergy_trajectories))

            vec_rates_gdp: np.ndarray vector of gdp growth rates (v_i = growth rate from t_i to t_{i + 1}) (requires len(vec_rates_gdp) == len(df_neenergy_trajectories) - 1)

            vec_rates_gdp_per_capita: np.ndarray vector of gdp per capita growth rates (v_i = growth rate from t_i to t_{i + 1}) (requires len(vec_rates_gdp_per_capita) == len(df_neenergy_trajectories) - 1)

            dict_dims: dict of dimensions (returned from check_projection_input_df). Default is None.

            n_projection_time_periods: int giving number of time periods (returned from check_projection_input_df). Default is None.

            projection_time_periods: list of time periods (returned from check_projection_input_df). Default is None.

            Notes
            -----
            If any of dict_dims, n_projection_time_periods, or projection_time_periods are unspecified (expected if ran outside of Energy.project()), self.model_attributes.check_projection_input_df wil be run

        """

        # allows production to be run outside of the project method
        if type(None) in set([type(x) for x in [dict_dims, n_projection_time_periods, projection_time_periods]]):
            dict_dims, df_neenergy_trajectories, n_projection_time_periods, projection_time_periods = self.model_attributes.check_projection_input_df(df_neenergy_trajectories, True, True, True)

        # check fields - transportation demand; if not present, add to the dataframe
        self.check_df_fields(df_neenergy_trajectories, "Transportation")
        try:
            self.check_df_fields(df_neenergy_trajectories, "Transportation Demand", "output", "Transportation")
        except:
            df_transport_demand = self.project_transportation_demand(
                df_neenergy_trajectories,
                vec_pop,
                vec_rates_gdp,
                vec_rates_gdp_per_capita,
                dict_dims,
                n_projection_time_periods,
                projection_time_periods
            )
            df_neenergy_trajectories = sf.merge_output_df_list([df_neenergy_trajectories, df_transport_demand], self.model_attributes, "concatenate")

        ##  CATEGORY AND ATTRIBUTE INITIALIZATION
        pycat_enfu = self.model_attributes.get_subsector_attribute("Energy Fuels", "pycategory_primary")
        pycat_trde = self.model_attributes.get_subsector_attribute("Transportation Demand", "pycategory_primary")
        pycat_trns = self.model_attributes.get_subsector_attribute("Transportation", "pycategory_primary")
        # attribute tables
        attr_enfu = self.model_attributes.dict_attributes[pycat_enfu]
        attr_trde = self.model_attributes.dict_attributes[pycat_trde]
        attr_trns = self.model_attributes.dict_attributes[pycat_trns]

        ##  OUTPUT INITIALIZATION

        df_out = [df_neenergy_trajectories[self.required_dimensions].copy()]

        ############################
        #    MODEL CALCULATIONS    #
        ############################

        ##  START WITH DEMANDS

        # start with demands and map categories in attribute to associated variable
        dict_trns_vars_to_trde_cats = self.model_attributes.get_ordered_category_attribute("Transportation", "cat_transportation_demand", "key_varreqs_partial", True, dict, True)
        dict_trns_vars_to_trde_cats = sf.reverse_dict(dict_trns_vars_to_trde_cats)
        array_trns_total_vehicle_demand = 0.0
        # get occupancy and freight occupancies
        array_trns_avg_load_freight = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_trns_average_vehicle_load_freight, return_type = "array_base", expand_to_all_cats = True)
        array_trns_occ_rate_passenger = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_trns_average_passenger_occupancy, return_type = "array_base", expand_to_all_cats = True)
        # convert average load to same units as demand
        array_trns_avg_load_freight *= self.model_attributes.get_variable_unit_conversion_factor(
            self.modvar_trns_average_vehicle_load_freight,
            self.modvar_trde_demand_mtkm,
            "mass"
        )
        # convert freight vehicle demand to same length units as passenger
        scalar_tnrs_length_demfrieght_to_dempass = self.model_attributes.get_variable_unit_conversion_factor(
            self.modvar_trde_demand_mtkm,
            self.modvar_trde_demand_pkm,
            "length"
        )

        # loop over the demand categories to get transportation demand
        for category in dict_trns_vars_to_trde_cats.keys():
            # get key index, model variable, and the current demand
            index_key = self.model_attributes.get_attribute_table("Transportation Demand").get_key_value_index(category)
            modvar = self.model_attributes.get_variable_from_category("Transportation Demand", category, "partial")
            vec_trde_dem_cur = self.model_attributes.get_standard_variables(df_neenergy_trajectories, modvar, return_type = "array_base", expand_to_all_cats = True)[:, index_key]
            # retrieve the demand mix, convert to total activity-demand by category, then divide by freight/occ_rate
            array_trde_dem_cur_by_cat = self.model_attributes.get_standard_variables(
                df_neenergy_trajectories,
                dict_trns_vars_to_trde_cats[category],
                return_type = "array_base",
                expand_to_all_cats = True,
                var_bounds = (0, 1),
                force_boundary_restriction = True
            )
            # ru
            array_trde_dem_cur_by_cat = (array_trde_dem_cur_by_cat.transpose()*vec_trde_dem_cur).transpose()
            """
            freight and passenger should be mutually exclusive categories
            - e.g., if the iterating variable category == "freight", then array_trde_dem_cur_by_cat*array_trns_occ_rate_passenger should be 0
            - if category != "freight", then array_trde_dem_cur_by_cat*array_trns_avg_load_freight should be 0)

            - demand length units should be in terms of 'modvar_trns_average_passenger_occupancy' (see scalar multiplication)
            """
            array_trde_vehicle_dem_cur_by_cat = np.nan_to_num(array_trde_dem_cur_by_cat/array_trns_avg_load_freight, 0.0, neginf = 0.0, posinf = 0.0)*scalar_tnrs_length_demfrieght_to_dempass
            array_trde_vehicle_dem_cur_by_cat += np.nan_to_num(array_trde_dem_cur_by_cat/array_trns_occ_rate_passenger, 0.0, neginf = 0.0, posinf = 0.0)
            # update total vehicle-km demand
            array_trns_total_vehicle_demand += array_trde_vehicle_dem_cur_by_cat

        # add the vehicle distance to output using the units modvar_trde_demand_pkm
        scalar_trns_total_vehicle_demand = self.model_attributes.get_scalar(self.modvar_trde_demand_pkm, "length")
        df_out.append(
            self.model_attributes.array_to_df(array_trns_total_vehicle_demand*scalar_trns_total_vehicle_demand, self.modvar_trns_vehicle_distance_traveled, False, True),
        )

        ##  LOOP OVER FUELS

        # first, retrieve fuel-mix fractions and ensure they sum to 1
        dict_arrs_trns_frac_fuel = self.model_attributes.get_multivariables_with_bounded_sum_by_category(
            df_neenergy_trajectories,
            self.modvars_trns_list_fuel_fraction,
            1,
            force_sum_equality = False,
            msg_append = "Energy fractions by category do not sum to 1. See definition of dict_arrs_trns_frac_fuel."
        )
        # get carbon dioxide combustion factors (corrected to output units)
        arr_trns_ef_by_fuel_co2 = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_enfu_ef_combustion_co2, return_type = "array_units_corrected", expand_to_all_cats = True)
        arr_trns_energy_density_fuel = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_enfu_volumetric_energy_density, return_type = "array_units_corrected", expand_to_all_cats = True)

        # initialize electrical demand to pass and output emission arrays
        arr_trns_demand_electricity = 0.0
        arr_trns_demand_electricity_total = 0.0
        arr_trns_emissions_ch4 = 0.0
        arr_trns_emissions_co2 = 0.0
        arr_trns_emissions_n2o = 0.0

        # loop over fuels to calculate emissions and demand associated with each fuel
        fuels_loop = sorted(list(self.dict_trns_fuel_categories_to_fuel_variables.keys()))
        for cat_fuel in fuels_loop:

            # initialize the fuel demand
            vec_fuel_demand = 0

            # set some model variables
            dict_tfc_to_fv_cur = self.dict_trns_fuel_categories_to_fuel_variables.get(cat_fuel)
            modvar_trns_ef_ch4_cur = dict_tfc_to_fv_cur.get("ef_ch4")
            modvar_trns_ef_n2o_cur = dict_tfc_to_fv_cur.get("ef_n2o")
            modvar_trns_fuel_efficiency_cur = dict_tfc_to_fv_cur.get("fuel_efficiency")
            modvar_trns_fuel_fraction_cur = dict_tfc_to_fv_cur.get("fuel_fraction")
            modvar_trns_total_volumetric_fuel_dem_cur = dict_tfc_to_fv_cur.get("total_fuel_demand")

            # set some scalars for use in the calculations
            scalar_trns_fuel_efficiency_to_demand = self.model_attributes.get_variable_unit_conversion_factor(
                modvar_trns_fuel_efficiency_cur,
                self.modvar_trde_demand_pkm,
                "length"
            )

            # get the index and vector of co2 emission factors
            ind_enfu_cur = attr_enfu.get_key_value_index(cat_fuel)
            vec_trns_ef_by_fuel_co2_cur = arr_trns_ef_by_fuel_co2[:, ind_enfu_cur]
            vec_trns_volumetric_enerdensity_by_fuel = arr_trns_energy_density_fuel[:, ind_enfu_cur]
            # get arrays
            arr_trns_fuel_fraction_cur = dict_arrs_trns_frac_fuel.get(modvar_trns_fuel_fraction_cur)
            arr_trns_ef_ch4_cur = self.model_attributes.get_standard_variables(df_neenergy_trajectories, modvar_trns_ef_ch4_cur, return_type = "array_units_corrected", expand_to_all_cats = True) if (modvar_trns_ef_ch4_cur is not None) else 0
            arr_trns_ef_n2o_cur = self.model_attributes.get_standard_variables(df_neenergy_trajectories, modvar_trns_ef_n2o_cur, return_type = "array_units_corrected", expand_to_all_cats = True) if (modvar_trns_ef_n2o_cur is not None) else 0
            arr_trns_fuel_efficiency_cur = self.model_attributes.get_standard_variables(df_neenergy_trajectories, modvar_trns_fuel_efficiency_cur, return_type = "array_base", expand_to_all_cats = True)

            # current demand associate with the fuel (in terms of modvar_trde_demand_pkm)
            arr_trns_vehdem_cur_fuel = array_trns_total_vehicle_demand*arr_trns_fuel_fraction_cur

            if (arr_trns_fuel_efficiency_cur is not None):

                # get demand for fuel in terms of modvar_trns_fuel_efficiency_cur, then get scalars to conert to emission factor fuel volume units
                arr_trns_fueldem_cur_fuel = np.nan_to_num(arr_trns_vehdem_cur_fuel/arr_trns_fuel_efficiency_cur, neginf = 0.0, posinf = 0.0)
                arr_trns_energydem_cur_fuel = (arr_trns_fueldem_cur_fuel.transpose()*vec_trns_volumetric_enerdensity_by_fuel).transpose()
                arr_trns_energydem_cur_fuel *= self.model_attributes.get_variable_unit_conversion_factor(
                    modvar_trns_fuel_efficiency_cur,
                    self.modvar_enfu_volumetric_energy_density,
                    "volume"
                )
                # add total fuel to output variable
                vec_fuel_demand += np.sum(arr_trns_fueldem_cur_fuel, axis = 1)

                ##  CH4 EMISSIONS

                # get scalar to prepare fuel energies for the emission factor
                scalar_fuel_energy_to_ef_ch4 = self.model_attributes.get_variable_unit_conversion_factor(
                    self.modvar_enfu_volumetric_energy_density,
                    modvar_trns_ef_ch4_cur,
                    "energy"
                ) if (modvar_trns_ef_ch4_cur is not None) else 0
                arr_trns_fuel_energydem_cur_fuel_ch4 = arr_trns_energydem_cur_fuel*scalar_fuel_energy_to_ef_ch4
                arr_emissions_ch4_cur_fuel = arr_trns_ef_ch4_cur*arr_trns_fuel_energydem_cur_fuel_ch4
                arr_trns_emissions_ch4 += arr_emissions_ch4_cur_fuel

                ##  CO2 EMISSIONS

                # get scalar to prepare fuel energies for the emission factor
                scalar_fuel_energy_to_ef_co2 = self.model_attributes.get_variable_unit_conversion_factor(
                    self.modvar_enfu_volumetric_energy_density,
                    self.modvar_enfu_ef_combustion_co2,
                    "energy"
                )
                arr_trns_fuel_energydem_cur_fuel_co2 = arr_trns_energydem_cur_fuel*scalar_fuel_energy_to_ef_co2
                arr_emissions_co2_cur_fuel = (arr_trns_fuel_energydem_cur_fuel_co2.transpose()*vec_trns_ef_by_fuel_co2_cur).transpose()
                arr_trns_emissions_co2 += arr_emissions_co2_cur_fuel

                ##  N2O EMISSIONS

                # n2o scalar
                scalar_fuel_energy_to_ef_n2o = self.model_attributes.get_variable_unit_conversion_factor(
                    self.modvar_enfu_volumetric_energy_density,
                    modvar_trns_ef_n2o_cur,
                    "energy"
                ) if (modvar_trns_ef_n2o_cur is not None) else 0
                arr_trns_fuel_energydem_cur_fuel_n2o = arr_trns_energydem_cur_fuel*scalar_fuel_energy_to_ef_n2o
                arr_emissions_n2o_cur_fuel = arr_trns_ef_n2o_cur*arr_trns_fuel_energydem_cur_fuel_n2o
                arr_trns_emissions_n2o += arr_emissions_n2o_cur_fuel

            elif cat_fuel == self.enfu_fuel_electricity:

                # get scalar for energy
                scalar_electric_eff_to_distance_equiv = self.model_attributes.get_variable_unit_conversion_factor(
                    self.modvar_trns_electrical_efficiency,
                    self.modvar_trde_demand_pkm,
                    "length"
                )
                # get demand for fuel in terms of modvar_trns_fuel_efficiency_cur, then get scalars to conert to emission factor fuel volume units
                arr_trns_elect_efficiency_cur = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_trns_electrical_efficiency, return_type = "array_base", expand_to_all_cats = True)
                arr_trns_elect_efficiency_cur *= scalar_electric_eff_to_distance_equiv
                arr_trns_energydem_elec = arr_trns_vehdem_cur_fuel/arr_trns_elect_efficiency_cur
                # write in terms of output units
                arr_trns_energydem_elec *= self.model_attributes.get_scalar(self.modvar_trns_electrical_efficiency, "energy")
                arr_trns_energydem_elec = np.nan_to_num(arr_trns_energydem_elec, posinf = 0, neginf = 0)

            # add total fuel volumetric fuel demand
            if modvar_trns_fuel_efficiency_cur is not None:
                vec_fuel_demand *= self.model_attributes.get_scalar(modvar_trns_fuel_efficiency_cur, "volume")
                df_out.append(
                    self.model_attributes.array_to_df(vec_fuel_demand, modvar_trns_total_volumetric_fuel_dem_cur, False, False),
                )

        # add aggregate emissions
        df_out += [
            self.model_attributes.array_to_df(arr_trns_emissions_ch4, self.modvar_trns_emissions_ch4, False),
            self.model_attributes.array_to_df(arr_trns_emissions_co2, self.modvar_trns_emissions_co2, False),
            self.model_attributes.array_to_df(arr_trns_emissions_n2o, self.modvar_trns_emissions_n2o, False),
            self.model_attributes.array_to_df(arr_trns_energydem_elec, self.modvar_tnrs_energy_demand_electricity, False, True),
            self.model_attributes.array_to_df(np.sum(arr_trns_energydem_elec, axis = 1), self.modvar_tnrs_energy_demand_electricity_agg, False)
        ]

        # concatenate and add subsector emission totals
        df_out = sf.merge_output_df_list(df_out, self.model_attributes, "concatenate")
        self.model_attributes.add_subsector_emissions_aggregates(df_out, ["Transportation"], False)

        return df_out

    ##  transportation demands
    def project_transportation_demand(self,
        df_neenergy_trajectories: pd.DataFrame,
        vec_pop: np.ndarray,
        vec_rates_gdp: np.ndarray,
        vec_rates_gdp_per_capita: np.ndarray,
        dict_dims: dict = None,
        n_projection_time_periods: int = None,
        projection_time_periods: list = None
    ) -> pd.DataFrame:

        """
            project_transportation_demand can be called from other sectors to simplify calculation of transportation demands and associated metrics.

            Function Arguments
            ------------------
            df_neenergy_trajectories: pd.DataFrame of input variables

            vec_pop: np.ndarray vector of population (requires len(vec_rates_gdp) == len(df_neenergy_trajectories))

            vec_rates_gdp: np.ndarray vector of gdp growth rates (v_i = growth rate from t_i to t_{i + 1}) (requires len(vec_rates_gdp) == len(df_neenergy_trajectories) - 1)

            vec_rates_gdp_per_capita: np.ndarray vector of gdp per capita growth rates (v_i = growth rate from t_i to t_{i + 1}) (requires len(vec_rates_gdp_per_capita) == len(df_neenergy_trajectories) - 1)

            dict_dims: dict of dimensions (returned from check_projection_input_df). Default is None.

            n_projection_time_periods: int giving number of time periods (returned from check_projection_input_df). Default is None.

            projection_time_periods: list of time periods (returned from check_projection_input_df). Default is None.

            Notes
            -----
            If any of dict_dims, n_projection_time_periods, or projection_time_periods are unspecified (expected if ran outside of Energy.project()), self.model_attributes.check_projection_input_df wil be run

        """

        # allows production to be run outside of the project method
        if type(None) in set([type(x) for x in [dict_dims, n_projection_time_periods, projection_time_periods]]):
            dict_dims, df_neenergy_trajectories, n_projection_time_periods, projection_time_periods = self.model_attributes.check_projection_input_df(df_neenergy_trajectories, True, True, True)

        ##  CATEGORY AND ATTRIBUTE INITIALIZATION
        pycat_enfu = self.model_attributes.get_subsector_attribute("Energy Fuels", "pycategory_primary")
        pycat_trde = self.model_attributes.get_subsector_attribute("Transportation Demand", "pycategory_primary")
        pycat_trns = self.model_attributes.get_subsector_attribute("Transportation", "pycategory_primary")
        # attribute tables
        attr_enfu = self.model_attributes.dict_attributes[pycat_enfu]
        attr_trde = self.model_attributes.dict_attributes[pycat_trde]
        attr_trns = self.model_attributes.dict_attributes[pycat_trns]

        ##  OUTPUT INITIALIZATION

        df_out = [df_neenergy_trajectories[self.required_dimensions].copy()]

        ############################
        #    MODEL CALCULATIONS    #
        ############################

        # get the demand scalar
        array_trde_demscalar = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_trde_demand_scalar, return_type = "array_base", expand_to_all_cats = True, var_bounds = (0, np.inf))
        # start with freight/megaton km demands
        array_trde_dem_init_freight = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_trde_demand_initial_mtkm, return_type = "array_base", expand_to_all_cats = True)
        array_trde_elast_freight_demand_to_gdp = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_trde_elasticity_mtkm_to_gdp, return_type = "array_base", expand_to_all_cats = True)
        array_trde_growth_freight_dem_by_cat = sf.project_growth_scalar_from_elasticity(vec_rates_gdp, array_trde_elast_freight_demand_to_gdp, False, "standard")
        # multiply and add to the output
        array_trde_freight_dem_by_cat = array_trde_dem_init_freight[0]*array_trde_growth_freight_dem_by_cat
        array_trde_freight_dem_by_cat *= array_trde_demscalar
        df_out.append(
            self.model_attributes.array_to_df(array_trde_freight_dem_by_cat, self.modvar_trde_demand_mtkm, False, True)
        )

        # deal with person-km
        array_trde_dem_init_passenger = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_trde_demand_initial_pkm_per_capita, return_type = "array_base", expand_to_all_cats = True)
        array_trde_elast_passenger_demand_to_gdppc = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.modvar_trde_elasticity_pkm_to_gdp, return_type = "array_base", expand_to_all_cats = True)
        array_trde_growth_passenger_dem_by_cat = sf.project_growth_scalar_from_elasticity(vec_rates_gdp_per_capita, array_trde_elast_passenger_demand_to_gdppc, False, "standard")
        # project the growth in per capita, multiply by population, then add it to the output
        array_trde_passenger_dem_by_cat = array_trde_dem_init_passenger[0]*array_trde_growth_passenger_dem_by_cat
        array_trde_passenger_dem_by_cat = (array_trde_passenger_dem_by_cat.transpose()*vec_pop).transpose()
        array_trde_passenger_dem_by_cat *= array_trde_demscalar
        df_out.append(
            self.model_attributes.array_to_df(array_trde_passenger_dem_by_cat, self.modvar_trde_demand_pkm, False, True)
        )

        # build output dataframe
        df_out = sf.merge_output_df_list(df_out, self.model_attributes, "concatenate")

        return df_out

    ##  other energy: stationary emissions and carbon capture and sequestration
    def project_oesc():

        return 0

    ##  primary method
    def project(self, df_neenergy_trajectories):

        """
            The Energy.project() method takes a data frame of input variables (ordered by time series) and returns a data frame of output variables (model projections for energy--including industrial energy, transportation, stationary emissions, carbon capture and sequestration, and electricity) the same order.

            Function Arguments
            ------------------
            df_neenergy_trajectories: pd.DataFrame with all required input fields as columns. The model will not run if any required variables are missing, but errors will detail which fields are missing.

            Notes
            -----
            - The .project() method is designed to be parallelized or called from command line via __main__ in run_sector_models.py.
            - df_neenergy_trajectories should have all input fields required (see Energy.required_variables for a list of variables to be defined)
            - the df_neenergy_trajectories.project method will run on valid time periods from 1 .. k, where k <= n (n is the number of time periods). By default, it drops invalid time periods. If there are missing time_periods between the first and maximum, data are interpolated.
        """

        ##  CHECKS

        # make sure socioeconomic variables are added and
        df_neenergy_trajectories, df_se_internal_shared_variables = self.model_socioeconomic.project(df_neenergy_trajectories)
        # check that all required fields are contained—assume that it is ordered by time period
        self.check_df_fields(df_neenergy_trajectories)
        dict_dims, df_neenergy_trajectories, n_projection_time_periods, projection_time_periods = self.model_attributes.check_projection_input_df(df_neenergy_trajectories, True, True, True)

        ##  CATEGORY AND ATTRIBUTE INITIALIZATION
        pycat_fuel = self.model_attributes.get_subsector_attribute("Energy Fuels", "pycategory_primary")
        pycat_gnrl = self.model_attributes.get_subsector_attribute("General", "pycategory_primary")
        pycat_inen = self.model_attributes.get_subsector_attribute("Industrial Energy", "pycategory_primary")
        pycat_ippu = self.model_attributes.get_subsector_attribute("IPPU", "pycategory_primary")
        pycat_oesc = self.model_attributes.get_subsector_attribute("Other Energy: Stationary Emissions and Carbon Capture and Sequestration", "pycategory_primary")
        pycat_trns = self.model_attributes.get_subsector_attribute("Transportation", "pycategory_primary")
        # attribute tables
        attr_fuel = self.model_attributes.dict_attributes[pycat_fuel]
        attr_gnrl = self.model_attributes.dict_attributes[pycat_gnrl]
        attr_inen = self.model_attributes.dict_attributes[pycat_inen]
        attr_ippu = self.model_attributes.dict_attributes[pycat_ippu]
        attr_oesc = self.model_attributes.dict_attributes[pycat_oesc]
        attr_trns = self.model_attributes.dict_attributes[pycat_trns]

        ##  ECON/GNRL VECTOR AND ARRAY INITIALIZATION

        # get some vectors from the se model
        vec_gdp = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.model_socioeconomic.modvar_econ_gdp, False, return_type = "array_base")
        vec_pop = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.model_socioeconomic.modvar_gnrl_pop_total, False, return_type = "array_base")
        array_pop = self.model_attributes.get_standard_variables(df_neenergy_trajectories, self.model_socioeconomic.modvar_gnrl_subpop, False, return_type = "array_base")
        vec_gdp_per_capita = np.array(df_se_internal_shared_variables["vec_gdp_per_capita"])
        vec_rates_gdp = np.array(df_se_internal_shared_variables["vec_rates_gdp"].dropna())
        vec_rates_gdp_per_capita = np.array(df_se_internal_shared_variables["vec_rates_gdp_per_capita"].dropna())

        ##  OUTPUT INITIALIZATION

        df_out = [df_neenergy_trajectories[self.required_dimensions].copy()]

        #########################################
        #    MODEL CALCULATIONS BY SUBSECTOR    #
        #########################################

        # add industrial energy, transportation, and OESC
        df_out.append(self.project_industrial_energy(df_neenergy_trajectories, vec_gdp, dict_dims, n_projection_time_periods, projection_time_periods))
        df_out.append(self.project_transportation(df_neenergy_trajectories, vec_pop, vec_rates_gdp, vec_rates_gdp_per_capita, dict_dims, n_projection_time_periods, projection_time_periods))

        # concatenate and add subsector emission totals
        df_out = sf.merge_output_df_list(df_out, self.model_attributes, "concatenate")

        return df_out