Skip to content

Commit e35cfd5

Browse files
authored
Merge pull request PSLmodels#1029 from jdebacker/taxfuncs
Merging
2 parents b753779 + c58f3a0 commit e35cfd5

10 files changed

Lines changed: 197 additions & 169 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ 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

88

9+
## [0.14.3] - 2025-04-25 10:00:00
10+
11+
### Added
12+
13+
- Puts a ceiling on the version of the `marshmallow<4.0.0` package in `environment.yml`
14+
- Update `txfunc.py` for the estimation of the `HSV` and `GS` tax functions.
15+
- Update `test_txfunc.py`, `tax_func_estimate_outputs.pkl`, and `tax_func_loop_outputs.pkl` files for testing
16+
- Update `dask` and `distributed` client calls in `SS.py` and `TPI.py` to allow for updated versions.
17+
918
## [0.14.2] - 2025-04-04 12:00:00
1019

1120
### Added
@@ -373,6 +382,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
373382
- Version [0.7.0] on August 30, 2021 was the first time that the OG-USA repository was detached from all of the core model logic, which was named OG-Core. Before this version, OG-USA was part of what is now the [`OG-Core`](https://github.com/PSLmodels/OG-Core) repository. In the next version of OG-USA, we adjusted the version numbering to begin with 0.1.0. This initial version of 0.7.0, was sequential from what OG-USA used to be when the OG-Core project was called OG-USA.
374383
- 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.
375384

385+
386+
[0.14.3]: https://github.com/PSLmodels/OG-Core/compare/v0.14.2...v0.14.3
376387
[0.14.2]: https://github.com/PSLmodels/OG-Core/compare/v0.14.1...v0.14.2
377388
[0.14.1]: https://github.com/PSLmodels/OG-Core/compare/v0.14.0...v0.14.1
378389
[0.14.0]: https://github.com/PSLmodels/OG-Core/compare/v0.13.2...v0.14.0

environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies:
1515
- distributed>=2.30.1
1616
- paramtools>=0.15.0
1717
- sphinx>=3.5.4
18+
- marshmallow<4.0.0
1819
- sphinx-argparse
1920
- sphinxcontrib-bibtex>=2.0.0
2021
- sphinx-math-dollar

ogcore/SS.py

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,6 @@
3434
level=log_level, format="%(message)s" # Only show the message itself
3535
)
3636

37-
"""
38-
A global future for the Parameters object for client workers.
39-
This is scattered once and place at module scope, then used
40-
by the client in the inner loop.
41-
"""
42-
scattered_p = None
4337

