Skip to content

Commit 1751936

Browse files
authored
Merge pull request #41 from Zhanwei-Liu/dev
feat(finance): add economic module with public/private debt modeling
2 parents 95b427a + bfd9de6 commit 1751936

15 files changed

+242
-49
lines changed

params.json

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,61 @@
311311
"unstack_levels": null,
312312
"first_col_only": false,
313313
"drop_na": false
314+
},
315+
"public_debt_ratio": {
316+
"file_name": "public_debt_ratio",
317+
"index_cols": [0],
318+
"header_rows": [0],
319+
"unstack_levels": null,
320+
"first_col_only": true,
321+
"drop_na": true
322+
},
323+
"private_debt_ratio": {
324+
"file_name": "private_debt_ratio",
325+
"index_cols": [0],
326+
"header_rows": [0],
327+
"unstack_levels": null,
328+
"first_col_only": true,
329+
"drop_na": true
330+
},
331+
"cost_of_public_debt": {
332+
"file_name": "cost_of_public_debt",
333+
"index_cols": [0],
334+
"header_rows": [0],
335+
"unstack_levels": [0],
336+
"first_col_only": false,
337+
"drop_na": true
338+
},
339+
"cost_of_private_equity": {
340+
"file_name": "cost_of_private_equity",
341+
"index_cols": [0],
342+
"header_rows": [0],
343+
"unstack_levels": [0],
344+
"first_col_only": false,
345+
"drop_na": true
346+
},
347+
"cost_of_private_debt": {
348+
"file_name": "cost_of_private_debt",
349+
"index_cols": [0],
350+
"header_rows": [0],
351+
"unstack_levels": [0],
352+
"first_col_only": false,
353+
"drop_na": true
354+
},
355+
"public_debt_upper_bound_zone": {
356+
"file_name": "public_debt_upper_bound_zone",
357+
"index_cols": [0],
358+
"header_rows": [0],
359+
"unstack_levels": [0],
360+
"first_col_only": false,
361+
"drop_na": true
362+
},
363+
"public_debt_upper_bound_system": {
364+
"file_name": "public_debt_upper_bound_system",
365+
"index_cols": [0],
366+
"header_rows": [0],
367+
"unstack_levels": null,
368+
"first_col_only": true,
369+
"drop_na": true
314370
}
315-
316371
}

