Squashed 'kiwi/' content from commit 268028e

git-subtree-dir: kiwi
git-subtree-split: 268028ee4a694dcd89e4b1e683bf2f9ac48c08d9
This commit is contained in:
2024-02-11 15:32:50 -06:00
commit 81396a5322
76 changed files with 13184 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2021, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import gc
import re
import pytest
from kiwisolver import Constraint, Variable, strength
@pytest.mark.parametrize("op", ("==", "<=", ">="))
def test_constraint_creation(op) -> None:
"""Test constraints creation and methods."""
v = Variable("foo")
c = Constraint(v + 1, op)
assert c.strength() == strength.required and c.op() == op
e = c.expression()
t = e.terms()
assert (
e.constant() == 1
and len(t) == 1
and t[0].variable() is v
and t[0].coefficient() == 1
)
constraint_format = r"1 \* foo \+ 1 %s 0 | strength = 1.001e\+[0]+9" % op
assert re.match(constraint_format, str(c))
for s in ("weak", "medium", "strong", "required"):
# Not an exact literal...
c = Constraint(v + 1, op, s) # type: ignore
assert c.strength() == getattr(strength, s)
# Ensure we test garbage collection.
del c
gc.collect()
def test_constraint_creation2() -> None:
"""Test for errors in Constraints creation."""
v = Variable("foo")
with pytest.raises(TypeError) as excinfo:
Constraint(1, "==") # type: ignore
assert "Expression" in excinfo.exconly()
with pytest.raises(TypeError) as excinfo:
Constraint(v + 1, 1) # type: ignore
assert "str" in excinfo.exconly()
with pytest.raises(ValueError) as excinfo2:
Constraint(v + 1, "!=") # type: ignore
assert "relational operator" in excinfo2.exconly()
@pytest.mark.parametrize("op", ("==", "<=", ">="))
def test_constraint_repr(op) -> None:
"""Test the repr method of a constraint object."""
v = Variable("foo")
c = Constraint(v + 1, op)
assert op in repr(c)
def test_constraint_or_operator() -> None:
"""Test modifying a constraint strength using the | operator."""
v = Variable("foo")
c = Constraint(v + 1, "==")
for s in ("weak", "medium", "strong", "required", strength.create(1, 1, 0)):
c2 = c | s # type: ignore
if isinstance(s, str):
assert c2.strength() == getattr(strength, s)
else:
assert c2.strength() == s
with pytest.raises(ValueError):
c | "unknown" # type: ignore

271
py/tests/test_expression.py Normal file
View File

