Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions test/test_legacy_violations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,15 @@ def xs(self, x: Variable) -> Variable:
# x.shift(time=1) → absent at time=0, present elsewhere.
return x.shift(time=1)

@pytest.fixture
def ab_all_masked(self, m: Model) -> tuple[Variable, Variable, xr.DataArray]:
"""Two present variables over a shared dim plus an all-False mask."""
t = pd.Index(range(3), name="time")
a = m.add_variables(name="a", coords=[t])
b = m.add_variables(name="b", coords=[t])
mask = xr.DataArray(False, coords=[t])
return a, b, mask

@pytest.mark.v1
def test_to_linexpr_marks_absent_with_nan_const(self, xs: Variable) -> None:
"""
Expand Down Expand Up @@ -1261,6 +1270,52 @@ def test_legacy_quadratic_collapses_absent(
assert not bool(quad.isnull().values[0])
assert not np.isnan(quad.coeffs.values[0]).any()

# --- masked-addend absence (a fully masked term in a sum) -----------
# Moved from test_linear_expression.py to co-locate §6 coverage.

@pytest.mark.v1
def test_masked_addend_absorbs_sum(
self, ab_all_masked: tuple[Variable, Variable, xr.DataArray]
) -> None:
"""§6: a fully masked addend absorbs the whole sum — no live terms."""
a, b, mask = ab_all_masked
expr = a + (b * 1).where(mask)
assert expr.variable_names == set()
assert expr.isnull().all()

@pytest.mark.v1
def test_simplify_absent_expression_has_no_terms(
self, ab_all_masked: tuple[Variable, Variable, xr.DataArray]
) -> None:
"""§6: absence propagates, so simplify() leaves nothing to keep."""
a, b, mask = ab_all_masked
expr = (a + b.where(mask)).simplify()
assert expr.nterm == 0
assert expr.isnull().all()

@pytest.mark.legacy
def test_legacy_masked_addend_keeps_dead_terms(
self, ab_all_masked: tuple[Variable, Variable, xr.DataArray]
) -> None:
"""Legacy: the masked addend's terms turn dead; the live one remains."""
a, b, mask = ab_all_masked
expr = a + (b * 1).where(mask)
assert expr.nterm == 2
assert expr.variable_names == {"a"}

expr = (b * 1).where(mask)
assert expr.nterm == 1
assert expr.variable_names == set()

@pytest.mark.legacy
def test_legacy_simplify_drops_masked_addend(
self, ab_all_masked: tuple[Variable, Variable, xr.DataArray]
) -> None:
"""Legacy: simplify() drops the masked addend's dead terms."""
a, b, mask = ab_all_masked
expr = (a + b.where(mask)).simplify()
assert expr.nterm == 1


class TestFillnaResolves:
"""§7 — fillna()/.where() are how the caller resolves an absent slot."""
Expand Down
69 changes: 4 additions & 65 deletions test/test_linear_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -1906,7 +1906,7 @@ def test_linear_expression_groupby_with_dataframe(v: Variable) -> None:
groups = pd.DataFrame(
{"a": [1] * 10 + [2] * 10, "b": list(range(4)) * 5}, index=v.indexes["dim_2"]
)
with pytest.raises(ValueError):
with pytest.raises(ValueError, match="only supported for"):
expr.groupby(groups).sum(use_fallback=True)
with pytest.warns(LinopySemanticsWarning, match=r"stacked `group` MultiIndex"):
grouped = expr.groupby(groups).sum()
Expand All @@ -1924,7 +1924,7 @@ def test_linear_expression_groupby_with_dataframe_v1(v: Variable) -> None:
groups = pd.DataFrame(
{"a": [1] * 10 + [2] * 10, "b": list(range(4)) * 5}, index=v.indexes["dim_2"]
)
with pytest.raises(ValueError):
with pytest.raises(ValueError, match="only supported for"):
expr.groupby(groups).sum(use_fallback=True)
grouped = expr.groupby(groups).sum()
assert not isinstance(grouped.indexes["group"], pd.MultiIndex)
Expand All @@ -1943,7 +1943,7 @@ def test_linear_expression_groupby_with_dataframe_with_same_group_name(
{"dim_2": [1] * 10 + [2] * 10, "b": list(range(4)) * 5},
index=v.indexes["dim_2"],
)
with pytest.raises(ValueError):
with pytest.raises(ValueError, match="only supported for"):
expr.groupby(groups).sum(use_fallback=True)
with pytest.warns(LinopySemanticsWarning, match=r"stacked `group` MultiIndex"):
grouped = expr.groupby(groups).sum()
Expand Down Expand Up @@ -1974,7 +1974,7 @@ def test_linear_expression_groupby_with_dataframe_on_multiindex(u: Variable) ->
expr = 1 * u
n = len(u.data["dim_3"])
groups = pd.DataFrame({"a": [1] * n}, index=u.indexes["dim_3"])
with pytest.raises(ValueError):
with pytest.raises(ValueError, match="only supported for"):
expr.groupby(groups).sum(use_fallback=True)
with pytest.warns(LinopySemanticsWarning, match=r"stacked `group` MultiIndex"):
grouped = expr.groupby(groups).sum()
Expand Down Expand Up @@ -2869,39 +2869,6 @@ def test_variable_names() -> None:
assert expr.variable_names == {"a"}


@pytest.mark.legacy
def test_variable_names_masked_addend_legacy() -> None:
# Legacy: a fully masked addend's terms turn dead; the live variable remains.
m = Model()
time = pd.Index(range(3), name="time")
a = m.add_variables(name="a", coords=[time])
b = m.add_variables(name="b", coords=[time])
mask = xr.DataArray(False, coords=[time])

expr = a + (b * 1).where(mask)
assert expr.nterm == 2
assert expr.variable_names == {"a"}

expr = (b * 1).where(mask)
assert expr.nterm == 1
assert expr.variable_names == set()


@pytest.mark.v1
def test_variable_names_masked_addend_v1() -> None:
# v1 (section 6): absence propagates — a sum with a fully masked addend
# has no live terms anywhere.
m = Model()
time = pd.Index(range(3), name="time")
a = m.add_variables(name="a", coords=[time])
b = m.add_variables(name="b", coords=[time])
mask = xr.DataArray(False, coords=[time])

expr = a + (b * 1).where(mask)
assert expr.variable_names == set()
assert expr.isnull().all()


def test_nterm() -> None:
m = Model()
time = pd.Index(range(3), name="time")
Expand All @@ -2921,34 +2888,6 @@ def test_nterm() -> None:
assert expr.nterm == 2


@pytest.mark.legacy
def test_nterm_simplify_collapses_fully_masked_addend() -> None:
# Legacy: a fully masked addend contributes dead terms that simplify() drops.
m = Model()
time = pd.Index(range(3), name="time")
all_false = xr.DataArray(False, coords=[time])
a = m.add_variables(name="a", coords=[time])
b = m.add_variables(name="b", coords=[time])

expr = (a + b.where(all_false)).simplify()
assert expr.nterm == 1


@pytest.mark.v1
def test_nterm_simplify_absent_expression_has_no_terms() -> None:
# v1 (section 6): absence propagates — a fully masked addend absorbs the
# whole sum, so nothing is left to simplify.
m = Model()
time = pd.Index(range(3), name="time")
all_false = xr.DataArray(False, coords=[time])
a = m.add_variables(name="a", coords=[time])
b = m.add_variables(name="b", coords=[time])

expr = (a + b.where(all_false)).simplify()
assert expr.nterm == 0
assert expr.isnull().all()


class TestJoinParameter:
@pytest.fixture
def m2(self) -> Model:
Expand Down