Skip to content

Commit 6e95fc9

Browse files
authored
Merge pull request #690 from aeshef/maint/hypotheses-constraint-tests
TEST: Add constraint parsing tests for quadratic_form_test
2 parents b72f5e3 + cefd96e commit 6e95fc9

File tree

2 files changed

+101
-2
lines changed

2 files changed

+101
-2
lines changed

linearmodels/shared/hypotheses.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,12 @@ def _parse_single(constraint: str) -> tuple[str, float]:
196196
except Exception as exc:
197197
raise TypeError(_constraint_error.format(cons=constraint)) from exc
198198
expr = "=".join(parts[:-1])
199-
return expr, value
199+
return expr.strip(), value
200200

201201

202202
def _reparse_constraint_formula(
203203
formula: str | list[str] | dict[str, float],
204204
) -> str | dict[str, float]:
205-
# TODO: Test against variable names constaining , or =
206205
if isinstance(formula, Mapping):
207206
return dict(formula)
208207
if isinstance(formula, str):
@@ -227,6 +226,11 @@ def quadratic_form_test(
227226
if formula is not None and restriction is not None:
228227
raise ValueError("restriction and formula cannot be used simultaneously.")
229228
if formula is not None:
229+
if not isinstance(params, Series):
230+
raise TypeError(
231+
"params must be a pandas Series when using formula= to specify "
232+
"linear restrictions (indexed by parameter names)."
233+
)
230234
assert isinstance(params, Series)
231235
param_names = [str(p) for p in params.index]
232236
rewritten_constraints = _reparse_constraint_formula(formula)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import numpy as np
2+
import pandas as pd
3+
import pytest
4+
5+
from linearmodels.shared.hypotheses import (
6+
_parse_single,
7+
_reparse_constraint_formula,
8+
quadratic_form_test,
9+
)
10+
11+
12+
def test_parse_single_simple():
13+
expr, val = _parse_single("x1 = 1")
14+
assert expr == "x1"
15+
assert val == 1.0
16+
17+
18+
def test_parse_single_expression_with_plus():
19+
expr, val = _parse_single("x1 + x2 = 2.5")
20+
assert expr == "x1 + x2"
21+
assert val == 2.5
22+
23+
24+
def test_parse_single_multiple_equals():
25+
expr, val = _parse_single("a = b = 1")
26+
assert expr == "a = b"
27+
assert val == 1.0
28+
29+
30+
def test_parse_single_no_equals_raises():
31+
with pytest.raises(ValueError, match="required syntax"):
32+
_parse_single("x1")
33+
34+
35+
def test_parse_single_non_float_rhs_raises():
36+
with pytest.raises(TypeError, match="required syntax"):
37+
_parse_single("x1 = not_a_number")
38+
39+
40+
def test_reparse_dict_passthrough():
41+
spec = {"x1": 0.0, "x2": 1.0}
42+
out = _reparse_constraint_formula(spec)
43+
assert out == spec
44+
45+
46+
def test_reparse_single_constraint_string_unchanged():
47+
s = "x1 + x2 = 1"
48+
assert _reparse_constraint_formula(s) is s
49+
50+
51+
def test_reparse_multiple_equals_without_comma():
52+
out = _reparse_constraint_formula("x1 = x2 = 0")
53+
assert out == {"x1": 0.0, "x2": 0.0}
54+
55+
56+
def test_reparse_comma_separated():
57+
out = _reparse_constraint_formula("x1 = 1, x2 = 2")
58+
assert out == {"x1": 1.0, "x2": 2.0}
59+
60+
61+
def test_reparse_list_of_strings():
62+
out = _reparse_constraint_formula(["x1 = 1", "x2 = 2"])
63+
assert out == {"x1": 1.0, "x2": 2.0}
64+
65+
66+
def test_reparse_comma_with_multiple_equals():
67+
out = _reparse_constraint_formula("a=b=1,c=2")
68+
assert out == {"a=b": 1.0, "c": 2.0}
69+
70+
71+
def test_quadratic_form_formula_end_to_end():
72+
params = pd.Series([0.0, 1.0], index=["x0", "x1"])
73+
cov = np.eye(2)
74+
res = quadratic_form_test(params, cov, formula="x0=0")
75+
assert res.stat == 0.0
76+
assert res.df == 1
77+
78+
79+
def test_quadratic_form_formula_and_restriction_exclusive():
80+
params = pd.Series([0.0, 0.0], index=["a", "b"])
81+
cov = np.eye(2)
82+
r = np.array([[1.0, 0.0]])
83+
with pytest.raises(ValueError, match="cannot be used simultaneously"):
84+
quadratic_form_test(params, cov, restriction=r, formula="a=0")
85+
86+
87+
def test_quadratic_form_formula_requires_series_params():
88+
with pytest.raises(TypeError, match="pandas Series"):
89+
quadratic_form_test(np.array([0.0, 1.0]), np.eye(2), formula="x0=0")
90+
91+
92+
def test_reparse_name_with_comma_single_equals():
93+
# One "=" in the string: pass through to formulaic as a single constraint.
94+
out = _reparse_constraint_formula("my,var = 1")
95+
assert out == "my,var = 1"

0 commit comments

Comments
 (0)