@@ -0,0 +1,271 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2021, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import gc
import math
import operator
import sys
from typing import Tuple
import pytest
from kiwisolver import Constraint, Expression, Term, Variable, strength
def test_expression_creation() -> None:
"""Test the Term constructor."""
v = Variable("foo")
v2 = Variable("bar")
v3 = Variable("aux")
e1 = Expression((v * 1, v2 * 2, v3 * 3))
e2 = Expression((v * 1, v2 * 2, v3 * 3), 10)
for e, val in ((e1, 0), (e2, 10)):
t = e.terms()
assert (
len(t) == 3
and t[0].variable() is v
and t[0].coefficient() == 1
and t[1].variable() is v2
and t[1].coefficient() == 2
and t[2].variable() is v3
and t[2].coefficient() == 3
)
assert e.constant() == val
assert str(e2) == "1 * foo + 2 * bar + 3 * aux + 10"
with pytest.raises(TypeError) as excinfo:
Expression((1, v2 * 2, v3 * 3)) # type: ignore
assert "Term" in excinfo.exconly()
# ensure we test garbage collection.
del e2
gc.collect()
@pytest.fixture()
def expressions():
"""Build expressions, terms and variables to test operations."""
v = Variable("foo")
v2 = Variable("bar")
t = Term(v, 10)
t2 = Term(v2)
e = t + 5
e2 = v2 - 10
return e, e2, t, t2, v, v2
def test_expression_neg(
expressions: Tuple[Expression, Expression, Term, Term, Variable, Variable],
):
"""Test neg on an expression."""
e, _, _, _, v, _ = expressions
neg = -e
assert isinstance(neg, Expression)
neg_t = neg.terms()
assert (
len(neg_t) == 1
and neg_t[0].variable() is v
and neg_t[0].coefficient() == -10
and neg.constant() == -5
)
def test_expression_mul(
expressions: Tuple[Expression, Expression, Term, Term, Variable, Variable],
):
"""Test expresion multiplication."""
e, _, _, _, v, _ = expressions
for mul in (e * 2.0, 2.0 * e):
assert isinstance(mul, Expression)
mul_t = mul.terms()
assert (
len(mul_t) == 1
and mul_t[0].variable() is v
and mul_t[0].coefficient() == 20
and mul.constant() == 10
)
with pytest.raises(TypeError):
e * v # type: ignore
def test_expression_div(
expressions: Tuple[Expression, Expression, Term, Term, Variable, Variable],
):
"""Test expression divisions."""
e, _, _, _, v, v2 = expressions
div = e / 2
assert isinstance(div, Expression)
div_t = div.terms()
assert (
len(div_t) == 1
and div_t[0].variable() is v
and div_t[0].coefficient() == 5
and div.constant() == 2.5
)
with pytest.raises(TypeError):
e / v2 # type: ignore
with pytest.raises(ZeroDivisionError):
e / 0
def test_expression_addition(
expressions: Tuple[Expression, Expression, Term, Term, Variable, Variable],
):
"""Test expressions additions."""
e, e2, _, t2, v, v2 = expressions
for add in (e + 2, 2.0 + e):
assert isinstance(add, Expression)
assert add.constant() == 7
terms = add.terms()
assert (
len(terms) == 1
and terms[0].variable() is v
and terms[0].coefficient() == 10
)
add2 = e + v2
assert isinstance(add2, Expression)
assert add2.constant() == 5
terms = add2.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 10
and terms[1].variable() is v2
and terms[1].coefficient() == 1
)
add3 = e + t2
assert isinstance(add3, Expression)
assert add3.constant() == 5
terms = add3.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 10
and terms[1].variable() is v2
and terms[1].coefficient() == 1
)
add4 = e + e2
assert isinstance(add4, Expression)
assert add4.constant() == -5
terms = add4.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 10
and terms[1].variable() is v2
and terms[1].coefficient() == 1
)
def test_expressions_substraction(
expressions: Tuple[Expression, Expression, Term, Term, Variable, Variable],
):
"""Test expression substraction."""
e, e2, _, t2, v, v2 = expressions
for sub, diff in zip((e - 2, 2.0 - e), (3, -3)):
assert isinstance(sub, Expression)
assert sub.constant() == diff
terms = sub.terms()
assert (
len(terms) == 1
and terms[0].variable() is v
and terms[0].coefficient() == math.copysign(10, diff)
)
for sub2, diff in zip((e - v2, v2 - e), (5, -5)):
assert isinstance(sub2, Expression)
assert sub2.constant() == diff
terms = sub2.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == math.copysign(10, diff)
and terms[1].variable() is v2
and terms[1].coefficient() == -math.copysign(1, diff)
)
for sub3, diff in zip((e - t2, t2 - e), (5, -5)):
assert isinstance(sub3, Expression)
assert sub3.constant() == diff
terms = sub3.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == math.copysign(10, diff)
and terms[1].variable() is v2
and terms[1].coefficient() == -math.copysign(1, diff)
)
sub4 = e - e2
assert isinstance(sub3, Expression)
assert sub4.constant() == 15
terms = sub4.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 10
and terms[1].variable() is v2
and terms[1].coefficient() == -1
)
@pytest.mark.parametrize(
"op, symbol",
[
(operator.le, "<="),
(operator.eq, "=="),
(operator.ge, ">="),
(operator.lt, None),
(operator.ne, None),
(operator.gt, None),
],
)
def test_expression_rich_compare_operations(op, symbol) -> None:
"""Test using comparison on variables."""
v1 = Variable("foo")
v2 = Variable("bar")
t1 = Term(v1, 10)
e1 = t1 + 5
e2 = v2 - 10
if symbol is not None:
c = op(e1, e2)
assert isinstance(c, Constraint)
e = c.expression()
t = e.terms()
assert len(t) == 2
if t[0].variable() is not v1:
t = (t[1], t[0])
assert (
t[0].variable() is v1
and t[0].coefficient() == 10
and t[1].variable() is v2
and t[1].coefficient() == -1
)
assert e.constant() == 15
assert c.op() == symbol and c.strength() == strength.required
else:
with pytest.raises(TypeError) as excinfo:
op(e1, e2)
if "PyPy" in sys.version:
assert "Expression" in excinfo.exconly()
else:
assert "kiwisolver.Expression" in excinfo.exconly()