4438
"""
4539
------------------------------------------------------------------------
@@ -223,12 +217,6 @@ def inner_loop(outer_loop_vars, p, client):
223217
units
224218
225219
"""
226-
# Retrieve the "scattered" Parameters object.
227-
global scattered_p
228-
229-
if scattered_p is None:
230-
scattered_p = client.scatter(p, broadcast=True) if client else p
231-
232220
# unpack variables to pass to function
233221
bssmat, nssmat, r_p, r, w, p_m, Y, BQ, TR, Ig_baseline, factor = (
234222
outer_loop_vars
@@ -247,35 +235,71 @@ def inner_loop(outer_loop_vars, p, client):
247235
tr = household.get_tr(TR, None, p, "SS")
248236
ubi = p.ubi_nom_array[-1, :, :] / factor
249237

238+
scattered_p = client.scatter(p, broadcast=True) if client else p
239+
250240
lazy_values = []
251241
for j in range(p.J):
252242
guesses = np.append(bssmat[:, j], nssmat[:, j])
253-
euler_params = (
243+
244+
# Create a delayed function that will access the scattered_p
245+
@delayed
246+
def solve_for_j(
247+
guesses,
254248
r_p,
255249
w,
256250
p_tilde,
257-
bq[:, j],
258-
rm[:, j],
259-
tr[:, j],
260-
ubi[:, j],
251+
bq_j,
252+
rm_j,
253+
tr_j,
254+
ubi_j,
261255
factor,
262256
j,
263-
scattered_p,
264-
)
265-
lazy_values.append(
266-
delayed(opt.root)(
257+
scattered_p_future,
258+
):
259+
# This function will be executed on workers with access to scattered_p
260+
return opt.root(
267261
euler_equation_solver,
268262
guesses * 0.9,
269-
args=euler_params,
263+
args=(
264+
r_p,
265+
w,
266+
p_tilde,
267+
bq_j,
268+
rm_j,
269+
tr_j,
270+
ubi_j,
271+
factor,
272+
j,
273+
scattered_p_future,
274+
),
270275
method=p.FOC_root_method,
271276
tol=MINIMIZER_TOL,
272277
)
278+
279+
# Add the delayed computation to our list
280+
lazy_values.append(
281+
solve_for_j(
282+
guesses,
283+
r_p,
284+
w,
285+
p_tilde,
286+
bq[:, j],
287+
rm[:, j],
288+
tr[:, j],
289+
ubi[:, j],
290+
factor,
291+
j,
292+
scattered_p,
293+
)
273294
)
295+
274296
if client:
275-
futures = client.compute(lazy_values, num_workers=p.num_workers)
297+
# Compute all the values
298+
futures = client.compute(lazy_values)
299+
# Later, gather the results when needed
276300
results = client.gather(futures)
277301
else:
278-
results = results = compute(
302+
results = compute(
279303
*lazy_values,
280304
scheduler=dask.multiprocessing.get,
281305
num_workers=p.num_workers,
@@ -1197,11 +1221,6 @@ def run_SS(p, client=None):
11971221
results
11981222
11991223
"""
1200-
global scattered_p
1201-
if client:
1202-
scattered_p = client.scatter(p, broadcast=True)
1203-
else:
1204-
scattered_p = p
12051224

12061225
# Create list of deviation factors for initial guesses of r and TR
12071226
dev_factor_list = [

ogcore/TPI.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,6 @@
4646
level=log_level, format="%(message)s" # Only show the message itself
4747
)
4848

49-
"""
50-
A global future for the Parameters object for client workers.
51-
This is scattered once and place at module scope, then used
52-
by the client in the inner loop.
53-
"""
54-
scattered_p = None
55-
5649

5750
def get_initial_SS_values(p):
5851
"""
@@ -578,12 +571,6 @@ def run_TPI(p, client=None):
578571
results
579572
580573
"""
581-
global scattered_p
582-
if client:
583-
scattered_p = client.scatter(p, broadcast=True)
584-
else:
585-
scattered_p = p
586-
587574
# unpack tuples of parameters
588575
initial_values, ss_vars, theta, baseline_values = get_initial_SS_values(p)
589576
(B0, b_sinit, b_splus1init, factor, initial_b, initial_n) = initial_values
@@ -779,10 +766,15 @@ def run_TPI(p, client=None):
779766
).sum(axis=2)
780767
p_tilde = aggr.get_ptilde(p_i[:, :], p.tau_c[:, :], p.alpha_c, "TPI")
781768

769+
# scatter parameters to workers
770+
scattered_p = client.scatter(p, broadcast=True) if client else p
771+
782772
euler_errors = np.zeros((p.T, 2 * p.S, p.J))
783773
lazy_values = []
784774
for j in range(p.J):
785775
guesses = (guesses_b[:, :, j], guesses_n[:, :, j])
776+
777+
# Add the delayed computation to our list
786778
lazy_values.append(
787779
delayed(inner_loop)(
788780
guesses,
@@ -795,7 +787,9 @@ def run_TPI(p, client=None):
795787
)
796788
)
797789
if client:
798-
futures = client.compute(lazy_values, num_workers=p.num_workers)
790+
# Compute all the values
791+
futures = client.compute(lazy_values)
792+
# Later, gather the results when needed
799793
results = client.gather(futures)
800794
else:
801795
results = compute(

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.2"
23+
__version__ = "0.14.3"

ogcore/txfunc.py

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,6 @@ def get_tax_rates(
288288
elif (
289289
income.ndim == 2
290290
): # I think only calls here are for loops over S and J
291-
# for s in range(income.shape[0]):
292-
# for j in range(income.shape[1]):
293-
# txrates[s, j] = params[s][j][0](income[s, j])
294291
txrates = [
295292
[
296293
params[s][j][0](income[s, j])
@@ -299,12 +296,6 @@ def get_tax_rates(
299296
for s in range(income.shape[0])
300297
]
301298
else: # to catch 3D arrays, looping over T, S, J
302-
# for t in range(income.shape[0]):
303-
# for s in range(income.shape[1]):
304-
# for j in range(income.shape[2]):
305-
# txrates[t, s, j] = params[t][s][j][0](
306-
# income[t, s, j]
307-
# )
308299
txrates = [
309300
[
310301
[
@@ -596,6 +587,8 @@ def txfunc_est(
596587
output_dir (str): output directory for saving plot files
597588
graph (bool): whether to plot the estimated functions compared
598589
to the data
590+
params_init (Numpy array): initial values for the parameters
591+
global_opt (bool): whether to use global optimization method
599592
600593
Returns:
601594
(tuple): tax function estimation output:
@@ -678,15 +671,6 @@ def txfunc_est(
678671
)
679672
lb_max_x = np.maximum(min_x, 0.0) + 1e-4
680673
lb_max_y = np.maximum(min_y, 0.0) + 1e-4
681-
# bnds = (
682-
# (1e-12, None),
683-
# (1e-12, None),
684-
# (1e-12, None),
685-
# (1e-12, None),
686-
# (lb_max_x, MAX_ETR + 0.15),
687-
# (lb_max_y, MAX_ETR + 0.15),
688-
# (0, 1),
689-
# )
690674
bnds = (
691675
(1e-12, 9999),
692676
(1e-12, 9999),
@@ -802,9 +786,9 @@ def txfunc_est(
802786
# Need to use a different functional form than for DEP function.
803787
# '''
804788
if params_init is None:
805-
phi0_init = 1.0
806-
phi1_init = 1.0
807-
phi2_init = 1.0
789+
phi0_init = 0.3
790+
phi1_init = 0.3
791+
phi2_init = 0.01
808792
params_init = np.array([phi0_init, phi1_init, phi2_init])
809793
tx_objs = (
810794
np.array([None]),
@@ -815,8 +799,7 @@ def txfunc_est(
815799
tax_func_type,
816800
rate_type,
817801
)
818-
# bnds = ((1e-12, None), (1e-12, None), (1e-12, None))
819-
bnds = ((1e-12, 9999), (1e-12, 9999), (1e-12, 9999))
802+
bnds = ((1e-12, 1.0), (1e-12, 1.0), (1e-12, 1.0))
820803
if global_opt:
821804
params_til = opt.differential_evolution(
822805
wsumsq, bounds=bnds, args=(tx_objs), seed=1
@@ -839,15 +822,16 @@ def txfunc_est(
839822
# set initial values to parameter estimates
840823
params_init = np.array([phi0til, phi1til, phi2til])
841824
elif tax_func_type == "HSV":
842-
# '''
843-
# Estimate Heathcote, Storesletten, Violante (2017) parameters via
844-
# OLS
845-
# '''
825+
"""
826+
Estimate Heathcote, Storesletten, Violante (2017) parameters via
827+
OLS
828+
"""
846829
constant = np.ones_like(income)
847830
ln_income = np.log(income)
848831
X_mat = np.column_stack((constant, ln_income))
849832
Y_vec = np.log(1 - txrates)
850-
param_est = np.linalg.inv(X_mat.T @ X_mat) @ X_mat.T @ Y_vec
833+
W = np.diag(wgts)
834+
param_est = np.linalg.inv(X_mat.T @ W @ X_mat) @ X_mat.T @ W @ Y_vec
851835
params = np.zeros(numparams)
852836
if rate_type == "etr":
853837
ln_lambda_s_hat, minus_tau_s_hat = param_est
@@ -858,16 +842,15 @@ def txfunc_est(
858842
params[:2] = np.array([lambda_s_hat, -minus_tau_s_hat])
859843
# Calculate the WSSE
860844
Y_hat = X_mat @ params
861-
# wsse = ((Y_vec - Y_hat) ** 2 * wgts).sum()
862-
wsse = ((Y_vec - Y_hat) ** 2).sum()
845+
wsse = ((Y_vec - Y_hat) ** 2 * wgts).sum()
863846
obs = df.shape[0]
864847
params_to_plot = params
865848
elif tax_func_type == "linear":
866-
# '''
867-
# For linear rates, just take the mean ETR or MTR by age-year.
868-
# Can use DEP form and set all parameters except for the shift
869-
# parameter to zero.
870-
# '''
849+
"""
850+
For linear rates, just take the mean ETR or MTR by age-year.
851+
Can use DEP form and set all parameters except for the shift
852+
parameter to zero.
853+
"""
871854
params = np.zeros(numparams)
872855
wsse = 0.0
873856
obs = df.shape[0]
@@ -1704,7 +1687,7 @@ def tax_func_estimate(
17041687
)
17051688
)
17061689
if client:
1707-
futures = client.compute(lazy_values, num_workers=num_workers)
1690+
futures = client.compute(lazy_values)
17081691
results = client.gather(futures)
17091692
else:
17101693
results = results = compute(

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="ogcore",
8-
version="0.14.2",
8+
version="0.14.3",
99
author="Jason DeBacker and Richard W. Evans",
1010
license="CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
1111
description="A general equilibrium overlapping generations model for fiscal policy analysis",
-172 Bytes
Binary file not shown.
-15.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)