prepshot/_model/cost.py

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -212,31 +212,6 @@ def cost_fix_tech_breakdown(
212212
ff = model.params['fix_factor'][y]
213213
return tfc * model.cap_existing[y, z, te] * ff
214214

215-
def cost_newtech_breakdown(
216-
self, y : int, z : str, te : str
217-
) -> poi.ExprBuilder:
218-
"""New technology investment cost breakdown.
219-
220-
Parameters
221-
----------
222-
y : int
223-
Year.
224-
z : str
225-
Zone.
226-
te : str
227-
Technology.
228-
229-
Returns
230-
-------
231-
poi.ExprBuilder
232-
Investment cost of new technologies at a given year, zone and
233-
technology.
234-
"""
235-
model = self.model
236-
tic = model.params['technology_investment_cost'][te, y]
237-
ivf = model.params['inv_factor'][te, y]
238-
return tic * model.cap_newtech[y, z, te] * ivf
239-
240215
def cost_newline_breakdown(
241216
self, y : int, z : str, z1 : str
242217
) -> poi.ExprBuilder:
@@ -330,10 +305,7 @@ def newtech_cost_rule(self) -> poi.ExprBuilder:
330305
technologies.
331306
"""
332307
model = self.model
333-
model.cost_newtech_breakdown = poi.make_tupledict(
334-
model.year, model.zone, model.tech,
335-
rule=self.cost_newtech_breakdown
336-
)
308+
# Define the cost breakdown of new technology investment in Finance module
337309
return poi.quicksum(model.cost_newtech_breakdown)
338310

339311
def newline_cost_rule(self) -> poi.ExprBuilder:

prepshot/_model/finance.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import pyoptinterface as poi
2+
import numpy as np
3+
4+
5+
class AddFinanceConstraints:
6+
"""Class for financial calculations and constraints.
7+
"""
8+
def __init__(self, model : object) -> None:
9+
"""Initialize the class.
10+
11+
Parameters
12+
----------
13+
model : object
14+
Model object depending on the solver.
15+
16+
"""
17+
self.model = model
18+
model.cost_newtech_breakdown = poi.make_tupledict(
19+
model.year, model.zone, model.tech,
20+
rule=self.cost_newtech_breakdown
21+
)
22+
model.public_debt_newtech = poi.make_tupledict(
23+
model.year, model.zone, model.tech,
24+
rule=self.public_debt_newtech
25+
)
26+
model.public_debt_upper_bound_system_cons = poi.make_tupledict(
27+
model.year,
28+
rule=self.public_debt_upper_bound_system_rule
29+
)
30+
model.public_debt_upper_bound_zone_cons = poi.make_tupledict(
31+
model.year, model.zone,
32+
rule=self.public_debt_upper_bound_zone_rule
33+
)
34+
35+
def public_debt_newtech(
36+
self, y : int, z : str, te : str
37+
) -> poi.ConstraintIndex:
38+
"""Public debt investment calculation for each zone and technology.
39+
40+
Parameters
41+
----------
42+
y : int
43+
Planned year.
44+
z : str
45+
Zone.
46+
te : str
47+
Technology.
48+
49+
Returns
50+
-------
51+
poi.ConstraintIndex
52+
A constraint of the model.
53+
"""
54+
model = self.model
55+
return model.cost_newtech_breakdown[y, z, te] * model.params['public_debt_ratio'][te]
56+
57+
def public_debt_upper_bound_system_rule(
58+
self, y : int
59+
) -> poi.ConstraintIndex:
60+
"""Public debt upper bound constraint for each zone and technology.
61+
62+
Parameters
63+
----------
64+
y : int
65+
Planned year.
66+
67+
Returns
68+
-------
69+
poi.ConstraintIndex
70+
A constraint of the model.
71+
"""
72+
model = self.model
73+
upper_bound = model.params['public_debt_upper_bound_system'][y]
74+
if upper_bound != np.Inf:
75+
lhs = poi.ExprBuilder(0)
76+
lhs += poi.quicksum(
77+
model.public_debt_newtech.select(y, '*', '*')
78+
)
79+
lhs -= upper_bound
80+
return model.add_linear_constraint(lhs, poi.Leq, 0)
81+
return None
82+
83+
def public_debt_upper_bound_zone_rule(
84+
self, y : int, z : str
85+
) -> poi.ConstraintIndex:
86+
"""Public debt upper bound constraint for each zone and technology.
87+
88+
Parameters
89+
----------
90+
y : int
91+
Planned year.
92+
z : str
93+
Zone.
94+
95+
Returns
96+
-------
97+
poi.ConstraintIndex
98+
A constraint of the model.
99+
"""
100+
model = self.model
101+
upper_bound = model.params['public_debt_upper_bound_zone'][z, y]
102+
if upper_bound != np.Inf:
103+
lhs = poi.ExprBuilder(0)
104+
lhs += poi.quicksum(
105+
model.public_debt_newtech.select(y, z, '*')
106+
)
107+
lhs -= upper_bound
108+
return model.add_linear_constraint(lhs, poi.Leq, 0)
109+
return None
110+
111+
def cost_newtech_breakdown(
112+
self, y : int, z : str, te : str
113+
) -> poi.ExprBuilder:
114+
"""New technology investment cost breakdown.
115+
116+
Parameters
117+
----------
118+
y : int
119+
Year.
120+
z : str
121+
Zone.
122+
te : str
123+
Technology.
124+
125+
Returns
126+
-------
127+
poi.ExprBuilder
128+
Investment cost of new technologies at a given year, zone and
129+
technology.
130+
"""
131+
model = self.model
132+
tic = model.params['technology_investment_cost'][te, y]
133+
ivf = model.params['inv_factor'][te, y, z]
134+
return tic * model.cap_newtech[y, z, te] * ivf

prepshot/load_data.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import pandas as pd
1414

15-
from prepshot.utils import calc_inv_cost_factor, calc_cost_factor
15+
from prepshot.utils import calc_inv_cost_factor, calc_cost_factor, calc_interest_rate
1616

1717

1818
def load_json(file_path : str) -> dict:
@@ -151,23 +151,31 @@ def compute_cost_factors(data_store : dict) -> None:
151151
# Calculate cost factors
152152
for tech in data_store["tech"]:
153153
for year in data_store["year"]:
154-
discount_rate = data_store["discount_factor"][year]
155-
next_year = year+1 if year == y_max \
156-
else data_store["year"][data_store["year"].index(year) + 1]
157-
data_store["trans_inv_factor"][year] = calc_inv_cost_factor(
158-
trans_line_lifetime, discount_rate, year, discount_rate,
159-
y_min, y_max
160-
)
161-
data_store["inv_factor"][tech, year] = calc_inv_cost_factor(
162-
lifetime[tech, year], discount_rate, year, discount_rate,
163-
y_min, y_max
164-
)
165-
data_store["fix_factor"][year] = calc_cost_factor(
166-
discount_rate, year, y_min, next_year
167-
)
168-
data_store["var_factor"][year] = calc_cost_factor(
169-
discount_rate, year, y_min, next_year
170-
)
154+
for zone in data_store["zone"]:
155+
interest_rate = calc_interest_rate(
156+
data_store["public_debt_ratio"][tech],
157+
data_store["private_debt_ratio"][tech],
158+
data_store["cost_of_public_debt"][tech, zone],
159+
data_store["cost_of_private_equity"][tech, zone],
160+
data_store["cost_of_private_debt"][tech, zone]
161+
)
162+
discount_rate = data_store["discount_factor"][year]
163+
next_year = year+1 if year == y_max \
164+
else data_store["year"][data_store["year"].index(year) + 1]
165+
data_store["trans_inv_factor"][year] = calc_inv_cost_factor(
166+
trans_line_lifetime, discount_rate, year, discount_rate,
167+
y_min, y_max
168+
)
169+
data_store["inv_factor"][tech, year, zone] = calc_inv_cost_factor(
170+
lifetime[tech, year], interest_rate, year, discount_rate,
171+
y_min, y_max
172+
)
173+
data_store["fix_factor"][year] = calc_cost_factor(
174+
discount_rate, year, y_min, next_year
175+
)
176+
data_store["var_factor"][year] = calc_cost_factor(
177+
discount_rate, year, y_min, next_year
178+
)
171179

172180

173181
def read_excel(

prepshot/model.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from prepshot._model.nondispatchable import AddNondispatchableConstraints
1616
from prepshot._model.transmission import AddTransmissionConstraints
1717
from prepshot._model.investment import AddInvestmentConstraints
18+
from prepshot._model.finance import AddFinanceConstraints
1819
from prepshot.logs import timer
1920
from prepshot.solver import get_solver
2021
from prepshot.solver import set_solver_parameters
@@ -157,6 +158,7 @@ def define_constraints(model : object) -> None:
157158
AddStorageConstraints(model)
158159
AddHydropowerConstraints(model)
159160
AddDemandConstraints(model)
161+
AddFinanceConstraints(model)
160162

161163
@timer
162164
def create_model(params : dict) -> object:

prepshot/output_data.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ def extract_results_non_hydro(model : object) -> xr.Dataset:
9696
model.cost_newtech_breakdown, ['year', 'zone', 'tech'],
9797
'dollar', model
9898
)
99+
data_vars['public_debt_newtech'] = create_data_array(
100+
model.public_debt_newtech, ['year', 'zone', 'tech'],
101+
'dollar', model
102+
)
99103
data_vars['cost_newline_breakdown'] = create_data_array(
100104
model.cost_newline_breakdown, ['year', 'zone1', 'zone2'],
101105
'dollar', model

prepshot/utils.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,29 @@ def check_positive(*values : Union[int, float]) -> None:
148148
if value <= 0:
149149
raise ValueError("All arguments must be greater than 0.")
150150

151+
def calc_interest_rate(
152+
public_debt_ratio : float,
153+
private_debt_ratio : float,
154+
cost_of_public_debt: float,
155+
cost_of_private_equity: float,
156+
cost_of_private_debt: float
157+
) -> float:
158+
"""Calculate interest rate for each zone and technology.
159+
"""
160+
equity_ratio = 1 - public_debt_ratio - private_debt_ratio
161+
if (public_debt_ratio < 0) or (private_debt_ratio < 0) or (equity_ratio < 0):
162+
raise ValueError("Debt and equity ratios must be non-negative "
163+
+ "and sum to 1 or less.")
164+
interest_rate = (public_debt_ratio * cost_of_public_debt
165+
+ private_debt_ratio * cost_of_private_debt
166+
+ equity_ratio * cost_of_private_equity)
167+
return interest_rate
168+
151169
def calc_inv_cost_factor(
152170
dep_period : int,
153171
interest_rate : float,
154172
year_built : int,
155-
discount_rate : int,
173+
discount_rate : float,
156174
year_min : int,
157175
year_max : int
158176
) -> float:
9.11 KB
Binary file not shown.
9.11 KB
Binary file not shown.
9.11 KB
Binary file not shown.

0 commit comments

Comments
 (0)