295
py/tests/test_solver.py Normal file
View File

@@ -0,0 +1,295 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2022, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import pytest
from kiwisolver import (
BadRequiredStrength,
DuplicateConstraint,
DuplicateEditVariable,
Solver,
UnknownConstraint,
UnknownEditVariable,
UnsatisfiableConstraint,
Variable,
)
def test_solver_creation() -> None:
"""Test initializing a solver."""
s = Solver()
assert isinstance(s, Solver)
with pytest.raises(TypeError):
Solver(Variable()) # type: ignore
def test_managing_edit_variable() -> None:
"""Test adding/removing edit variables."""
s = Solver()
v1 = Variable("foo")
v2 = Variable("bar")
with pytest.raises(TypeError):
s.hasEditVariable(object()) # type: ignore
with pytest.raises(TypeError):
s.addEditVariable(object(), "weak") # type: ignore
with pytest.raises(TypeError):
s.removeEditVariable(object()) # type: ignore
with pytest.raises(TypeError):
s.suggestValue(object(), 10) # type: ignore
assert not s.hasEditVariable(v1)
s.addEditVariable(v1, "weak")
assert s.hasEditVariable(v1)
with pytest.raises(DuplicateEditVariable) as e:
s.addEditVariable(v1, "medium")
assert e.value.edit_variable is v1
with pytest.raises(UnknownEditVariable) as e2:
s.removeEditVariable(v2)
assert e2.value.edit_variable is v2
s.removeEditVariable(v1)
assert not s.hasEditVariable(v1)
with pytest.raises(BadRequiredStrength):
s.addEditVariable(v1, "required")
s.addEditVariable(v2, "strong")
assert s.hasEditVariable(v2)
with pytest.raises(UnknownEditVariable) as e3:
s.suggestValue(v1, 10)
assert e3.value.edit_variable is v1
s.reset()
assert not s.hasEditVariable(v2)
def test_suggesting_values_for_edit_variables() -> None:
"""Test suggesting values in different situations."""
# Suggest value for an edit variable entering a weak equality
s = Solver()
v1 = Variable("foo")
s.addEditVariable(v1, "medium")
s.addConstraint((v1 == 1) | "weak")
s.suggestValue(v1, 2)
s.updateVariables()
assert v1.value() == 2
# Suggest a value for an edit variable entering multiple solver rows
s.reset()
v1 = Variable("foo")
v2 = Variable("bar")
s = Solver()
s.addEditVariable(v2, "weak")
s.addConstraint(v1 + v2 == 0)
s.addConstraint((v2 <= -1))
s.addConstraint((v2 >= 0) | "weak")
s.suggestValue(v2, 0)
s.updateVariables()
assert v2.value() <= -1
def test_managing_constraints() -> None:
"""Test adding/removing constraints."""
s = Solver()
v = Variable("foo")
c1 = v >= 1
c2 = v <= 0
with pytest.raises(TypeError):
s.hasConstraint(object()) # type: ignore
with pytest.raises(TypeError):
s.addConstraint(object()) # type: ignore
with pytest.raises(TypeError):
s.removeConstraint(object()) # type: ignore
assert not s.hasConstraint(c1)
s.addConstraint(c1)
assert s.hasConstraint(c1)
with pytest.raises(DuplicateConstraint) as e:
s.addConstraint(c1)
assert e.value.constraint is c1
with pytest.raises(UnknownConstraint) as e2:
s.removeConstraint(c2)
assert e2.value.constraint is c2
with pytest.raises(UnsatisfiableConstraint) as e3:
s.addConstraint(c2)
assert e3.value.constraint is c2
# XXX need to find how to get an invalid symbol from choose subject
# with pytest.raises(UnsatisfiableConstraint):
# s.addConstraint(c3)
s.removeConstraint(c1)
assert not s.hasConstraint(c1)
s.addConstraint(c2)
assert s.hasConstraint(c2)
s.reset()
assert not s.hasConstraint(c2)
def test_solving_under_constrained_system() -> None:
"""Test solving an under constrained system."""
s = Solver()
v = Variable("foo")
c = 2 * v + 1 >= 0
s.addEditVariable(v, "weak")
s.addConstraint(c)
s.suggestValue(v, 10)
s.updateVariables()
assert c.expression().value() == 21
assert c.expression().terms()[0].value() == 20
assert c.expression().terms()[0].variable().value() == 10
def test_solving_with_strength() -> None:
"""Test solving a system with unsatisfiable non-required constraint."""
v1 = Variable("foo")
v2 = Variable("bar")
s = Solver()
s.addConstraint(v1 + v2 == 0)
s.addConstraint(v1 == 10)
s.addConstraint((v2 >= 0) | "weak")
s.updateVariables()
assert v1.value() == 10 and v2.value() == -10
s.reset()
s.addConstraint(v1 + v2 == 0)
s.addConstraint((v1 >= 10) | "medium")
s.addConstraint((v2 == 2) | "strong")
s.updateVariables()
assert v1.value() == -2 and v2.value() == 2
# Typical output solver.dump in the following function.
# the order is not stable.
# """Objective
# ---------
# -2 + 2 * e2 + 1 * s8 + -2 * s10
# Tableau
# -------
# v1 | 1 + 1 * s10
# e3 | -1 + 1 * e2 + -1 * s10
# v4 | -1 + -1 * d5 + -1 * s10
# s6 | -2 + -1 * s10
# e9 | -1 + 1 * s8 + -1 * s10
# Infeasible
# ----------
# e3
# e9
# Variables
# ---------
# bar = v1
# foo = v4
# Edit Variables
# --------------
# bar
# Constraints
# -----------
# 1 * bar + -0 >= 0 | strength = 1
# 1 * bar + 1 <= 0 | strength = 1.001e+09
# 1 * foo + 1 * bar + 0 == 0 | strength = 1.001e+09
# 1 * bar + 0 == 0 | strength = 1
# """
def test_dumping_solver(capsys) -> None:
"""Test dumping the solver internal to stdout."""
v1 = Variable("foo")
v2 = Variable("bar")
s = Solver()
s.addEditVariable(v2, "weak")
s.addConstraint(v1 + v2 == 0)
s.addConstraint((v2 <= -1))
s.addConstraint((v2 >= 0) | "weak")
s.updateVariables()
try:
s.addConstraint((v2 >= 1))
except Exception:
pass
# Print the solver state
s.dump()
state = s.dumps()
for header in (
"Objective",
"Tableau",
"Infeasible",
"Variables",
"Edit Variables",
"Constraints",
):
assert header in state
def test_handling_infeasible_constraints() -> None:
"""Test that we properly handle infeasible constraints.
We use the example of the cassowary paper to generate an infeasible
situation after updating an edit variable which causes the solver to use
the dual optimization.
"""
xm = Variable("xm")
xl = Variable("xl")
xr = Variable("xr")
s = Solver()
s.addEditVariable(xm, "strong")
s.addEditVariable(xl, "weak")
s.addEditVariable(xr, "weak")
s.addConstraint(2 * xm == xl + xr)
s.addConstraint(xl + 20 <= xr)
s.addConstraint(xl >= -10)
s.addConstraint(xr <= 100)
s.suggestValue(xm, 40)
s.suggestValue(xr, 50)
s.suggestValue(xl, 30)
# First update causing a normal update.
s.suggestValue(xm, 60)
# Create an infeasible condition triggering a dual optimization
s.suggestValue(xm, 90)
s.updateVariables()
assert xl.value() + xr.value() == 2 * xm.value()
assert xl.value() == 80
assert xr.value() == 100
def test_constraint_violated():
"""Test running a solver and check that constraints
report they've been violated
"""
s = Solver()
v = Variable("foo")
c1 = (v >= 10) | "required"
c2 = (v <= -5) | "weak"
s.addConstraint(c1)
s.addConstraint(c2)
s.updateVariables()
assert v.value() >= 10
assert c1.violated() is False
assert c2.violated() is True

