Skip to content

Commit 0b1b6a9

Browse files
authored
Merge pull request PSLmodels#1149 from SeaCelo/fix/J-group-vector-length
Validate beta_annual and chi_b length against the number of income groups
2 parents daeb53b + 361b60a commit 0b1b6a9

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

ogcore/parameters.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,28 @@ def compute_default_params(self):
8787
self.T = int(self.T)
8888
self.J = len(self.lambdas)
8989

90+
# beta_annual and chi_b carry one value per lifetime-income group, but
91+
# paramtools validates values and nesting depth, not list length, so a
92+
# wrong-length vector used to flow through silently: a short one
93+
# crashed much later (IndexError deep in the SS solve, far from the
94+
# cause) and a long one was silently truncated to the first J entries
95+
# -- wrong economics with no symptom. Guard here, where J is first
96+
# known: one value broadcasts to all J groups, J values pass through,
97+
# a uniform vector of any other length is reshaped losslessly
98+
# (smaller-J runs that keep the uniform defaults rely on this), and a
99+
# non-uniform mismatch raises immediately.
100+
for param in ("beta_annual", "chi_b"):
101+
val = np.asarray(getattr(self, param)).ravel()
102+
if val.shape[0] != self.J:
103+
if val.shape[0] == 1 or np.all(val == val[0]):
104+
setattr(self, param, np.full(self.J, val[0]))
105+
else:
106+
raise ValueError(
107+
f"{param} must have either 1 value (applied to all "
108+
f"J groups) or J={self.J} values; got "
109+
f"{val.shape[0]} non-uniform values."
110+
)
111+
90112
# get parameters of elliptical utility function
91113
self.b_ellipse, self.upsilon = elliptical_u_est.estimation(
92114
self.frisch, self.ltilde

tests/test_parameters.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,28 @@ def test_expand_taxfunc_params():
194194
assert len(specs.etr_params) == specs.T + specs.S
195195
assert len(specs.etr_params[0]) == specs.S
196196
assert specs.etr_params[0][0][0] == 0.35
197+
198+
199+
def test_J_dimensioned_length_guard():
200+
"""beta_annual / chi_b must have 1 or J values; see issue #1146.
201+
202+
Wrong-length vectors used to flow through silently: a short one crashed
203+
much later (IndexError deep in the SS solve) and a long one was silently
204+
truncated to the first J entries.
205+
"""
206+
# one value broadcasts to all J groups
207+
specs = Specifications()
208+
specs.update_specifications({"beta_annual": [0.95], "chi_b": [50.0]})
209+
assert specs.beta.shape == (specs.J,)
210+
assert specs.chi_b.shape == (specs.J,)
211+
assert np.allclose(specs.chi_b, 50.0)
212+
213+
# a uniform vector of the wrong length is reshaped losslessly
214+
specs2 = Specifications()
215+
specs2.update_specifications({"beta_annual": [0.96, 0.96, 0.96]})
216+
assert specs2.beta.shape == (specs2.J,)
217+
218+
# a non-uniform vector of the wrong length raises immediately
219+
specs3 = Specifications()
220+
with pytest.raises(ValueError, match="beta_annual"):
221+
specs3.update_specifications({"beta_annual": [0.94, 0.95, 0.96]})

0 commit comments

Comments
 (0)