Skip to content

Commit 83227e3

Browse files
authored
Merge pull request PSLmodels#1077 from jdebacker/reform_theta
Merging
2 parents cb0e9df + f9e34c1 commit 83227e3

File tree

9 files changed

+233
-131
lines changed

9 files changed

+233
-131
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.15.0] - 2025-12-03 12:00:00
9+
10+
### Added
11+
12+
- A new parameter `baseline_theta` to the `Parameters` class that allows the user to specify whether to use the steady-state replacement rate parameters from the baseline solution in a reform run. See PR [#1077](https://github.com/PSLmodels/OG-Core/pull/1077)
13+
814
## [0.14.14] - 2025-11-24 12:00:00
915

1016
### Added
@@ -481,6 +487,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
481487
- Any earlier versions of OG-USA can be found in the [`OG-Core`](https://github.com/PSLmodels/OG-Core) repository [release history](https://github.com/PSLmodels/OG-Core/releases) from [v.0.6.4](https://github.com/PSLmodels/OG-Core/releases/tag/v0.6.4) (Jul. 20, 2021) or earlier.
482488

483489

490+
[0.15.0]: https://github.com/PSLmodels/OG-Core/compare/v0.14.14...v0.15.0
484491
[0.14.14]: https://github.com/PSLmodels/OG-Core/compare/v0.14.13...v0.14.14
485492
[0.14.13]: https://github.com/PSLmodels/OG-Core/compare/v0.14.12...v0.14.13
486493
[0.14.12]: https://github.com/PSLmodels/OG-Core/compare/v0.14.11...v0.14.12

ogcore/SS.py

Lines changed: 100 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,27 @@ def inner_loop(outer_loop_vars, p, client):
283283
# from dask.base import dask_sizeof
284284

285285
if client:
286-
# Scatter p only once and only if client not equal None
286+
287+
# Before scattering, temporarily remove unpicklable schema objects
288+
schema_backup = {}
289+
for attr in ["_defaults_schema", "_validator_schema", "sel"]:
290+
if hasattr(p, attr):
291+
schema_backup[attr] = getattr(p, attr)
292+
try:
293+
delattr(p, attr)
294+
except:
295+
pass
296+
297+
# Scatter the parameters
287298
scattered_p_future = client.scatter(p, broadcast=True)
288299

300+
# Restore the schema objects (they're not needed by workers anyway)
301+
for attr, value in schema_backup.items():
302+
try:
303+
setattr(p, attr, value)
304+
except:
305+
pass
306+
289307
# Launch in parallel with submit (or map)
290308
futures = []
291309
for j in range(p.J):
@@ -1363,7 +1381,7 @@ def SS_initial_guesses(p, b_val=0.0055, n_val=0.4, r_tr_scalars=[1.0, 1.0]):
13631381
if p.baseline:
13641382
guesses.append(p.initial_guess_factor_SS)
13651383

1366-
return guesses, b_guess, n_guess
1384+
return guesses, b_guess, n_guess
13671385

13681386

13691387
def run_SS(p, client=None):
@@ -1381,92 +1399,90 @@ def run_SS(p, client=None):
13811399
"""
13821400
# For initial guesses of w, r, TR, and factor, we use values that
13831401
# are close to some steady state values.
1384-
if p.baseline is False and p.reform_use_baseline_solution:
1385-
# Use the baseline solution to get starting values for the reform
1402+
# Use the baseline solution to get starting values for the reform
1403+
use_new_guesses = False # initialize this flag, switches to true
1404+
# if baseline solution not work for reform
1405+
if p.baseline is False:
13861406
baseline_ss_path = os.path.join(p.baseline_dir, "SS", "SS_vars.pkl")
13871407
ss_solutions = utils.safe_read_pickle(baseline_ss_path)
1388-
if p.reform_use_baseline_solution:
1389-
# use baseline solution as starting values if dimensions match
1390-
try:
1391-
if ss_solutions["b_sp1"].shape == (
1392-
p.S,
1393-
p.J,
1394-
) and np.squeeze(
1395-
ss_solutions["Y_m"].shape
1396-
) == (p.M):
1397-
logging.info("Using previous solutions for SS")
1398-
(
1399-
b_guess,
1400-
n_guess,
1401-
r_p_guess,
1402-
rguess,
1403-
wguess,
1404-
p_m_guess,
1405-
BQguess,
1406-
TRguess,
1407-
Yguess,
1408-
factor_ss,
1409-
) = (
1410-
ss_solutions["b_sp1"],
1411-
ss_solutions["n"],
1412-
float(ss_solutions["r_p"]),
1413-
float(ss_solutions["r"]),
1414-
float(ss_solutions["w"]),
1415-
ss_solutions["p_m"],
1416-
ss_solutions["BQ"],
1417-
float(ss_solutions["TR"]),
1418-
float(ss_solutions["Y"]),
1419-
ss_solutions["factor"],
1420-
)
1421-
use_new_guesses = False
1422-
if p.baseline_spending:
1423-
TR_baseline = TRguess
1424-
Ig_baseline = ss_solutions["I_g"]
1425-
else:
1426-
TR_baseline = None
1427-
Ig_baseline = None
1428-
BQ_items = [BQguess] if p.use_zeta else list(BQguess)
1429-
guesses = (
1430-
[r_p_guess, rguess, wguess]
1431-
+ list(p_m_guess)
1432-
+ [Yguess]
1433-
+ BQ_items
1434-
+ [TRguess]
1435-
)
1436-
# Now solve for the steady state of the reform
1437-
ss_params = (
1438-
b_guess,
1439-
n_guess,
1440-
TR_baseline,
1441-
Ig_baseline,
1442-
factor_ss,
1443-
p,
1444-
client,
1445-
)
1446-
1447-
# Solve for steady state using root finder
1448-
sol = opt.root(
1449-
SS_fsolve,
1450-
guesses,
1451-
args=ss_params,
1452-
method=p.SS_root_method,
1453-
tol=p.mindist_SS,
1454-
)
1408+
if p.baseline is False and p.reform_use_baseline_solution:
1409+
# use baseline solution as starting values if dimensions match
1410+
try:
1411+
if ss_solutions["b_sp1"].shape == (
1412+
p.S,
1413+
p.J,
1414+
) and np.squeeze(
1415+
ss_solutions["Y_m"].shape
1416+
) == (p.M):
1417+
logging.info("Using previous solutions for SS")
1418+
(
1419+
b_guess,
1420+
n_guess,
1421+
r_p_guess,
1422+
rguess,
1423+
wguess,
1424+
p_m_guess,
1425+
BQguess,
1426+
TRguess,
1427+
Yguess,
1428+
factor_ss,
1429+
) = (
1430+
ss_solutions["b_sp1"],
1431+
ss_solutions["n"],
1432+
float(ss_solutions["r_p"]),
1433+
float(ss_solutions["r"]),
1434+
float(ss_solutions["w"]),
1435+
ss_solutions["p_m"],
1436+
ss_solutions["BQ"],
1437+
float(ss_solutions["TR"]),
1438+
float(ss_solutions["Y"]),
1439+
ss_solutions["factor"],
1440+
)
1441+
# set new attribute of parameters for SS theta
1442+
p.SS_theta = ss_solutions["theta"]
1443+
use_new_guesses = False
1444+
if p.baseline_spending:
1445+
TR_baseline = TRguess
1446+
Ig_baseline = ss_solutions["I_g"]
14551447
else:
1456-
logging.warning(
1457-
"Dimensions of previous solutions for SS do not match"
1458-
)
1459-
use_new_guesses = True
1460-
except KeyError:
1448+
TR_baseline = None
1449+
Ig_baseline = None
1450+
BQ_items = [BQguess] if p.use_zeta else list(BQguess)
1451+
guesses = (
1452+
[r_p_guess, rguess, wguess]
1453+
+ list(p_m_guess)
1454+
+ [Yguess]
1455+
+ BQ_items
1456+
+ [TRguess]
1457+
)
1458+
# Now solve for the steady state of the reform
1459+
ss_params = (
1460+
b_guess,
1461+
n_guess,
1462+
TR_baseline,
1463+
Ig_baseline,
1464+
factor_ss,
1465+
p,
1466+
client,
1467+
)
1468+
1469+
# Solve for steady state using root finder
1470+
sol = opt.root(
1471+
SS_fsolve,
1472+
guesses,
1473+
args=ss_params,
1474+
method=p.SS_root_method,
1475+
tol=p.mindist_SS,
1476+
)
1477+
else:
14611478
logging.warning(
1462-
"KeyError: previous solutions for SS not found"
1479+
"Dimensions of previous solutions for SS do not match"
14631480
)
14641481
use_new_guesses = True
1465-
if (
1466-
p.baseline
1467-
or p.reform_use_baseline_solution is False
1468-
or use_new_guesses
1469-
):
1482+
except KeyError:
1483+
logging.warning("KeyError: previous solutions for SS not found")
1484+
use_new_guesses = True
1485+
if p.baseline or not p.reform_use_baseline_solution or use_new_guesses:
14701486
# Loop over initial guesses of r and TR until find a solution or until have
14711487
# gone through all guesses. This should usually solve in the first guess
14721488
SS_solved = False
@@ -1495,6 +1511,8 @@ def run_SS(p, client=None):
14951511
factor_ss = ss_solutions[
14961512
"factor"
14971513
] # don't guess factor, use baseline
1514+
# set new attribute of parameters for SS theta
1515+
p.SS_theta = ss_solutions["theta"]
14981516
if p.baseline_spending:
14991517
TR_baseline = ss_solutions["TR"]
15001518
Ig_baseline = ss_solutions["I_g"]

ogcore/TPI.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -744,9 +744,25 @@ def run_TPI(p, client=None):
744744
euler_errors = np.zeros((p.T, 2 * p.S, p.J))
745745
TPIdist_vec = np.zeros(p.maxiter)
746746

747-
# scatter parameters to workers
748-
if client:
749-
scattered_p_future = client.scatter(p, broadcast=True)
747+
# Before scattering, temporarily remove unpicklable schema objects
748+
schema_backup = {}
749+
for attr in ["_defaults_schema", "_validator_schema", "sel"]:
750+
if hasattr(p, attr):
751+
schema_backup[attr] = getattr(p, attr)
752+
try:
753+
delattr(p, attr)
754+
except:
755+
pass
756+
757+
# Scatter the parameters
758+
scattered_p_future = client.scatter(p, broadcast=True)
759+
760+
# Restore the schema objects (they're not needed by workers anyway)
761+
for attr, value in schema_backup.items():
762+
try:
763+
setattr(p, attr, value)
764+
except:
765+
pass
750766

751767
# TPI loop
752768
while (TPIiter < p.maxiter) and (TPIdist >= p.mindist_TPI):

ogcore/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@
2020
from ogcore.txfunc import *
2121
from ogcore.utils import *
2222

23-
__version__ = "0.14.14"
23+
__version__ = "0.15.0"

ogcore/default_parameters.json

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,9 @@
333333
"value": false
334334
}
335335
],
336-
"validators": {}
336+
"validators": {
337+
"choice": {"choices": [true, false]}
338+
}
337339
},
338340
"beta_annual": {
339341
"title": "Annual rate of time preference for households",
@@ -3686,7 +3688,9 @@
36863688
"value": false
36873689
}
36883690
],
3689-
"validators": {}
3691+
"validators": {
3692+
"choice": {"choices": [true, false]}
3693+
}
36903694
},
36913695
"constant_rates": {
36923696
"title": "Flag to use linear tax functions",
@@ -3700,7 +3704,9 @@
37003704
"value": false
37013705
}
37023706
],
3703-
"validators": {}
3707+
"validators": {
3708+
"choice": {"choices": [true, false]}
3709+
}
37043710
},
37053711
"zero_taxes": {
37063712
"title": "Flag to run model without any individual income taxes",
@@ -3714,7 +3720,9 @@
37143720
"value": false
37153721
}
37163722
],
3717-
"validators": {}
3723+
"validators": {
3724+
"choice": {"choices": [true, false]}
3725+
}
37183726
},
37193727
"analytical_mtrs": {
37203728
"title": "Flag to use analytically derived marginal tax rates in tax functions",
@@ -3728,7 +3736,9 @@
37283736
"value": false
37293737
}
37303738
],
3731-
"validators": {}
3739+
"validators": {
3740+
"choice": {"choices": [true, false]}
3741+
}
37323742
},
37333743
"age_specific": {
37343744
"title": "Flag to use analytically derived marginal tax rates in tax functions",
@@ -3742,7 +3752,9 @@
37423752
"value": true
37433753
}
37443754
],
3745-
"validators": {}
3755+
"validators": {
3756+
"choice": {"choices": [true, false]}
3757+
}
37463758
},
37473759
"retirement_age": {
37483760
"title": "Age at which agents can collect Social Security benefits",
@@ -4119,6 +4131,22 @@
41194131
}
41204132
}
41214133
},
4134+
"baseline_theta": {
4135+
"title": "Flag for use in reform simulations to keep Social Security system replacement rate constant between baseline and reform runs",
4136+
"description": "Flag for use in reform simulations to keep Social Security system replacement rate constant between baseline and reform runs.",
4137+
"section_1": "Fiscal Policy Parameters",
4138+
"section_2": "Government Pension Parameters",
4139+
"notes": "",
4140+
"type": "bool",
4141+
"value": [
4142+
{
4143+
"value": false
4144+
}
4145+
],
4146+
"validators": {
4147+
"choice": {"choices": [true, false]}
4148+
}
4149+
},
41224150
"start_year": {
41234151
"title": "Calendar year in which to start model analysis",
41244152
"description": "Calendar year in which to start model analysis.",
@@ -4395,7 +4423,9 @@
43954423
"value": true
43964424
}
43974425
],
4398-
"validators": {}
4426+
"validators": {
4427+
"choice": {"choices": [true, false]}
4428+
}
43994429
},
44004430
"initial_guess_r_SS": {
44014431
"title": "Initial guess of r for the SS solution",

0 commit comments

Comments
 (0)