27
py/tests/test_strength.py Normal file
View File

@@ -0,0 +1,27 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2021, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import pytest
from kiwisolver import strength
def test_accessing_predefined_strength() -> None:
"""Test getting the default values for the strength."""
assert strength.weak < strength.medium
assert strength.medium < strength.strong
assert strength.strong < strength.required
def test_creating_strength() -> None:
"""Test creating strength from constituent values."""
assert strength.create(0, 0, 1) < strength.create(0, 1, 0)
assert strength.create(0, 1, 0) < strength.create(1, 0, 0)
assert strength.create(1, 0, 0, 1) < strength.create(1, 0, 0, 4)
with pytest.raises(TypeError):
strength.create("", "", "") # type: ignore

202
py/tests/test_term.py Normal file
View File

@@ -0,0 +1,202 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2021, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import gc
import math
import operator
from typing import Tuple
import pytest
from kiwisolver import Constraint, Expression, Term, Variable, strength
def test_term_creation() -> None:
"""Test the Term constructor."""
v = Variable("foo")
t = Term(v)
assert t.variable() is v
assert t.coefficient() == 1
t = Term(v, 100)
assert t.variable() is v
assert t.coefficient() == 100
assert str(t) == "100 * foo"
with pytest.raises(TypeError) as excinfo:
Term("") # type: ignore
assert "Variable" in excinfo.exconly()
# ensure we test garbage collection
del t
gc.collect()
@pytest.fixture()
def terms():
"""Terms for testing."""
v = Variable("foo")
v2 = Variable("bar")
t = Term(v, 10)
t2 = Term(v2)
return t, t2, v, v2
def test_term_neg(terms: Tuple[Term, Term, Variable, Variable]) -> None:
"""Test neg on a term."""
t, _, v, _ = terms
neg = -t
assert isinstance(neg, Term)
assert neg.variable() is v and neg.coefficient() == -10
def test_term_mul(terms: Tuple[Term, Term, Variable, Variable]) -> None:
"""Test term multiplications"""
t, _, v, _ = terms
for mul in (t * 2, 2.0 * t):
assert isinstance(mul, Term)
assert mul.variable() is v and mul.coefficient() == 20
with pytest.raises(TypeError):
t * v # type: ignore
def test_term_div(terms: Tuple[Term, Term, Variable, Variable]) -> None:
"""Test term divisions."""
t, _, v, v2 = terms
div = t / 2
assert isinstance(div, Term)
assert div.variable() is v and div.coefficient() == 5
with pytest.raises(TypeError):
t / v2 # type: ignore
with pytest.raises(ZeroDivisionError):
t / 0
def test_term_add(terms: Tuple[Term, Term, Variable, Variable]) -> None:
"""Test term additions."""
t, t2, v, v2 = terms
for add in (t + 2, 2.0 + t):
assert isinstance(add, Expression)
assert add.constant() == 2
terms_ = add.terms()
assert (
len(terms_) == 1
and terms[0].variable() is v
and terms_[0].coefficient() == 10
)
for add2, order in zip((t + v2, v2 + t), ((0, 1), (1, 0))):
assert isinstance(add2, Expression)
assert add2.constant() == 0
terms_ = add2.terms()
assert (
len(terms_) == 2
and terms_[order[0]].variable() is v
and terms_[order[0]].coefficient() == 10
and terms_[order[1]].variable() is v2
and terms_[order[1]].coefficient() == 1
)
add2 = t + t2
assert isinstance(add2, Expression)
assert add2.constant() == 0
terms_ = add2.terms()
assert (
len(terms_) == 2
and terms_[0].variable() is v
and terms_[0].coefficient() == 10
and terms_[1].variable() is v2
and terms_[1].coefficient() == 1
)
def test_term_sub(terms: Tuple[Term, Term, Variable, Variable]) -> None:
"""Test term substractions."""
t, t2, v, v2 = terms
for sub, diff in zip((t - 2, 2.0 - t), (-2, 2)):
assert isinstance(sub, Expression)
assert sub.constant() == diff
terms_ = sub.terms()
assert (
len(terms_) == 1
and terms[0].variable() is v
and terms_[0].coefficient() == -math.copysign(10, diff)
)
for sub2, order in zip((t - v2, v2 - t), ((0, 1), (1, 0))):
assert isinstance(sub2, Expression)
assert sub2.constant() == 0
terms_ = sub2.terms()
assert (
len(terms_) == 2
and terms_[order[0]].variable() is v
and terms_[order[0]].coefficient() == 10 * (-1) ** order[0]
and terms_[order[1]].variable() is v2
and terms_[order[1]].coefficient() == -1 * (-1) ** order[0]
)
sub2 = t - t2
assert isinstance(sub2, Expression)
assert sub2.constant() == 0
terms_ = sub2.terms()
assert (
len(terms_) == 2
and terms_[0].variable() is v
and terms_[0].coefficient() == 10
and terms_[1].variable() is v2
and terms_[1].coefficient() == -1
)
@pytest.mark.parametrize(
"op, symbol",
[
(operator.le, "<="),
(operator.eq, "=="),
(operator.ge, ">="),
(operator.lt, None),
(operator.ne, None),
(operator.gt, None),
],
)
def test_term_rich_compare_operations(op, symbol):
"""Test using comparison on variables."""
v = Variable("foo")
v2 = Variable("bar")
t1 = Term(v, 10)
t2 = Term(v2, 20)
if symbol is not None:
c = op(t1, t2 + 1)
assert isinstance(c, Constraint)
e = c.expression()
t = e.terms()
assert len(t) == 2
if t[0].variable() is not v:
t = (t[1], t[0])
assert (
t[0].variable() is v
and t[0].coefficient() == 10
and t[1].variable() is v2
and t[1].coefficient() == -20
)
assert e.constant() == -1
assert c.op() == symbol and c.strength() == strength.required
else:
with pytest.raises(TypeError):
op(t1, t2)

