37
37
'Sales [MM$]' ,
38
38
'Tax [MM$]' ,
39
39
'Incentives [MM$]' ,
40
+ 'Taxed earnings [MM$]' ,
41
+ 'Forwarded losses [MM$]' ,
40
42
'Net earnings [MM$]' ,
41
43
'Cash flow [MM$]' ,
42
44
'Discount factor' ,
@@ -72,7 +74,7 @@ def NPV_at_IRR(IRR, cashflow_array, duration_array):
72
74
return (cashflow_array / (1. + IRR )** duration_array ).sum ()
73
75
74
76
@njit (cache = True )
75
- def initial_loan_principal (loan , interest ):
77
+ def loan_principal_with_interest (loan , interest ):
76
78
principal = 0
77
79
k = 1. + interest
78
80
for i in loan :
@@ -81,18 +83,21 @@ def initial_loan_principal(loan, interest):
81
83
return principal
82
84
83
85
@njit (cache = True )
84
- def final_loan_principal ( payment , principal , interest , years ):
85
- for iter in range ( years ):
86
- principal += principal * interest - payment
87
- return principal
86
+ def solve_payment ( loan_principal , interest , years ):
87
+ f = 1 + interest
88
+ fn = f ** years
89
+ return loan_principal * interest * fn / ( fn - 1 )
88
90
89
- def solve_payment (payment , loan , interest , years ):
90
- principal = initial_loan_principal (loan , interest )
91
- payment = flx .aitken_secant (final_loan_principal ,
92
- payment , payment + 10. , 1. , 10. ,
93
- args = (principal , interest , years ),
94
- maxiter = 200 , checkiter = False )
95
- return payment
91
+ @njit (cache = True )
92
+ def taxable_earnings_with_fowarded_losses (taxable_cashflow ): # Forwards losses to later years to reduce future taxes
93
+ taxed_earnings = taxable_cashflow .copy ()
94
+ for i in range (taxed_earnings .size - 1 ):
95
+ x = taxed_earnings [i ]
96
+ if x < 0 :
97
+ taxed_earnings [i ] = 0
98
+ taxed_earnings [i + 1 ] += x
99
+ if taxed_earnings [- 1 ] < 0 : taxed_earnings [- 1 ] = 0
100
+ return taxed_earnings
96
101
97
102
@njit (cache = True )
98
103
def add_replacement_cost_to_cashflow_array (equipment_installed_cost ,
@@ -170,6 +175,7 @@ def taxable_and_nontaxable_cashflows(
170
175
finance_fraction ,
171
176
start , years ,
172
177
lang_factor ,
178
+ accumulate_interest_during_construction ,
173
179
):
174
180
# Cash flow data and parameters
175
181
# C_FC: Fixed capital
@@ -186,18 +192,23 @@ def taxable_and_nontaxable_cashflows(
186
192
startup_FOCfrac ,
187
193
startup_salesfrac ,
188
194
construction_schedule ,
189
- start
195
+ start ,
190
196
)
191
197
for i in unit_capital_costs :
192
198
add_all_replacement_costs_to_cashflow_array (i , C_FC , years , start , lang_factor )
193
199
if finance_interest :
194
200
interest = finance_interest
195
201
years = finance_years
196
- Loan [:start ] = loan = finance_fraction * (C_FC [:start ]+ C_WC [:start ])
197
- LP [start :start + years ] = solve_payment (loan .sum ()/ years * (1. + interest ),
198
- loan , interest , years )
202
+ Loan [:start ] = loan = finance_fraction * (C_FC [:start ])
203
+ if accumulate_interest_during_construction :
204
+ loan_principal = loan_principal_with_interest (loan , interest )
205
+ else :
206
+ loan_principal = loan .sum ()
207
+ LP [start :start + years ] = solve_payment (loan_principal , interest , years )
199
208
taxable_cashflow = S - C - D - LP
200
209
nontaxable_cashflow = D + Loan - C_FC - C_WC
210
+ if not accumulate_interest_during_construction :
211
+ nontaxable_cashflow [:start ] -= loan * interest
201
212
else :
202
213
taxable_cashflow = S - C - D
203
214
nontaxable_cashflow = D - C_FC - C_WC
@@ -214,9 +225,13 @@ def NPV_with_sales(
214
225
):
215
226
"""Return NPV with an additional annualized sales."""
216
227
taxable_cashflow = taxable_cashflow + sales * sales_coefficients
217
- tax = np .zeros_like (taxable_cashflow )
228
+ tax = np .zeros_like (taxable_cashflow , dtype = float )
218
229
incentives = tax .copy ()
219
- fill_tax_and_incentives (incentives , taxable_cashflow , nontaxable_cashflow , tax , depreciation )
230
+ fill_tax_and_incentives (
231
+ incentives ,
232
+ taxable_earnings_with_fowarded_losses (taxable_cashflow ),
233
+ nontaxable_cashflow , tax , depreciation
234
+ )
220
235
cashflow = nontaxable_cashflow + taxable_cashflow + incentives - tax
221
236
return (cashflow / discount_factors ).sum ()
222
237
@@ -312,7 +327,7 @@ class TEA:
312
327
'_startup_schedule' , '_operating_days' ,
313
328
'_duration' , '_depreciation_key' , '_depreciation' ,
314
329
'_years' , '_duration' , '_start' , 'IRR' , '_IRR' , '_sales' ,
315
- '_duration_array_cache' )
330
+ '_duration_array_cache' , 'accumulate_interest_during_construction' )
316
331
317
332
#: Available depreciation schedules. Defaults include modified
318
333
#: accelerated cost recovery system from U.S. IRS publication 946 (MACRS),
@@ -389,7 +404,8 @@ def __init__(self, system: System, IRR: float, duration: tuple[int, int],
389
404
construction_schedule : Sequence [float ],
390
405
startup_months : float , startup_FOCfrac : float , startup_VOCfrac : float ,
391
406
startup_salesfrac : float , WC_over_FCI : float , finance_interest : float ,
392
- finance_years : int , finance_fraction : float ):
407
+ finance_years : int , finance_fraction : float ,
408
+ accumulate_interest_during_construction : bool = False ):
393
409
#: System being evaluated.
394
410
self .system : System = system
395
411
@@ -434,6 +450,9 @@ def __init__(self, system: System, IRR: float, duration: tuple[int, int],
434
450
#: Guess cost for solve_price method
435
451
self ._sales : float = 0
436
452
453
+ #: Whether to immediately pay interest before operation or to accumulate interest during construction
454
+ self .accumulate_interest_during_construction = accumulate_interest_during_construction
455
+
437
456
#: For convenience, set a TEA attribute for the system
438
457
system ._TEA = self
439
458
@@ -708,6 +727,8 @@ def get_cashflow_table(self):
708
727
# S: Sales
709
728
# T: Tax
710
729
# I: Incentives
730
+ # TE: Taxed earnings
731
+ # FL: Forwarded losses
711
732
# NE: Net earnings
712
733
# CF: Cash flow
713
734
# DF: Discount factor
@@ -721,7 +742,7 @@ def get_cashflow_table(self):
721
742
VOC = self .VOC
722
743
sales = self .sales
723
744
length = start + years
724
- C_D , C_FC , C_WC , D , L , LI , LP , LPl , C , S , T , I , NE , CF , DF , NPV , CNPV = data = np .zeros ((17 , length ))
745
+ C_D , C_FC , C_WC , D , L , LI , LP , LPl , C , S , T , I , TE , FL , NE , CF , DF , NPV , CNPV = data = np .zeros ((19 , length ))
725
746
self ._fill_depreciation_array (D , start , years , TDC )
726
747
w0 = self ._startup_time
727
748
w1 = 1. - w0
@@ -744,24 +765,44 @@ def get_cashflow_table(self):
744
765
interest = self .finance_interest
745
766
years = self .finance_years
746
767
end = start + years
747
- L [:start ] = loan = self .finance_fraction * (C_FC [:start ]+ C_WC [:start ])
748
- f_interest = (1. + interest )
749
- LP [start :end ] = solve_payment (loan .sum ()/ years * f_interest ,
750
- loan , interest , years )
768
+ L [:start ] = loan = self .finance_fraction * (C_FC [:start ])
769
+ accumulate_interest_during_construction = self .accumulate_interest_during_construction
770
+ if accumulate_interest_during_construction :
771
+ initial_loan_principal = loan_principal_with_interest (loan , interest )
772
+ else :
773
+ initial_loan_principal = loan .sum ()
774
+ LP [start :end ] = solve_payment (initial_loan_principal , interest , years )
751
775
loan_principal = 0
752
- for i in range (end ):
753
- LI [i ] = li = (loan_principal + L [i ]) * interest
754
- LPl [i ] = loan_principal = loan_principal - LP [i ] + li + L [i ]
776
+ if accumulate_interest_during_construction :
777
+ for i in range (end ):
778
+ LI [i ] = li = (loan_principal + L [i ]) * interest
779
+ LPl [i ] = loan_principal = loan_principal - LP [i ] + li + L [i ]
780
+ else :
781
+ for i in range (end ):
782
+ if i < start :
783
+ li = 0
784
+ else :
785
+ li = (loan_principal + L [i ]) * interest
786
+ LI [i ] = li
787
+ LPl [i ] = loan_principal = loan_principal - LP [i ] + li + L [i ]
788
+ LI [:start ] = L [:start ] * interest # Interest still needs to be payed
789
+
755
790
taxable_cashflow = S - C - D - LP
756
791
nontaxable_cashflow = D + L - C_FC - C_WC
792
+ if not accumulate_interest_during_construction :
793
+ nontaxable_cashflow [:start ] -= LI [:start ] # Subtract the interest during construction from NPV
757
794
else :
758
795
taxable_cashflow = S - C - D
759
796
nontaxable_cashflow = D - C_FC - C_WC
760
- self ._fill_tax_and_incentives (I , taxable_cashflow , nontaxable_cashflow , T , D )
797
+ TE [:] = taxable_earnings_with_fowarded_losses (taxable_cashflow )
798
+ FL [1 :] = (taxable_cashflow - TE ).cumsum ()[:- 1 ]
799
+ self ._fill_tax_and_incentives (
800
+ I , TE , nontaxable_cashflow , T , D
801
+ )
761
802
NE [:] = taxable_cashflow + I - T
762
803
CF [:] = NE + nontaxable_cashflow
763
804
DF [:] = 1 / (1. + self .IRR )** self ._get_duration_array ()
764
- NPV [:] = CF * DF
805
+ NPV [:] = CF * DF
765
806
CNPV [:] = NPV .cumsum ()
766
807
DF *= 1e6
767
808
data /= 1e6
@@ -774,7 +815,11 @@ def NPV(self) -> float:
774
815
taxable_cashflow , nontaxable_cashflow , depreciation = self ._taxable_nontaxable_depreciation_cashflows ()
775
816
tax = np .zeros_like (taxable_cashflow )
776
817
incentives = tax .copy ()
777
- self ._fill_tax_and_incentives (incentives , taxable_cashflow , nontaxable_cashflow , tax , depreciation )
818
+ self ._fill_tax_and_incentives (
819
+ incentives ,
820
+ taxable_earnings_with_fowarded_losses (taxable_cashflow ),
821
+ nontaxable_cashflow , tax , depreciation
822
+ )
778
823
cashflow = nontaxable_cashflow + taxable_cashflow + incentives - tax
779
824
return NPV_at_IRR (self .IRR , cashflow , self ._get_duration_array ())
780
825
@@ -818,21 +863,25 @@ def _taxable_nontaxable_depreciation_cashflows(self):
818
863
self .finance_years ,
819
864
self .finance_fraction ,
820
865
start , years ,
821
- self .lang_factor
866
+ self .lang_factor ,
867
+ self .accumulate_interest_during_construction ,
822
868
),
823
869
D
824
870
)
825
871
826
872
def _fill_tax_and_incentives (self , incentives , taxable_cashflow , nontaxable_cashflow , tax , depreciation ):
827
- index = taxable_cashflow > 0.
828
- tax [index ] = self .income_tax * taxable_cashflow [index ]
873
+ tax [:] = self .income_tax * taxable_cashflow
829
874
830
875
def _net_earnings_and_nontaxable_cashflow_arrays (self ):
831
876
taxable_cashflow , nontaxable_cashflow , depreciation = self ._taxable_nontaxable_depreciation_cashflows ()
832
877
size = taxable_cashflow .size
833
878
tax = np .zeros (size )
834
879
incentives = tax .copy ()
835
- self ._fill_tax_and_incentives (incentives , taxable_cashflow , nontaxable_cashflow , tax , depreciation )
880
+ self ._fill_tax_and_incentives (
881
+ incentives ,
882
+ taxable_earnings_with_fowarded_losses (taxable_cashflow ),
883
+ nontaxable_cashflow , tax , depreciation
884
+ )
836
885
net_earnings = taxable_cashflow + incentives - tax
837
886
return net_earnings , nontaxable_cashflow
838
887
@@ -950,12 +999,11 @@ def solve_sales(self):
950
999
951
1000
"""
952
1001
discount_factors = (1 + self .IRR )** self ._get_duration_array ()
953
- sales_coefficients = np .ones_like (discount_factors )
1002
+ sales_coefficients = np .ones_like (discount_factors , dtype = float )
954
1003
start = self ._start
955
1004
sales_coefficients [:start ] = 0
956
1005
w0 = self ._startup_time
957
- sales_coefficients [self ._start ] = w0 * self .startup_VOCfrac + (1 - w0 )
958
- sales = self ._sales
1006
+ sales_coefficients [start ] = w0 * self .startup_salesfrac + (1. - w0 )
959
1007
taxable_cashflow , nontaxable_cashflow , depreciation = self ._taxable_nontaxable_depreciation_cashflows ()
960
1008
if np .isnan (taxable_cashflow ).any ():
961
1009
warn ('nan encountered in cashflow array; resimulating system' , category = RuntimeWarning )
@@ -969,17 +1017,16 @@ def solve_sales(self):
969
1017
sales_coefficients ,
970
1018
discount_factors ,
971
1019
self ._fill_tax_and_incentives )
972
- x0 = sales
1020
+ x0 = self . _sales if np . isfinite ( self . _sales ) else 0
973
1021
f = NPV_with_sales
974
- if not np .isfinite (x0 ): x0 = 0.
975
1022
y0 = f (x0 , * args )
976
1023
x1 = x0 - y0 / self ._years # First estimate
977
1024
try :
978
- sales = flx .aitken_secant (f , x0 , x1 , xtol = 10 , ytol = 1000 . ,
1025
+ sales = flx .aitken_secant (f , x0 , x1 , xtol = 10 , ytol = 100 . ,
979
1026
maxiter = 1000 , args = args , checkiter = True )
980
1027
except :
981
1028
bracket = flx .find_bracket (f , x0 , x1 , args = args )
982
- sales = flx .IQ_interpolation (f , * bracket , args = args , xtol = 10 , ytol = 1000 , maxiter = 1000 , checkiter = False )
1029
+ sales = flx .IQ_interpolation (f , * bracket , args = args , xtol = 10 , ytol = 100 , maxiter = 1000 , checkiter = False )
983
1030
self ._sales = sales
984
1031
return sales
985
1032
0 commit comments