Skip to content

Commit 4c2ecae

Browse files
Merge pull request #47 from SeaCelo/feature/port-ogzaf-106-111
Port calibration API structure and offline gating
2 parents feb04e1 + f3ed63a commit 4c2ecae

15 files changed

Lines changed: 1023 additions & 229 deletions

File tree

.github/workflows/deploy_docs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ jobs:
3434
- name: Build # Build Jupyter Book
3535
shell: bash -l {0}
3636
run: |
37-
pip install jupyter-book>=0.11.3
38-
pip install sphinxcontrib-bibtex>=2.0.0
37+
pip install "jupyter-book<2.0.0"
38+
pip install "sphinxcontrib-bibtex>=2.0.0"
3939
pip install -e .
4040
python -m ipykernel install --user --name=ogphl-dev
4141
jb build ./docs/book

.github/workflows/docs_check.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ jobs:
3232
- name: Build # Build Jupyter Book
3333
shell: bash -l {0}
3434
run: |
35-
pip install jupyter-book>=0.11.3
36-
pip install sphinxcontrib-bibtex>=2.0.0
35+
pip install "jupyter-book<2.0.0"
36+
pip install "sphinxcontrib-bibtex>=2.0.0"
3737
pip install -e .
3838
python -m ipykernel install --user --name=ogphl-dev
3939
jb build ./docs/book

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ Once the package is installed, one can adjust parameters in the OG-Core `Specifi
4949
```
5050
from ogcore.parameters import Specifications
5151
from ogphl.calibrate import Calibration
52+
from ogphl.utils import is_connected
5253
p = Specifications()
53-
c = Calibration(p)
54-
updated_params = c.get_dict()
55-
p.update_specifications({'initial_debt_ratio': updated_params['initial_debt_ratio']})
54+
if is_connected():
55+
c = Calibration(p, update_from_api=True)
56+
updated_params = c.get_dict()
57+
p.update_specifications(updated_params)
5658
```
5759

5860
## Disclaimer

docs/book/content/UNtutorial/solutions/3perOG/SS.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from matplotlib.ticker import MultipleLocator
2929
import os
3030

31-
3231
# Define functions
3332

3433

docs/create_doc_figures.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from ogcore import demographics as demog
1515
import ogphl
1616

17-
1817
CUR_DIR = os.path.dirname(os.path.realpath(__file__))
1918
UN_COUNTRY_CODE = "608"
2019
plot_path = os.path.join(CUR_DIR, "book", "content", "calibration", "images")

environment.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ dependencies:
3434
- pip
3535
- pip:
3636
- openpyxl>=3.1.2
37-
- pandas-datareader
3837
- linecheck
3938
- ogcore>=0.14.5
4039
- sphinx-exercise

examples/run_og_phl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def main():
5454
p.update_specifications(defaults)
5555
# Update parameters from calibrate.py Calibration class
5656
if is_connected(): # only update if connected to internet
57-
c = Calibration(p)
57+
c = Calibration(p, update_from_api=True)
5858
updated_params = c.get_dict()
5959
p.update_specifications(updated_params)
6060

ogphl/calibrate.py

Lines changed: 97 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from ogphl import macro_params, income
22
from ogphl import input_output as io
33
import os
4+
import warnings
45
import numpy as np
56
import datetime
6-
from ogcore import demographics
77

88
UN_COUNTRY_CODE = "608"
99

@@ -18,14 +18,19 @@ def __init__(
1818
macro_data_end_year=datetime.datetime(2023, 1, 1),
1919
demographic_data_path=None,
2020
output_path=None,
21+
update_from_api=False,
2122
):
2223
"""
2324
Constructor for the Calibration class.
2425
2526
Args:
2627
p (OG-Core Specifications object): model parameters
28+
macro_data_start_year (datetime): start date for macro data
29+
macro_data_end_year (datetime): end date for macro data
2730
demographic_data_path (str): path to save demographic data
2831
output_path (str): path to save output to
32+
update_from_api (bool): Set True to pull updated data from
33+
online sources
2934
3035
Returns:
3136
None
@@ -36,70 +41,100 @@ def __init__(
3641
if not os.path.exists(output_path):
3742
os.makedirs(output_path)
3843

44+
# Initialize with overlay values only. Existing values on p are the
45+
# baseline from the packaged defaults.
46+
self.macro_params = {}
47+
self.demographic_params = {}
48+
self.e = None
49+
self.alpha_c = np.array([1.0]) if p.I == 1 else None
50+
self.io_matrix = np.array([[1.0]]) if p.M == 1 else None
51+
52+
if not update_from_api:
53+
return
54+
3955
# Macro estimation
40-
self.macro_params = macro_params.get_macro_params(
41-
macro_data_start_year, macro_data_end_year
42-
)
56+
try:
57+
self.macro_params = macro_params.get_macro_params(
58+
macro_data_start_year,
59+
macro_data_end_year,
60+
update_from_api=update_from_api,
61+
)
62+
except Exception as exc:
63+
warnings.warn(f"Macro params update failed: {exc}", stacklevel=2)
4364

4465
# io matrix and alpha_c
45-
if p.I > 1: # no need if just one consumption good
46-
alpha_c_dict = io.get_alpha_c()
47-
# check that model dimensions are consistent with alpha_c
48-
assert p.I == len(list(alpha_c_dict.keys()))
49-
self.alpha_c = np.array(list(alpha_c_dict.values()))
50-
else:
51-
self.alpha_c = np.array([1.0])
52-
if p.M > 1: # no need if just one production good
53-
io_df = io.get_io_matrix()
54-
# check that model dimensions are consistent with io_matrix
55-
assert p.M == len(list(io_df.keys()))
56-
self.io_matrix = io_df.values
57-
else:
58-
self.io_matrix = np.array([[1.0]])
59-
60-
# demographics
61-
self.demographic_params = demographics.get_pop_objs(
62-
p.E,
63-
p.S,
64-
p.T,
65-
0,
66-
99,
67-
country_id=UN_COUNTRY_CODE,
68-
initial_data_year=p.start_year - 1,
69-
final_data_year=p.start_year + 1,
70-
GraphDiag=False,
71-
download_path=demographic_data_path,
72-
)
73-
74-
# demographics for 80 period lives (needed for getting e below)
75-
demog80 = demographics.get_pop_objs(
76-
20,
77-
80,
78-
p.T,
79-
0,
80-
99,
81-
country_id=UN_COUNTRY_CODE,
82-
initial_data_year=p.start_year - 1,
83-
final_data_year=p.start_year + 1,
84-
GraphDiag=False,
85-
)
86-
87-
# earnings profiles
88-
self.e = income.get_e_interp(
89-
p.E,
90-
p.S,
91-
p.J,
92-
p.lambdas,
93-
demog80["omega_SS"],
94-
)
66+
if p.I > 1:
67+
try:
68+
alpha_c_dict = io.get_alpha_c()
69+
# check that model dimensions are consistent with alpha_c
70+
assert p.I == len(list(alpha_c_dict.keys()))
71+
self.alpha_c = np.array(list(alpha_c_dict.values()))
72+
except Exception as exc:
73+
warnings.warn(f"alpha_c update failed: {exc}", stacklevel=2)
74+
if p.M > 1:
75+
try:
76+
io_df = io.get_io_matrix()
77+
# check that model dimensions are consistent with io_matrix
78+
assert p.M == len(list(io_df.keys()))
79+
self.io_matrix = io_df.values
80+
except Exception as exc:
81+
warnings.warn(f"io_matrix update failed: {exc}", stacklevel=2)
82+
83+
# Demographics and income are atomic because e depends on demography.
84+
try:
85+
from ogcore import demographics
86+
87+
self.demographic_params = demographics.get_pop_objs(
88+
p.E,
89+
p.S,
90+
p.T,
91+
0,
92+
99,
93+
country_id=UN_COUNTRY_CODE,
94+
initial_data_year=p.start_year - 1,
95+
final_data_year=p.start_year + 1,
96+
GraphDiag=False,
97+
download_path=demographic_data_path,
98+
)
99+
100+
# demographics for 80 period lives (needed for getting e below)
101+
demog80 = demographics.get_pop_objs(
102+
20,
103+
80,
104+
p.T,
105+
0,
106+
99,
107+
country_id=UN_COUNTRY_CODE,
108+
initial_data_year=p.start_year - 1,
109+
final_data_year=p.start_year + 1,
110+
GraphDiag=False,
111+
)
112+
113+
# earnings profiles
114+
self.e = income.get_e_interp(
115+
p.E,
116+
p.S,
117+
p.J,
118+
p.lambdas,
119+
demog80["omega_SS"],
120+
)
121+
except Exception as exc:
122+
warnings.warn(
123+
f"Demographics/income update failed: {exc}", stacklevel=2
124+
)
125+
self.demographic_params = {}
126+
self.e = None
95127

96128
# method to return all newly calibrated parameters in a dictionary
97129
def get_dict(self):
98-
dict = {}
99-
dict.update(self.macro_params)
100-
dict["e"] = self.e
101-
dict["alpha_c"] = self.alpha_c
102-
dict["io_matrix"] = self.io_matrix
103-
dict.update(self.demographic_params)
104-
105-
return dict
130+
d = {}
131+
d.update(self.macro_params)
132+
d.update(self.demographic_params)
133+
if self.e is not None:
134+
d["e"] = self.e
135+
if self.alpha_c is not None:
136+
d["alpha_c"] = self.alpha_c
137+
if self.io_matrix is not None:
138+
d["io_matrix"] = self.io_matrix
139+
140+
return d

ogphl/input_output.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,30 @@
33
import os
44
from ogphl.constants import CONS_DICT, PROD_DICT
55

6-
76
CUR_DIR = os.path.dirname(os.path.realpath(__file__))
87
"""
98
Read in Social Accounting Matrix (SAM) file
109
"""
11-
# Read in SAM file
12-
# SAM file:
1310
sam_path = os.path.join(CUR_DIR, "data", "002_IFPRI_SAM_PHL_2018_SAM.csv")
14-
SAM = pd.read_csv(sam_path, index_col=1, thousands=",")
15-
# replace NaN with 0
16-
SAM.fillna(0, inplace=True)
1711

1812

19-
def get_alpha_c(sam=SAM, cons_dict=CONS_DICT):
13+
def read_SAM():
14+
"""
15+
Read in the packaged Social Accounting Matrix (SAM) file.
16+
17+
Returns:
18+
SAM (pd.DataFrame | None): Social Accounting Matrix, or None if unavailable
19+
"""
20+
try:
21+
sam = pd.read_csv(sam_path, index_col=1, thousands=",")
22+
sam.fillna(0, inplace=True)
23+
return sam
24+
except Exception as exc:
25+
print(f"Failed to read packaged SAM file: {exc}")
26+
return None
27+
28+
29+
def get_alpha_c(sam=None, cons_dict=CONS_DICT):
2030
"""
2131
Calibrate the alpha_c vector, showing the shares of household
2232
expenditures for each consumption category
@@ -28,6 +38,10 @@ def get_alpha_c(sam=SAM, cons_dict=CONS_DICT):
2838
Returns:
2939
alpha_c (dict): Dictionary of shares of household expenditures
3040
"""
41+
if sam is None:
42+
sam = read_SAM()
43+
if sam is None:
44+
raise RuntimeError("SAM data is unavailable. Cannot compute alpha_c.")
3145
hh_cols = [
3246
"hhd-r1",
3347
"hhd-r2",
@@ -55,7 +69,7 @@ def get_alpha_c(sam=SAM, cons_dict=CONS_DICT):
5569
return alpha_c
5670

5771

58-
def get_io_matrix(sam=SAM, cons_dict=CONS_DICT, prod_dict=PROD_DICT):
72+
def get_io_matrix(sam=None, cons_dict=CONS_DICT, prod_dict=PROD_DICT):
5973
"""
6074
Calibrate the io_matrix array. This array relates the share of each
6175
production category in each consumption category
@@ -68,6 +82,12 @@ def get_io_matrix(sam=SAM, cons_dict=CONS_DICT, prod_dict=PROD_DICT):
6882
Returns:
6983
io_df (pd.DataFrame): Dataframe of io_matrix
7084
"""
85+
if sam is None:
86+
sam = read_SAM()
87+
if sam is None:
88+
raise RuntimeError(
89+
"SAM data is unavailable. Cannot compute io_matrix."
90+
)
7191
# Create initial matrix as dataframe of 0's to fill in
7292
io_dict = {}
7393
for key in prod_dict.keys():

0 commit comments

Comments
 (0)