163
py/tests/test_variable.py Normal file
View File

@@ -0,0 +1,163 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2022, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import math
import operator
import sys
import pytest
from kiwisolver import Constraint, Expression, Term, Variable, strength
def test_variable_methods() -> None:
"""Test the variable modification methods."""
v = Variable()
assert v.name() == ""
v.setName("Δ")
assert v.name() == "Δ"
v.setName("foo")
assert v.name() == "foo"
with pytest.raises(TypeError):
v.setName(1) # type: ignore
if sys.version_info >= (3,):
with pytest.raises(TypeError):
v.setName(b"r") # type: ignore
assert v.value() == 0.0
assert v.context() is None
ctx = object()
v.setContext(ctx)
assert v.context() is ctx
assert str(v) == "foo"
with pytest.raises(TypeError):
Variable(1) # type: ignore
def test_variable_neg() -> None:
"""Test neg on a variable."""
v = Variable("foo")
neg = -v
assert isinstance(neg, Term)
assert neg.variable() is v and neg.coefficient() == -1
def test_variable_mul() -> None:
"""Test variable multiplications."""
v = Variable("foo")
v2 = Variable("bar")
for mul in (v * 2.0, 2 * v):
assert isinstance(mul, Term)
assert mul.variable() is v and mul.coefficient() == 2
with pytest.raises(TypeError):
v * v2 # type: ignore
def test_variable_division() -> None:
"""Test variable divisions."""
v = Variable("foo")
v2 = Variable("bar")
div = v / 2.0
assert isinstance(div, Term)
assert div.variable() is v and div.coefficient() == 0.5
with pytest.raises(TypeError):
v / v2 # type: ignore
with pytest.raises(ZeroDivisionError):
v / 0
def test_variable_addition() -> None:
"""Test variable additions."""
v = Variable("foo")
v2 = Variable("bar")
for add in (v + 2, 2.0 + v):
assert isinstance(add, Expression)
assert add.constant() == 2
terms = add.terms()
assert (
len(terms) == 1 and terms[0].variable() is v and terms[0].coefficient() == 1
)
add2 = v + v2
assert isinstance(add2, Expression)
assert add2.constant() == 0
terms = add2.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 1
and terms[1].variable() is v2
and terms[1].coefficient() == 1
)
with pytest.raises(TypeError):
v + "" # type: ignore
def test_variable_sub() -> None:
"""Test variable substractions."""
v = Variable("foo")
v2 = Variable("bar")
for sub, diff in zip((v - 2, 2 - v), (-2, 2)):
assert isinstance(sub, Expression)
assert sub.constant() == diff
terms = sub.terms()
assert (
len(terms) == 1
and terms[0].variable() is v
and terms[0].coefficient() == -math.copysign(1, diff)
)
sub2 = v - v2
assert isinstance(sub2, Expression)
assert sub2.constant() == 0
terms = sub2.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 1
and terms[1].variable() is v2
and terms[1].coefficient() == -1
)
def test_variable_rich_compare_operations() -> None:
"""Test using comparison on variables."""
v = Variable("foo")
v2 = Variable("Δ")
for op, symbol in ((operator.le, "<="), (operator.eq, "=="), (operator.ge, ">=")):
c = op(v, v2 + 1)
assert isinstance(c, Constraint)
e = c.expression()
t = e.terms()
assert len(t) == 2
if t[0].variable() is not v:
t = (t[1], t[0])
assert (
t[0].variable() is v
and t[0].coefficient() == 1
and t[1].variable() is v2
and t[1].coefficient() == -1
)
assert e.constant() == -1
assert c.op() == symbol and c.strength() == strength.required
for op in (operator.lt, operator.ne, operator.gt):
with pytest.raises(TypeError):
op(v, v2)