Skip to content

Commit 2f53f0c

Browse files
committed
Add more tests
1 parent 0a70e60 commit 2f53f0c

1 file changed

Lines changed: 213 additions & 0 deletions

File tree

test/test_quadratic_constraint.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,3 +1644,216 @@ def test_quadratic_expression_rolling_centered(self) -> None:
16441644
qexpr = x * x
16451645
result = qexpr.rolling(dim_0=3, center=True).sum()
16461646
assert result is not None
1647+
1648+
1649+
class TestPrintSingleQuadraticConstraint:
1650+
"""Tests for print_single_quadratic_constraint function."""
1651+
1652+
def test_print_single_quadratic_constraint_basic(self) -> None:
1653+
"""Test printing a single quadratic constraint."""
1654+
from linopy.common import print_single_quadratic_constraint
1655+
1656+
m = Model()
1657+
x = m.add_variables(lower=0, name="x")
1658+
y = m.add_variables(lower=0, name="y")
1659+
m.add_quadratic_constraints(x * x + 2 * y, "<=", 10, name="qc")
1660+
1661+
label = int(m.quadratic_constraints["qc"].labels.values)
1662+
result = print_single_quadratic_constraint(m, label)
1663+
1664+
assert "qc" in result
1665+
assert "≤" in result or "<=" in result
1666+
assert "10" in result
1667+
1668+
def test_print_single_quadratic_constraint_multidim(self) -> None:
1669+
"""Test printing a single quadratic constraint from multidimensional."""
1670+
from linopy.common import print_single_quadratic_constraint
1671+
1672+
m = Model()
1673+
x = m.add_variables(lower=0, coords=[range(3)], name="x")
1674+
m.add_quadratic_constraints(x * x, "<=", 10, name="qc")
1675+
1676+
# Get first label
1677+
labels = m.quadratic_constraints["qc"].labels.values.ravel()
1678+
first_label = int(labels[labels != -1][0])
1679+
result = print_single_quadratic_constraint(m, first_label)
1680+
1681+
assert "qc" in result
1682+
1683+
1684+
class TestQuadraticConstraintIOCoverage:
1685+
"""Tests for IO coverage of quadratic constraints."""
1686+
1687+
def test_lp_file_with_progress(self) -> None:
1688+
"""Test LP file writing with progress=True."""
1689+
m = Model()
1690+
x = m.add_variables(lower=0, name="x")
1691+
m.add_quadratic_constraints(x * x, "<=", 10, name="qc")
1692+
m.add_objective(x)
1693+
1694+
with tempfile.NamedTemporaryFile(suffix=".lp", delete=False) as f:
1695+
# Write with progress (tests the progress branch)
1696+
m.to_file(f.name, progress=True)
1697+
content = Path(f.name).read_text()
1698+
1699+
assert "qc" in content.lower() or "QCMATRIX" in content
1700+
1701+
def test_lp_file_only_quadratic_constraints(self) -> None:
1702+
"""Test LP file with only quadratic constraints (no linear)."""
1703+
m = Model()
1704+
x = m.add_variables(lower=0, name="x")
1705+
# Only quadratic constraint, no linear constraints
1706+
m.add_quadratic_constraints(x * x, "<=", 10, name="qc")
1707+
m.add_objective(x)
1708+
1709+
with tempfile.NamedTemporaryFile(suffix=".lp", delete=False) as f:
1710+
m.to_file(f.name)
1711+
content = Path(f.name).read_text()
1712+
1713+
# Should have "s.t." section header
1714+
assert "s.t." in content or "Subject" in content
1715+
1716+
def test_lp_file_explicit_coordinate_names(self) -> None:
1717+
"""Test LP file with explicit coordinate names."""
1718+
m = Model()
1719+
x = m.add_variables(lower=0, coords=[range(2)], name="x")
1720+
m.add_quadratic_constraints(x * x, "<=", 10, name="qc")
1721+
m.add_objective(x.sum())
1722+
1723+
with tempfile.NamedTemporaryFile(suffix=".lp", delete=False) as f:
1724+
m.to_file(f.name, explicit_coordinate_names=True)
1725+
content = Path(f.name).read_text()
1726+
1727+
assert "qc" in content.lower()
1728+
1729+
def test_netcdf_roundtrip_with_linear_terms(self) -> None:
1730+
"""Test netCDF roundtrip for QC with both quadratic and linear terms."""
1731+
m = Model()
1732+
x = m.add_variables(lower=0, name="x")
1733+
y = m.add_variables(lower=0, name="y")
1734+
# Mixed quadratic and linear terms
1735+
m.add_quadratic_constraints(x * x + 2 * x + 3 * y, "<=", 10, name="qc")
1736+
m.add_objective(x + y)
1737+
1738+
with tempfile.TemporaryDirectory() as tmpdir:
1739+
path = Path(tmpdir) / "model.nc"
1740+
m.to_netcdf(path)
1741+
1742+
m2 = linopy.read_netcdf(path)
1743+
1744+
assert len(m2.quadratic_constraints) == 1
1745+
assert "qc" in m2.quadratic_constraints
1746+
1747+
1748+
class TestQuadraticConstraintToPolars:
1749+
"""Tests for to_polars method coverage."""
1750+
1751+
def test_to_polars_with_linear_terms(self) -> None:
1752+
"""Test to_polars with mixed quadratic and linear terms."""
1753+
m = Model()
1754+
x = m.add_variables(lower=0, name="x")
1755+
y = m.add_variables(lower=0, name="y")
1756+
qc = m.add_quadratic_constraints(x * x + 2 * x + 3 * y, "<=", 10, name="qc")
1757+
1758+
df = qc.to_polars()
1759+
assert "coeffs" in df.columns
1760+
assert "is_quadratic" in df.columns
1761+
1762+
# Should have both quadratic and linear terms
1763+
n_quad = df.filter(pl.col("is_quadratic")).height
1764+
n_linear = df.filter(~pl.col("is_quadratic")).height
1765+
assert n_quad >= 1
1766+
assert n_linear >= 1
1767+
1768+
def test_to_polars_multidimensional(self) -> None:
1769+
"""Test to_polars with multidimensional constraint."""
1770+
m = Model()
1771+
x = m.add_variables(lower=0, coords=[range(3)], name="x")
1772+
qc = m.add_quadratic_constraints(x * x, "<=", 10, name="qc")
1773+
1774+
df = qc.to_polars()
1775+
assert df.height > 0
1776+
# Should have 3 unique labels (one per coordinate)
1777+
assert df["labels"].n_unique() == 3
1778+
1779+
1780+
class TestQuadraticConstraintFlat:
1781+
"""Tests for flat property coverage."""
1782+
1783+
def test_flat_multidimensional(self) -> None:
1784+
"""Test flat property with multidimensional constraints."""
1785+
m = Model()
1786+
x = m.add_variables(lower=0, coords=[range(2), range(3)], name="x")
1787+
m.add_quadratic_constraints(x * x, "<=", 10, name="qc")
1788+
1789+
flat = m.quadratic_constraints.flat
1790+
assert isinstance(flat, pd.DataFrame)
1791+
assert len(flat) > 0
1792+
1793+
def test_flat_multiple_constraints(self) -> None:
1794+
"""Test flat property with multiple constraints."""
1795+
m = Model()
1796+
x = m.add_variables(lower=0, name="x")
1797+
y = m.add_variables(lower=0, name="y")
1798+
m.add_quadratic_constraints(x * x, "<=", 10, name="qc1")
1799+
m.add_quadratic_constraints(y * y, "<=", 20, name="qc2")
1800+
1801+
flat = m.quadratic_constraints.flat
1802+
assert len(flat) >= 2
1803+
1804+
1805+
class TestQuadraticExpressionAdditional:
1806+
"""Additional tests for QuadraticExpression edge cases."""
1807+
1808+
def test_quadratic_expression_constant_only(self) -> None:
1809+
"""Test quadratic expression that simplifies to constant."""
1810+
m = Model()
1811+
x = m.add_variables(lower=0, name="x")
1812+
1813+
# Expression with only constant term (after cancellation)
1814+
qexpr = x * x - x * x + 5
1815+
# This should still be a QuadraticExpression but effectively constant
1816+
assert qexpr is not None
1817+
1818+
def test_quadratic_expression_to_constraint_ge(self) -> None:
1819+
"""Test QuadraticExpression.to_constraint with >= sign."""
1820+
from linopy.constraints import QuadraticConstraint
1821+
1822+
m = Model()
1823+
x = m.add_variables(lower=0, name="x")
1824+
1825+
qexpr = x * x
1826+
qc = qexpr.to_constraint(">=", 5)
1827+
assert isinstance(qc, QuadraticConstraint)
1828+
assert str(qc.sign.values) == ">="
1829+
1830+
def test_quadratic_expression_to_constraint_eq(self) -> None:
1831+
"""Test QuadraticExpression.to_constraint with = sign."""
1832+
from linopy.constraints import QuadraticConstraint
1833+
1834+
m = Model()
1835+
x = m.add_variables(lower=0, name="x")
1836+
1837+
qexpr = x * x
1838+
qc = qexpr.to_constraint("=", 5)
1839+
assert isinstance(qc, QuadraticConstraint)
1840+
assert str(qc.sign.values) == "="
1841+
1842+
def test_quadratic_expression_comparison_operators(self) -> None:
1843+
"""Test QuadraticExpression comparison operators."""
1844+
m = Model()
1845+
x = m.add_variables(lower=0, name="x")
1846+
1847+
qexpr = x * x
1848+
1849+
# Test <= operator
1850+
c1 = qexpr <= 10
1851+
assert c1 is not None
1852+
1853+
# Test >= operator
1854+
c2 = qexpr >= 1
1855+
assert c2 is not None
1856+
1857+
# Test == operator
1858+
c3 = qexpr == 5
1859+
assert c3 is not None

0 commit comments

Comments
 (0)