Merge commit '81396a5322a7a48764fcf254d5d933ba1e57bdc5' as 'kiwi'
This commit is contained in:
42
kiwi/py/kiwisolver/__init__.py
Normal file
42
kiwi/py/kiwisolver/__init__.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# --------------------------------------------------------------------------------------
|
||||
# Copyright (c) 2013-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.
|
||||
# --------------------------------------------------------------------------------------
|
||||
from ._cext import (
|
||||
Constraint,
|
||||
Expression,
|
||||
Solver,
|
||||
Term,
|
||||
Variable,
|
||||
__kiwi_version__,
|
||||
__version__,
|
||||
strength,
|
||||
)
|
||||
from .exceptions import (
|
||||
BadRequiredStrength,
|
||||
DuplicateConstraint,
|
||||
DuplicateEditVariable,
|
||||
UnknownConstraint,
|
||||
UnknownEditVariable,
|
||||
UnsatisfiableConstraint,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"BadRequiredStrength",
|
||||
"DuplicateConstraint",
|
||||
"DuplicateEditVariable",
|
||||
"UnknownConstraint",
|
||||
"UnknownEditVariable",
|
||||
"UnsatisfiableConstraint",
|
||||
"strength",
|
||||
"Variable",
|
||||
"Term",
|
||||
"Expression",
|
||||
"Constraint",
|
||||
"Solver",
|
||||
"__version__",
|
||||
"__kiwi_version__",
|
||||
]
|
||||
228
kiwi/py/kiwisolver/_cext.pyi
Normal file
228
kiwi/py/kiwisolver/_cext.pyi
Normal file
@@ -0,0 +1,228 @@
|
||||
# --------------------------------------------------------------------------------------
|
||||
# Copyright (c) 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.
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
from typing import Any, Iterable, NoReturn, Tuple, type_check_only
|
||||
|
||||
try:
|
||||
from typing import Literal
|
||||
except ImportError:
|
||||
from typing_extensions import Literal # type: ignore
|
||||
|
||||
__version__: str
|
||||
__kiwi_version__: str
|
||||
|
||||
# Types
|
||||
@type_check_only
|
||||
class Strength:
|
||||
@property
|
||||
def weak(self) -> float: ...
|
||||
@property
|
||||
def medium(self) -> float: ...
|
||||
@property
|
||||
def strong(self) -> float: ...
|
||||
@property
|
||||
def required(self) -> float: ...
|
||||
def create(
|
||||
self,
|
||||
a: int | float,
|
||||
b: int | float,
|
||||
c: int | float,
|
||||
weight: int | float = 1.0,
|
||||
/,
|
||||
) -> float: ...
|
||||
|
||||
# This is meant as a singleton and users should not access the Strength type.
|
||||
strength: Strength
|
||||
|
||||
class Variable:
|
||||
"""Variable to express a constraint in a solver."""
|
||||
|
||||
__hash__: None # type: ignore
|
||||
def __init__(self, name: str = "", context: Any = None, /) -> None: ...
|
||||
def name(self) -> str:
|
||||
"""Get the name of the variable."""
|
||||
...
|
||||
def setName(self, name: str, /) -> Any:
|
||||
"""Set the name of the variable."""
|
||||
...
|
||||
def value(self) -> float:
|
||||
"""Get the current value of the variable."""
|
||||
...
|
||||
def context(self) -> Any:
|
||||
"""Get the context object associated with the variable."""
|
||||
...
|
||||
def setContext(self, context: Any, /) -> Any:
|
||||
"""Set the context object associated with the variable."""
|
||||
...
|
||||
def __neg__(self) -> Term: ...
|
||||
def __add__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __radd__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __sub__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __rsub__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __mul__(self, other: float) -> Term: ...
|
||||
def __rmul__(self, other: float) -> Term: ...
|
||||
def __truediv__(self, other: float) -> Term: ...
|
||||
def __rtruediv__(self, other: float) -> Term: ...
|
||||
def __eq__(self, other: float | Variable | Term | Expression) -> Constraint: ... # type: ignore
|
||||
def __ge__(self, other: float | Variable | Term | Expression) -> Constraint: ...
|
||||
def __le__(self, other: float | Variable | Term | Expression) -> Constraint: ...
|
||||
def __ne__(self, other: Any) -> NoReturn: ...
|
||||
def __gt__(self, other: Any) -> NoReturn: ...
|
||||
def __lt__(self, other: Any) -> NoReturn: ...
|
||||
|
||||
class Term:
|
||||
"""Product of a variable by a constant pre-factor."""
|
||||
|
||||
__hash__: None # type: ignore
|
||||
def __init__(
|
||||
self, variable: Variable, coefficient: int | float = 1.0, /
|
||||
) -> None: ...
|
||||
def coefficient(self) -> float:
|
||||
"""Get the coefficient for the term."""
|
||||
...
|
||||
def variable(self) -> Variable:
|
||||
"""Get the variable for the term."""
|
||||
...
|
||||
def value(self) -> float:
|
||||
"""Get the value for the term."""
|
||||
...
|
||||
def __neg__(self) -> Term: ...
|
||||
def __add__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __radd__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __sub__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __rsub__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __mul__(self, other: float) -> Term: ...
|
||||
def __rmul__(self, other: float) -> Term: ...
|
||||
def __truediv__(self, other: float) -> Term: ...
|
||||
def __rtruediv__(self, other: float) -> Term: ...
|
||||
def __eq__(self, other: float | Variable | Term | Expression) -> Constraint: ... # type: ignore
|
||||
def __ge__(self, other: float | Variable | Term | Expression) -> Constraint: ...
|
||||
def __le__(self, other: float | Variable | Term | Expression) -> Constraint: ...
|
||||
def __ne__(self, other: Any) -> NoReturn: ...
|
||||
def __gt__(self, other: Any) -> NoReturn: ...
|
||||
def __lt__(self, other: Any) -> NoReturn: ...
|
||||
|
||||
class Expression:
|
||||
"""Sum of terms and an additional constant."""
|
||||
|
||||
__hash__: None # type: ignore
|
||||
def __init__(
|
||||
self, terms: Iterable[Term], constant: int | float = 0.0, /
|
||||
) -> None: ...
|
||||
def constant(self) -> float:
|
||||
"" "Get the constant for the expression." ""
|
||||
...
|
||||
def terms(self) -> Tuple[Term, ...]:
|
||||
"""Get the tuple of terms for the expression."""
|
||||
...
|
||||
def value(self) -> float:
|
||||
"""Get the value for the expression."""
|
||||
...
|
||||
def __neg__(self) -> Expression: ...
|
||||
def __add__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __radd__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __sub__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __rsub__(self, other: float | Variable | Term | Expression) -> Expression: ...
|
||||
def __mul__(self, other: float) -> Expression: ...
|
||||
def __rmul__(self, other: float) -> Expression: ...
|
||||
def __truediv__(self, other: float) -> Expression: ...
|
||||
def __rtruediv__(self, other: float) -> Expression: ...
|
||||
def __eq__(self, other: float | Variable | Term | Expression) -> Constraint: ... # type: ignore
|
||||
def __ge__(self, other: float | Variable | Term | Expression) -> Constraint: ...
|
||||
def __le__(self, other: float | Variable | Term | Expression) -> Constraint: ...
|
||||
def __ne__(self, other: Any) -> NoReturn: ...
|
||||
def __gt__(self, other: Any) -> NoReturn: ...
|
||||
def __lt__(self, other: Any) -> NoReturn: ...
|
||||
|
||||
class Constraint:
|
||||
def __init__(
|
||||
self,
|
||||
expression: Expression,
|
||||
op: Literal["=="] | Literal["<="] | Literal[">="],
|
||||
strength: float
|
||||
| Literal["weak"]
|
||||
| Literal["medium"]
|
||||
| Literal["strong"]
|
||||
| Literal["required"] = "required",
|
||||
/,
|
||||
) -> None: ...
|
||||
def expression(self) -> Expression:
|
||||
"""Get the expression object for the constraint."""
|
||||
...
|
||||
def op(self) -> Literal["=="] | Literal["<="] | Literal[">="]:
|
||||
"""Get the relational operator for the constraint."""
|
||||
...
|
||||
def strength(self) -> float:
|
||||
"""Get the strength for the constraint."""
|
||||
...
|
||||
def violated(self) -> bool:
|
||||
"""Indicate if the constraint is violated in teh current state of the solver."""
|
||||
...
|
||||
def __or__(
|
||||
self,
|
||||
other: float
|
||||
| Literal["weak"]
|
||||
| Literal["medium"]
|
||||
| Literal["strong"]
|
||||
| Literal["required"],
|
||||
) -> Constraint: ...
|
||||
def __ror__(
|
||||
self,
|
||||
other: float
|
||||
| Literal["weak"]
|
||||
| Literal["medium"]
|
||||
| Literal["strong"]
|
||||
| Literal["required"],
|
||||
) -> Constraint: ...
|
||||
|
||||
class Solver:
|
||||
"""Kiwi solver class."""
|
||||
|
||||
def __init__(self) -> None: ...
|
||||
def addConstraint(self, constraint: Constraint, /) -> None:
|
||||
"""Add a constraint to the solver."""
|
||||
...
|
||||
def removeConstraint(self, constraint: Constraint, /) -> None:
|
||||
"""Remove a constraint from the solver."""
|
||||
...
|
||||
def hasConstraint(self, constraint: Constraint, /) -> bool:
|
||||
"""Check whether the solver contains a constraint."""
|
||||
...
|
||||
def addEditVariable(
|
||||
self,
|
||||
variable: Variable,
|
||||
strength: float
|
||||
| Literal["weak"]
|
||||
| Literal["medium"]
|
||||
| Literal["strong"]
|
||||
| Literal["required"],
|
||||
/,
|
||||
) -> None:
|
||||
"""Add an edit variable to the solver."""
|
||||
...
|
||||
def removeEditVariable(self, variable: Variable, /) -> None:
|
||||
"""Remove an edit variable from the solver."""
|
||||
...
|
||||
def hasEditVariable(self, variable: Variable, /) -> bool:
|
||||
"""Check whether the solver contains an edit variable."""
|
||||
...
|
||||
def suggestValue(self, variable: Variable, value: int | float, /) -> None:
|
||||
"""Suggest a desired value for an edit variable."""
|
||||
...
|
||||
def updateVariables(self) -> None:
|
||||
"""Update the values of the solver variables."""
|
||||
...
|
||||
def reset(self) -> None:
|
||||
"""Reset the solver to the initial empty starting condition."""
|
||||
...
|
||||
def dump(self) -> None:
|
||||
"""Dump a representation of the solver internals to stdout."""
|
||||
...
|
||||
def dumps(self) -> str:
|
||||
"""Dump a representation of the solver internals to a string."""
|
||||
...
|
||||
51
kiwi/py/kiwisolver/exceptions.py
Normal file
51
kiwi/py/kiwisolver/exceptions.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# --------------------------------------------------------------------------------------
|
||||
# Copyright (c) 2023, Nucleic Development Team.
|
||||
#
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
#
|
||||
# The full license is in the file LICENSE, distributed with this software.
|
||||
# --------------------------------------------------------------------------------------
|
||||
"""Kiwi exceptions.
|
||||
|
||||
Imported by the kiwisolver C extension.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class BadRequiredStrength(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DuplicateConstraint(Exception):
|
||||
__slots__ = ("constraint",)
|
||||
|
||||
def __init__(self, constraint):
|
||||
self.constraint = constraint
|
||||
|
||||
|
||||
class DuplicateEditVariable(Exception):
|
||||
__slots__ = ("edit_variable",)
|
||||
|
||||
def __init__(self, edit_variable):
|
||||
self.edit_variable = edit_variable
|
||||
|
||||
|
||||
class UnknownConstraint(Exception):
|
||||
__slots__ = ("constraint",)
|
||||
|
||||
def __init__(self, constraint):
|
||||
self.constraint = constraint
|
||||
|
||||
|
||||
class UnknownEditVariable(Exception):
|
||||
__slots__ = ("edit_variable",)
|
||||
|
||||
def __init__(self, edit_variable):
|
||||
self.edit_variable = edit_variable
|
||||
|
||||
|
||||
class UnsatisfiableConstraint(Exception):
|
||||
__slots__ = ("constraint",)
|
||||
|
||||
def __init__(self, constraint):
|
||||
self.constraint = constraint
|
||||
0
kiwi/py/kiwisolver/py.typed
Normal file
0
kiwi/py/kiwisolver/py.typed
Normal file
222
kiwi/py/src/constraint.cpp
Normal file
222
kiwi/py/src/constraint.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) 2013-2019, Nucleic Development Team.
|
||||
|
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|
|
||||
| The full license is in the file LICENSE, distributed with this software.
|
||||
|----------------------------------------------------------------------------*/
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <cppy/cppy.h>
|
||||
#include <kiwi/kiwi.h>
|
||||
#include "types.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace kiwisolver
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
PyObject *
|
||||
Constraint_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
static const char *kwlist[] = {"expression", "op", "strength", 0};
|
||||
PyObject *pyexpr;
|
||||
PyObject *pyop;
|
||||
PyObject *pystrength = 0;
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, kwargs, "OO|O:__new__", const_cast<char **>(kwlist),
|
||||
&pyexpr, &pyop, &pystrength))
|
||||
return 0;
|
||||
if (!Expression::TypeCheck(pyexpr))
|
||||
return cppy::type_error(pyexpr, "Expression");
|
||||
kiwi::RelationalOperator op;
|
||||
if (!convert_to_relational_op(pyop, op))
|
||||
return 0;
|
||||
double strength = kiwi::strength::required;
|
||||
if (pystrength && !convert_to_strength(pystrength, strength))
|
||||
return 0;
|
||||
cppy::ptr pycn(PyType_GenericNew(type, args, kwargs));
|
||||
if (!pycn)
|
||||
return 0;
|
||||
Constraint *cn = reinterpret_cast<Constraint *>(pycn.get());
|
||||
cn->expression = reduce_expression(pyexpr);
|
||||
if (!cn->expression)
|
||||
return 0;
|
||||
kiwi::Expression expr(convert_to_kiwi_expression(cn->expression));
|
||||
new (&cn->constraint) kiwi::Constraint(expr, op, strength);
|
||||
return pycn.release();
|
||||
}
|
||||
|
||||
void Constraint_clear(Constraint *self)
|
||||
{
|
||||
Py_CLEAR(self->expression);
|
||||
}
|
||||
|
||||
int Constraint_traverse(Constraint *self, visitproc visit, void *arg)
|
||||
{
|
||||
Py_VISIT(self->expression);
|
||||
#if PY_VERSION_HEX >= 0x03090000
|
||||
// This was not needed before Python 3.9 (Python issue 35810 and 40217)
|
||||
Py_VISIT(Py_TYPE(self));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Constraint_dealloc(Constraint *self)
|
||||
{
|
||||
PyObject_GC_UnTrack(self);
|
||||
Constraint_clear(self);
|
||||
self->constraint.~Constraint();
|
||||
Py_TYPE(self)->tp_free(pyobject_cast(self));
|
||||
}
|
||||
|
||||
PyObject *
|
||||
Constraint_repr(Constraint *self)
|
||||
{
|
||||
std::stringstream stream;
|
||||
Expression *expr = reinterpret_cast<Expression *>(self->expression);
|
||||
Py_ssize_t size = PyTuple_GET_SIZE(expr->terms);
|
||||
for (Py_ssize_t i = 0; i < size; ++i)
|
||||
{
|
||||
PyObject *item = PyTuple_GET_ITEM(expr->terms, i);
|
||||
Term *term = reinterpret_cast<Term *>(item);
|
||||
stream << term->coefficient << " * ";
|
||||
stream << reinterpret_cast<Variable *>(term->variable)->variable.name();
|
||||
stream << " + ";
|
||||
}
|
||||
stream << expr->constant;
|
||||
switch (self->constraint.op())
|
||||
{
|
||||
case kiwi::OP_EQ:
|
||||
stream << " == 0";
|
||||
break;
|
||||
case kiwi::OP_LE:
|
||||
stream << " <= 0";
|
||||
break;
|
||||
case kiwi::OP_GE:
|
||||
stream << " >= 0";
|
||||
break;
|
||||
}
|
||||
stream << " | strength = " << self->constraint.strength();
|
||||
if (self->constraint.violated())
|
||||
{
|
||||
stream << " (VIOLATED)";
|
||||
}
|
||||
return PyUnicode_FromString(stream.str().c_str());
|
||||
}
|
||||
|
||||
PyObject *
|
||||
Constraint_expression(Constraint *self)
|
||||
{
|
||||
return cppy::incref(self->expression);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
Constraint_op(Constraint *self)
|
||||
{
|
||||
PyObject *res = 0;
|
||||
switch (self->constraint.op())
|
||||
{
|
||||
case kiwi::OP_EQ:
|
||||
res = PyUnicode_FromString("==");
|
||||
break;
|
||||
case kiwi::OP_LE:
|
||||
res = PyUnicode_FromString("<=");
|
||||
break;
|
||||
case kiwi::OP_GE:
|
||||
res = PyUnicode_FromString(">=");
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
Constraint_strength(Constraint *self)
|
||||
{
|
||||
return PyFloat_FromDouble(self->constraint.strength());
|
||||
}
|
||||
|
||||
PyObject *
|
||||
Constraint_violated(Constraint *self)
|
||||
{
|
||||
if (self->constraint.violated()) {
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
PyObject *
|
||||
Constraint_or(PyObject *pyoldcn, PyObject *value)
|
||||
{
|
||||
if (!Constraint::TypeCheck(pyoldcn))
|
||||
std::swap(pyoldcn, value);
|
||||
double strength;
|
||||
if (!convert_to_strength(value, strength))
|
||||
return 0;
|
||||
PyObject *pynewcn = PyType_GenericNew(Constraint::TypeObject, 0, 0);
|
||||
if (!pynewcn)
|
||||
return 0;
|
||||
Constraint *oldcn = reinterpret_cast<Constraint *>(pyoldcn);
|
||||
Constraint *newcn = reinterpret_cast<Constraint *>(pynewcn);
|
||||
newcn->expression = cppy::incref(oldcn->expression);
|
||||
new (&newcn->constraint) kiwi::Constraint(oldcn->constraint, strength);
|
||||
return pynewcn;
|
||||
}
|
||||
|
||||
static PyMethodDef
|
||||
Constraint_methods[] = {
|
||||
{"expression", (PyCFunction)Constraint_expression, METH_NOARGS,
|
||||
"Get the expression object for the constraint."},
|
||||
{"op", (PyCFunction)Constraint_op, METH_NOARGS,
|
||||
"Get the relational operator for the constraint."},
|
||||
{"strength", (PyCFunction)Constraint_strength, METH_NOARGS,
|
||||
"Get the strength for the constraint."},
|
||||
{"violated", (PyCFunction)Constraint_violated, METH_NOARGS,
|
||||
"Return whether or not the constraint was violated "
|
||||
"during the last solver pass."},
|
||||
{0} // sentinel
|
||||
};
|
||||
|
||||
static PyType_Slot Constraint_Type_slots[] = {
|
||||
{Py_tp_dealloc, void_cast(Constraint_dealloc)}, /* tp_dealloc */
|
||||
{Py_tp_traverse, void_cast(Constraint_traverse)}, /* tp_traverse */
|
||||
{Py_tp_clear, void_cast(Constraint_clear)}, /* tp_clear */
|
||||
{Py_tp_repr, void_cast(Constraint_repr)}, /* tp_repr */
|
||||
{Py_tp_methods, void_cast(Constraint_methods)}, /* tp_methods */
|
||||
{Py_tp_new, void_cast(Constraint_new)}, /* tp_new */
|
||||
{Py_tp_alloc, void_cast(PyType_GenericAlloc)}, /* tp_alloc */
|
||||
{Py_tp_free, void_cast(PyObject_GC_Del)}, /* tp_free */
|
||||
{Py_nb_or, void_cast(Constraint_or)}, /* nb_or */
|
||||
{0, 0},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Initialize static variables (otherwise the compiler eliminates them)
|
||||
PyTypeObject *Constraint::TypeObject = NULL;
|
||||
|
||||
PyType_Spec Constraint::TypeObject_Spec = {
|
||||
"kiwisolver.Constraint", /* tp_name */
|
||||
sizeof(Constraint), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
Py_TPFLAGS_DEFAULT |
|
||||
Py_TPFLAGS_HAVE_GC |
|
||||
Py_TPFLAGS_BASETYPE, /* tp_flags */
|
||||
Constraint_Type_slots /* slots */
|
||||
};
|
||||
|
||||
bool Constraint::Ready()
|
||||
{
|
||||
// The reference will be handled by the module to which we will add the type
|
||||
TypeObject = pytype_cast(PyType_FromSpec(&TypeObject_Spec));
|
||||
if (!TypeObject)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace kiwisolver
|
||||
251
kiwi/py/src/expression.cpp
Normal file
251
kiwi/py/src/expression.cpp
Normal file
@@ -0,0 +1,251 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) 2013-2019, Nucleic Development Team.
|
||||
|
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|
|
||||
| The full license is in the file LICENSE, distributed with this software.
|
||||
|----------------------------------------------------------------------------*/
|
||||
#include <sstream>
|
||||
#include <cppy/cppy.h>
|
||||
#include "symbolics.h"
|
||||
#include "types.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
namespace kiwisolver
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
PyObject*
|
||||
Expression_new( PyTypeObject* type, PyObject* args, PyObject* kwargs )
|
||||
{
|
||||
static const char *kwlist[] = { "terms", "constant", 0 };
|
||||
PyObject* pyterms;
|
||||
PyObject* pyconstant = 0;
|
||||
if( !PyArg_ParseTupleAndKeywords(
|
||||
args, kwargs, "O|O:__new__", const_cast<char**>( kwlist ),
|
||||
&pyterms, &pyconstant ) )
|
||||
return 0;
|
||||
cppy::ptr terms( PySequence_Tuple( pyterms ) );
|
||||
if( !terms )
|
||||
return 0;
|
||||
Py_ssize_t end = PyTuple_GET_SIZE( terms.get() );
|
||||
for( Py_ssize_t i = 0; i < end; ++i )
|
||||
{
|
||||
PyObject* item = PyTuple_GET_ITEM( terms.get(), i );
|
||||
if( !Term::TypeCheck( item ) )
|
||||
return cppy::type_error( item, "Term" );
|
||||
}
|
||||
double constant = 0.0;
|
||||
if( pyconstant && !convert_to_double( pyconstant, constant ) )
|
||||
return 0;
|
||||
PyObject* pyexpr = PyType_GenericNew( type, args, kwargs );
|
||||
if( !pyexpr )
|
||||
return 0;
|
||||
Expression* self = reinterpret_cast<Expression*>( pyexpr );
|
||||
self->terms = terms.release();
|
||||
self->constant = constant;
|
||||
return pyexpr;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Expression_clear( Expression* self )
|
||||
{
|
||||
Py_CLEAR( self->terms );
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
Expression_traverse( Expression* self, visitproc visit, void* arg )
|
||||
{
|
||||
Py_VISIT( self->terms );
|
||||
#if PY_VERSION_HEX >= 0x03090000
|
||||
// This was not needed before Python 3.9 (Python issue 35810 and 40217)
|
||||
Py_VISIT(Py_TYPE(self));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Expression_dealloc( Expression* self )
|
||||
{
|
||||
PyObject_GC_UnTrack( self );
|
||||
Expression_clear( self );
|
||||
Py_TYPE( self )->tp_free( pyobject_cast( self ) );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Expression_repr( Expression* self )
|
||||
{
|
||||
std::stringstream stream;
|
||||
Py_ssize_t end = PyTuple_GET_SIZE( self->terms );
|
||||
for( Py_ssize_t i = 0; i < end; ++i )
|
||||
{
|
||||
PyObject* item = PyTuple_GET_ITEM( self->terms, i );
|
||||
Term* term = reinterpret_cast<Term*>( item );
|
||||
stream << term->coefficient << " * ";
|
||||
stream << reinterpret_cast<Variable*>( term->variable )->variable.name();
|
||||
stream << " + ";
|
||||
}
|
||||
stream << self->constant;
|
||||
return PyUnicode_FromString( stream.str().c_str() );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Expression_terms( Expression* self )
|
||||
{
|
||||
return cppy::incref( self->terms );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Expression_constant( Expression* self )
|
||||
{
|
||||
return PyFloat_FromDouble( self->constant );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Expression_value( Expression* self )
|
||||
{
|
||||
double result = self->constant;
|
||||
Py_ssize_t size = PyTuple_GET_SIZE( self->terms );
|
||||
for( Py_ssize_t i = 0; i < size; ++i )
|
||||
{
|
||||
PyObject* item = PyTuple_GET_ITEM( self->terms, i );
|
||||
Term* term = reinterpret_cast<Term*>( item );
|
||||
Variable* pyvar = reinterpret_cast<Variable*>( term->variable );
|
||||
result += term->coefficient * pyvar->variable.value();
|
||||
}
|
||||
return PyFloat_FromDouble( result );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Expression_add( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinaryAdd, Expression>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Expression_sub( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinarySub, Expression>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Expression_mul( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinaryMul, Expression>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Expression_div( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinaryDiv, Expression>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Expression_neg( PyObject* value )
|
||||
{
|
||||
return UnaryInvoke<UnaryNeg, Expression>()( value );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Expression_richcmp( PyObject* first, PyObject* second, int op )
|
||||
{
|
||||
switch( op )
|
||||
{
|
||||
case Py_EQ:
|
||||
return BinaryInvoke<CmpEQ, Expression>()( first, second );
|
||||
case Py_LE:
|
||||
return BinaryInvoke<CmpLE, Expression>()( first, second );
|
||||
case Py_GE:
|
||||
return BinaryInvoke<CmpGE, Expression>()( first, second );
|
||||
default:
|
||||
break;
|
||||
}
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"unsupported operand type(s) for %s: "
|
||||
"'%.100s' and '%.100s'",
|
||||
pyop_str( op ),
|
||||
Py_TYPE( first )->tp_name,
|
||||
Py_TYPE( second )->tp_name
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef
|
||||
Expression_methods[] = {
|
||||
{ "terms", ( PyCFunction )Expression_terms, METH_NOARGS,
|
||||
"Get the tuple of terms for the expression." },
|
||||
{ "constant", ( PyCFunction )Expression_constant, METH_NOARGS,
|
||||
"Get the constant for the expression." },
|
||||
{ "value", ( PyCFunction )Expression_value, METH_NOARGS,
|
||||
"Get the value for the expression." },
|
||||
{ 0 } // sentinel
|
||||
};
|
||||
|
||||
|
||||
static PyType_Slot Expression_Type_slots[] = {
|
||||
{ Py_tp_dealloc, void_cast( Expression_dealloc ) }, /* tp_dealloc */
|
||||
{ Py_tp_traverse, void_cast( Expression_traverse ) }, /* tp_traverse */
|
||||
{ Py_tp_clear, void_cast( Expression_clear ) }, /* tp_clear */
|
||||
{ Py_tp_repr, void_cast( Expression_repr ) }, /* tp_repr */
|
||||
{ Py_tp_richcompare, void_cast( Expression_richcmp ) }, /* tp_richcompare */
|
||||
{ Py_tp_methods, void_cast( Expression_methods ) }, /* tp_methods */
|
||||
{ Py_tp_new, void_cast( Expression_new ) }, /* tp_new */
|
||||
{ Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */
|
||||
{ Py_tp_free, void_cast( PyObject_GC_Del ) }, /* tp_free */
|
||||
{ Py_nb_add, void_cast( Expression_add ) }, /* nb_add */
|
||||
{ Py_nb_subtract, void_cast( Expression_sub ) }, /* nb_sub */
|
||||
{ Py_nb_multiply, void_cast( Expression_mul ) }, /* nb_mul */
|
||||
{ Py_nb_negative, void_cast( Expression_neg ) }, /* nb_neg */
|
||||
{ Py_nb_true_divide, void_cast( Expression_div ) }, /* nb_div */
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// Initialize static variables (otherwise the compiler eliminates them)
|
||||
PyTypeObject* Expression::TypeObject = NULL;
|
||||
|
||||
|
||||
PyType_Spec Expression::TypeObject_Spec = {
|
||||
"kiwisolver.Expression", /* tp_name */
|
||||
sizeof( Expression ), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
Py_TPFLAGS_DEFAULT|
|
||||
Py_TPFLAGS_HAVE_GC|
|
||||
Py_TPFLAGS_BASETYPE, /* tp_flags */
|
||||
Expression_Type_slots /* slots */
|
||||
};
|
||||
|
||||
|
||||
bool Expression::Ready()
|
||||
{
|
||||
// The reference will be handled by the module to which we will add the type
|
||||
TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) );
|
||||
if( !TypeObject )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namesapce kiwisolver
|
||||
187
kiwi/py/src/kiwisolver.cpp
Normal file
187
kiwi/py/src/kiwisolver.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) 2013-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.
|
||||
|----------------------------------------------------------------------------*/
|
||||
#include <cppy/cppy.h>
|
||||
#include <kiwi/kiwi.h>
|
||||
#include "types.h"
|
||||
#include "version.h"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
||||
bool ready_types()
|
||||
{
|
||||
using namespace kiwisolver;
|
||||
if( !Variable::Ready() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if( !Term::Ready() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if( !Expression::Ready() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if( !Constraint::Ready() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if( !strength::Ready() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if( !Solver::Ready() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool add_objects( PyObject* mod )
|
||||
{
|
||||
using namespace kiwisolver;
|
||||
|
||||
cppy::ptr kiwiversion( PyUnicode_FromString( KIWI_VERSION ) );
|
||||
if( !kiwiversion )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
cppy::ptr pyversion( PyUnicode_FromString( PY_KIWI_VERSION ) );
|
||||
if( !pyversion )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
cppy::ptr pystrength( PyType_GenericNew( strength::TypeObject, 0, 0 ) );
|
||||
if( !pystrength )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( PyModule_AddObject( mod, "__version__", pyversion.get() ) < 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pyversion.release();
|
||||
|
||||
if( PyModule_AddObject( mod, "__kiwi_version__", kiwiversion.get() ) < 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
kiwiversion.release();
|
||||
|
||||
if( PyModule_AddObject( mod, "strength", pystrength.get() ) < 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pystrength.release();
|
||||
|
||||
// Variable
|
||||
cppy::ptr var( pyobject_cast( Variable::TypeObject ) );
|
||||
if( PyModule_AddObject( mod, "Variable", var.get() ) < 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var.release();
|
||||
|
||||
// Term
|
||||
cppy::ptr term( pyobject_cast( Term::TypeObject ) );
|
||||
if( PyModule_AddObject( mod, "Term", term.get() ) < 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
term.release();
|
||||
|
||||
// Expression
|
||||
cppy::ptr expr( pyobject_cast( Expression::TypeObject ) );
|
||||
if( PyModule_AddObject( mod, "Expression", expr.get() ) < 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
expr.release();
|
||||
|
||||
// Constraint
|
||||
cppy::ptr cons( pyobject_cast( Constraint::TypeObject ) );
|
||||
if( PyModule_AddObject( mod, "Constraint", cons.get() ) < 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
cons.release();
|
||||
|
||||
cppy::ptr solver( pyobject_cast( Solver::TypeObject ) );
|
||||
if( PyModule_AddObject( mod, "Solver", solver.get() ) < 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
solver.release();
|
||||
|
||||
PyModule_AddObject( mod, "DuplicateConstraint", DuplicateConstraint );
|
||||
PyModule_AddObject( mod, "UnsatisfiableConstraint", UnsatisfiableConstraint );
|
||||
PyModule_AddObject( mod, "UnknownConstraint", UnknownConstraint );
|
||||
PyModule_AddObject( mod, "DuplicateEditVariable", DuplicateEditVariable );
|
||||
PyModule_AddObject( mod, "UnknownEditVariable", UnknownEditVariable );
|
||||
PyModule_AddObject( mod, "BadRequiredStrength", BadRequiredStrength );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
kiwi_modexec( PyObject *mod )
|
||||
{
|
||||
if( !ready_types() )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if( !kiwisolver::init_exceptions() )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if( !add_objects( mod ) )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef
|
||||
kiwisolver_methods[] = {
|
||||
{ 0 } // Sentinel
|
||||
};
|
||||
|
||||
|
||||
PyModuleDef_Slot kiwisolver_slots[] = {
|
||||
{Py_mod_exec, reinterpret_cast<void*>( kiwi_modexec ) },
|
||||
{0, NULL}
|
||||
};
|
||||
|
||||
|
||||
struct PyModuleDef moduledef = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_cext",
|
||||
"kiwisolver extension module",
|
||||
0,
|
||||
kiwisolver_methods,
|
||||
kiwisolver_slots,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
PyMODINIT_FUNC PyInit__cext( void )
|
||||
{
|
||||
return PyModuleDef_Init( &moduledef );
|
||||
}
|
||||
338
kiwi/py/src/solver.cpp
Normal file
338
kiwi/py/src/solver.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) 2013-2019, Nucleic Development Team.
|
||||
|
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|
|
||||
| The full license is in the file LICENSE, distributed with this software.
|
||||
|----------------------------------------------------------------------------*/
|
||||
#include <cppy/cppy.h>
|
||||
#include <kiwi/kiwi.h>
|
||||
#include "types.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
namespace kiwisolver
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
PyObject*
|
||||
Solver_new( PyTypeObject* type, PyObject* args, PyObject* kwargs )
|
||||
{
|
||||
if( PyTuple_GET_SIZE( args ) != 0 || ( kwargs && PyDict_Size( kwargs ) != 0 ) )
|
||||
return cppy::type_error( "Solver.__new__ takes no arguments" );
|
||||
PyObject* pysolver = PyType_GenericNew( type, args, kwargs );
|
||||
if( !pysolver )
|
||||
return 0;
|
||||
Solver* self = reinterpret_cast<Solver*>( pysolver );
|
||||
new( &self->solver ) kiwi::Solver();
|
||||
return pysolver;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Solver_dealloc( Solver* self )
|
||||
{
|
||||
self->solver.~Solver();
|
||||
Py_TYPE( self )->tp_free( pyobject_cast( self ) );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Solver_addConstraint( Solver* self, PyObject* other )
|
||||
{
|
||||
if( !Constraint::TypeCheck( other ) )
|
||||
return cppy::type_error( other, "Constraint" );
|
||||
Constraint* cn = reinterpret_cast<Constraint*>( other );
|
||||
try
|
||||
{
|
||||
self->solver.addConstraint( cn->constraint );
|
||||
}
|
||||
catch( const kiwi::DuplicateConstraint& )
|
||||
{
|
||||
PyErr_SetObject( DuplicateConstraint, other );
|
||||
return 0;
|
||||
}
|
||||
catch( const kiwi::UnsatisfiableConstraint& )
|
||||
{
|
||||
PyErr_SetObject( UnsatisfiableConstraint, other );
|
||||
return 0;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Solver_removeConstraint( Solver* self, PyObject* other )
|
||||
{
|
||||
if( !Constraint::TypeCheck( other ) )
|
||||
return cppy::type_error( other, "Constraint" );
|
||||
Constraint* cn = reinterpret_cast<Constraint*>( other );
|
||||
try
|
||||
{
|
||||
self->solver.removeConstraint( cn->constraint );
|
||||
}
|
||||
catch( const kiwi::UnknownConstraint& )
|
||||
{
|
||||
PyErr_SetObject( UnknownConstraint, other );
|
||||
return 0;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Solver_hasConstraint( Solver* self, PyObject* other )
|
||||
{
|
||||
if( !Constraint::TypeCheck( other ) )
|
||||
return cppy::type_error( other, "Constraint" );
|
||||
Constraint* cn = reinterpret_cast<Constraint*>( other );
|
||||
return cppy::incref( self->solver.hasConstraint( cn->constraint ) ? Py_True : Py_False );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Solver_addEditVariable( Solver* self, PyObject* args )
|
||||
{
|
||||
PyObject* pyvar;
|
||||
PyObject* pystrength;
|
||||
if( !PyArg_ParseTuple( args, "OO", &pyvar, &pystrength ) )
|
||||
return 0;
|
||||
if( !Variable::TypeCheck( pyvar ) )
|
||||
return cppy::type_error( pyvar, "Variable" );
|
||||
double strength;
|
||||
if( !convert_to_strength( pystrength, strength ) )
|
||||
return 0;
|
||||
Variable* var = reinterpret_cast<Variable*>( pyvar );
|
||||
try
|
||||
{
|
||||
self->solver.addEditVariable( var->variable, strength );
|
||||
}
|
||||
catch( const kiwi::DuplicateEditVariable& )
|
||||
{
|
||||
PyErr_SetObject( DuplicateEditVariable, pyvar );
|
||||
return 0;
|
||||
}
|
||||
catch( const kiwi::BadRequiredStrength& e )
|
||||
{
|
||||
PyErr_SetString( BadRequiredStrength, e.what() );
|
||||
return 0;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Solver_removeEditVariable( Solver* self, PyObject* other )
|
||||
{
|
||||
if( !Variable::TypeCheck( other ) )
|
||||
return cppy::type_error( other, "Variable" );
|
||||
Variable* var = reinterpret_cast<Variable*>( other );
|
||||
try
|
||||
{
|
||||
self->solver.removeEditVariable( var->variable );
|
||||
}
|
||||
catch( const kiwi::UnknownEditVariable& )
|
||||
{
|
||||
PyErr_SetObject( UnknownEditVariable, other );
|
||||
return 0;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Solver_hasEditVariable( Solver* self, PyObject* other )
|
||||
{
|
||||
if( !Variable::TypeCheck( other ) )
|
||||
return cppy::type_error( other, "Variable" );
|
||||
Variable* var = reinterpret_cast<Variable*>( other );
|
||||
return cppy::incref( self->solver.hasEditVariable( var->variable ) ? Py_True : Py_False );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Solver_suggestValue( Solver* self, PyObject* args )
|
||||
{
|
||||
PyObject* pyvar;
|
||||
PyObject* pyvalue;
|
||||
if( !PyArg_ParseTuple( args, "OO", &pyvar, &pyvalue ) )
|
||||
return 0;
|
||||
if( !Variable::TypeCheck( pyvar ) )
|
||||
return cppy::type_error( pyvar, "Variable" );
|
||||
double value;
|
||||
if( !convert_to_double( pyvalue, value ) )
|
||||
return 0;
|
||||
Variable* var = reinterpret_cast<Variable*>( pyvar );
|
||||
try
|
||||
{
|
||||
self->solver.suggestValue( var->variable, value );
|
||||
}
|
||||
catch( const kiwi::UnknownEditVariable& )
|
||||
{
|
||||
PyErr_SetObject( UnknownEditVariable, pyvar );
|
||||
return 0;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Solver_updateVariables( Solver* self )
|
||||
{
|
||||
self->solver.updateVariables();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Solver_reset( Solver* self )
|
||||
{
|
||||
self->solver.reset();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Solver_dump( Solver* self )
|
||||
{
|
||||
cppy::ptr dump_str( PyUnicode_FromString( self->solver.dumps().c_str() ) );
|
||||
PyObject_Print( dump_str.get(), stdout, 0 );
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
Solver_dumps( Solver* self )
|
||||
{
|
||||
return PyUnicode_FromString( self->solver.dumps().c_str() );
|
||||
}
|
||||
|
||||
static PyMethodDef
|
||||
Solver_methods[] = {
|
||||
{ "addConstraint", ( PyCFunction )Solver_addConstraint, METH_O,
|
||||
"Add a constraint to the solver." },
|
||||
{ "removeConstraint", ( PyCFunction )Solver_removeConstraint, METH_O,
|
||||
"Remove a constraint from the solver." },
|
||||
{ "hasConstraint", ( PyCFunction )Solver_hasConstraint, METH_O,
|
||||
"Check whether the solver contains a constraint." },
|
||||
{ "addEditVariable", ( PyCFunction )Solver_addEditVariable, METH_VARARGS,
|
||||
"Add an edit variable to the solver." },
|
||||
{ "removeEditVariable", ( PyCFunction )Solver_removeEditVariable, METH_O,
|
||||
"Remove an edit variable from the solver." },
|
||||
{ "hasEditVariable", ( PyCFunction )Solver_hasEditVariable, METH_O,
|
||||
"Check whether the solver contains an edit variable." },
|
||||
{ "suggestValue", ( PyCFunction )Solver_suggestValue, METH_VARARGS,
|
||||
"Suggest a desired value for an edit variable." },
|
||||
{ "updateVariables", ( PyCFunction )Solver_updateVariables, METH_NOARGS,
|
||||
"Update the values of the solver variables." },
|
||||
{ "reset", ( PyCFunction )Solver_reset, METH_NOARGS,
|
||||
"Reset the solver to the initial empty starting condition." },
|
||||
{ "dump", ( PyCFunction )Solver_dump, METH_NOARGS,
|
||||
"Dump a representation of the solver internals to stdout." },
|
||||
{ "dumps", ( PyCFunction )Solver_dumps, METH_NOARGS,
|
||||
"Dump a representation of the solver internals to a string." },
|
||||
{ 0 } // sentinel
|
||||
};
|
||||
|
||||
|
||||
static PyType_Slot Solver_Type_slots[] = {
|
||||
{ Py_tp_dealloc, void_cast( Solver_dealloc ) }, /* tp_dealloc */
|
||||
{ Py_tp_methods, void_cast( Solver_methods ) }, /* tp_methods */
|
||||
{ Py_tp_new, void_cast( Solver_new ) }, /* tp_new */
|
||||
{ Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */
|
||||
{ Py_tp_free, void_cast( PyObject_Del ) }, /* tp_free */
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// Initialize static variables (otherwise the compiler eliminates them)
|
||||
PyTypeObject* Solver::TypeObject = NULL;
|
||||
|
||||
|
||||
PyType_Spec Solver::TypeObject_Spec = {
|
||||
"kiwisolver.Solver", /* tp_name */
|
||||
sizeof( Solver ), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
Py_TPFLAGS_DEFAULT|
|
||||
Py_TPFLAGS_BASETYPE, /* tp_flags */
|
||||
Solver_Type_slots /* slots */
|
||||
};
|
||||
|
||||
|
||||
bool Solver::Ready()
|
||||
{
|
||||
// The reference will be handled by the module to which we will add the type
|
||||
TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) );
|
||||
if( !TypeObject )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
PyObject* DuplicateConstraint;
|
||||
|
||||
PyObject* UnsatisfiableConstraint;
|
||||
|
||||
PyObject* UnknownConstraint;
|
||||
|
||||
PyObject* DuplicateEditVariable;
|
||||
|
||||
PyObject* UnknownEditVariable;
|
||||
|
||||
PyObject* BadRequiredStrength;
|
||||
|
||||
|
||||
bool init_exceptions()
|
||||
{
|
||||
cppy::ptr mod( PyImport_ImportModule( "kiwisolver.exceptions" ) );
|
||||
if( !mod )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DuplicateConstraint = mod.getattr( "DuplicateConstraint" );
|
||||
if( !DuplicateConstraint )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UnsatisfiableConstraint = mod.getattr( "UnsatisfiableConstraint" );
|
||||
if( !UnsatisfiableConstraint )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UnknownConstraint = mod.getattr( "UnknownConstraint" );
|
||||
if( !UnknownConstraint )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DuplicateEditVariable = mod.getattr( "DuplicateEditVariable" );
|
||||
if( !DuplicateEditVariable )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UnknownEditVariable = mod.getattr( "UnknownEditVariable" );
|
||||
if( !UnknownEditVariable )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BadRequiredStrength = mod.getattr( "BadRequiredStrength" );
|
||||
if( !BadRequiredStrength )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
149
kiwi/py/src/strength.cpp
Normal file
149
kiwi/py/src/strength.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) 2013-2019, Nucleic Development Team.
|
||||
|
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|
|
||||
| The full license is in the file LICENSE, distributed with this software.
|
||||
|----------------------------------------------------------------------------*/
|
||||
#include <cppy/cppy.h>
|
||||
#include <kiwi/kiwi.h>
|
||||
#include "util.h"
|
||||
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-writable-strings"
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic ignored "-Wwrite-strings"
|
||||
#endif
|
||||
|
||||
|
||||
namespace kiwisolver
|
||||
{
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
||||
void
|
||||
strength_dealloc( PyObject* self )
|
||||
{
|
||||
Py_TYPE( self )->tp_free( self );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
strength_weak( strength* self )
|
||||
{
|
||||
return PyFloat_FromDouble( kiwi::strength::weak );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
strength_medium( strength* self )
|
||||
{
|
||||
return PyFloat_FromDouble( kiwi::strength::medium );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
strength_strong( strength* self )
|
||||
{
|
||||
return PyFloat_FromDouble( kiwi::strength::strong );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
strength_required( strength* self )
|
||||
{
|
||||
return PyFloat_FromDouble( kiwi::strength::required );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
strength_create( strength* self, PyObject* args )
|
||||
{
|
||||
PyObject* pya;
|
||||
PyObject* pyb;
|
||||
PyObject* pyc;
|
||||
PyObject* pyw = 0;
|
||||
if( !PyArg_ParseTuple( args, "OOO|O", &pya, &pyb, &pyc, &pyw ) )
|
||||
return 0;
|
||||
double a, b, c;
|
||||
double w = 1.0;
|
||||
if( !convert_to_double( pya, a ) )
|
||||
return 0;
|
||||
if( !convert_to_double( pyb, b ) )
|
||||
return 0;
|
||||
if( !convert_to_double( pyc, c ) )
|
||||
return 0;
|
||||
if( pyw && !convert_to_double( pyw, w ) )
|
||||
return 0;
|
||||
return PyFloat_FromDouble( kiwi::strength::create( a, b, c, w ) );
|
||||
}
|
||||
|
||||
|
||||
static PyGetSetDef
|
||||
strength_getset[] = {
|
||||
{ "weak", ( getter )strength_weak, 0,
|
||||
"The predefined weak strength." },
|
||||
{ "medium", ( getter )strength_medium, 0,
|
||||
"The predefined medium strength." },
|
||||
{ "strong", ( getter )strength_strong, 0,
|
||||
"The predefined strong strength." },
|
||||
{ "required", ( getter )strength_required, 0,
|
||||
"The predefined required strength." },
|
||||
{ 0 } // sentinel
|
||||
};
|
||||
|
||||
|
||||
static PyMethodDef
|
||||
strength_methods[] = {
|
||||
{ "create", ( PyCFunction )strength_create, METH_VARARGS,
|
||||
"Create a strength from constituent values and optional weight." },
|
||||
{ 0 } // sentinel
|
||||
};
|
||||
|
||||
|
||||
|
||||
static PyType_Slot strength_Type_slots[] = {
|
||||
{ Py_tp_dealloc, void_cast( strength_dealloc ) }, /* tp_dealloc */
|
||||
{ Py_tp_getset, void_cast( strength_getset ) }, /* tp_getset */
|
||||
{ Py_tp_methods, void_cast( strength_methods ) }, /* tp_methods */
|
||||
{ Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */
|
||||
{ Py_tp_free, void_cast( PyObject_Del ) }, /* tp_free */
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// Initialize static variables (otherwise the compiler eliminates them)
|
||||
PyTypeObject* strength::TypeObject = NULL;
|
||||
|
||||
|
||||
PyType_Spec strength::TypeObject_Spec = {
|
||||
"kiwisolver.Strength", /* tp_name */
|
||||
sizeof( strength ), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
||||
strength_Type_slots /* slots */
|
||||
};
|
||||
|
||||
|
||||
bool strength::Ready()
|
||||
{
|
||||
// The reference will be handled by the module to which we will add the type
|
||||
TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) );
|
||||
if( !TypeObject )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
618
kiwi/py/src/symbolics.h
Normal file
618
kiwi/py/src/symbolics.h
Normal file
@@ -0,0 +1,618 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) 2013-2019, Nucleic Development Team.
|
||||
|
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|
|
||||
| The full license is in the file LICENSE, distributed with this software.
|
||||
|----------------------------------------------------------------------------*/
|
||||
#pragma once
|
||||
#include <cppy/cppy.h>
|
||||
#include "types.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
namespace kiwisolver
|
||||
{
|
||||
|
||||
template<typename Op, typename T>
|
||||
struct UnaryInvoke
|
||||
{
|
||||
PyObject* operator()( PyObject* value )
|
||||
{
|
||||
return Op()( reinterpret_cast<T*>( value ) );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Op, typename T>
|
||||
struct BinaryInvoke
|
||||
{
|
||||
PyObject* operator()( PyObject* first, PyObject* second )
|
||||
{
|
||||
if( T::TypeCheck( first ) )
|
||||
return invoke<Normal>( reinterpret_cast<T*>( first ), second );
|
||||
return invoke<Reverse>( reinterpret_cast<T*>( second ), first );
|
||||
}
|
||||
|
||||
struct Normal
|
||||
{
|
||||
template<typename U>
|
||||
PyObject* operator()( T* primary, U secondary )
|
||||
{
|
||||
return Op()( primary, secondary );
|
||||
}
|
||||
};
|
||||
|
||||
struct Reverse
|
||||
{
|
||||
template<typename U>
|
||||
PyObject* operator()( T* primary, U secondary )
|
||||
{
|
||||
return Op()( secondary, primary );
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Invk>
|
||||
PyObject* invoke( T* primary, PyObject* secondary )
|
||||
{
|
||||
if( Expression::TypeCheck( secondary ) )
|
||||
return Invk()( primary, reinterpret_cast<Expression*>( secondary ) );
|
||||
if( Term::TypeCheck( secondary ) )
|
||||
return Invk()( primary, reinterpret_cast<Term*>( secondary ) );
|
||||
if( Variable::TypeCheck( secondary ) )
|
||||
return Invk()( primary, reinterpret_cast<Variable*>( secondary ) );
|
||||
if( PyFloat_Check( secondary ) )
|
||||
return Invk()( primary, PyFloat_AS_DOUBLE( secondary ) );
|
||||
if( PyLong_Check( secondary ) )
|
||||
{
|
||||
double v = PyLong_AsDouble( secondary );
|
||||
if( v == -1 && PyErr_Occurred() )
|
||||
return 0;
|
||||
return Invk()( primary, v );
|
||||
}
|
||||
Py_RETURN_NOTIMPLEMENTED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct BinaryMul
|
||||
{
|
||||
template<typename T, typename U>
|
||||
PyObject* operator()( T first, U second )
|
||||
{
|
||||
Py_RETURN_NOTIMPLEMENTED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryMul::operator()( Variable* first, double second )
|
||||
{
|
||||
PyObject* pyterm = PyType_GenericNew( Term::TypeObject, 0, 0 );
|
||||
if( !pyterm )
|
||||
return 0;
|
||||
Term* term = reinterpret_cast<Term*>( pyterm );
|
||||
term->variable = cppy::incref( pyobject_cast( first ) );
|
||||
term->coefficient = second;
|
||||
return pyterm;
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryMul::operator()( Term* first, double second )
|
||||
{
|
||||
PyObject* pyterm = PyType_GenericNew( Term::TypeObject, 0, 0 );
|
||||
if( !pyterm )
|
||||
return 0;
|
||||
Term* term = reinterpret_cast<Term*>( pyterm );
|
||||
term->variable = cppy::incref( first->variable );
|
||||
term->coefficient = first->coefficient * second;
|
||||
return pyterm;
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryMul::operator()( Expression* first, double second )
|
||||
{
|
||||
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
|
||||
if( !pyexpr )
|
||||
return 0;
|
||||
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
|
||||
cppy::ptr terms( PyTuple_New( PyTuple_GET_SIZE( first->terms ) ) );
|
||||
if( !terms )
|
||||
return 0;
|
||||
Py_ssize_t end = PyTuple_GET_SIZE( first->terms );
|
||||
for( Py_ssize_t i = 0; i < end; ++i ) // memset 0 for safe error return
|
||||
PyTuple_SET_ITEM( terms.get(), i, 0 );
|
||||
for( Py_ssize_t i = 0; i < end; ++i )
|
||||
{
|
||||
PyObject* item = PyTuple_GET_ITEM( first->terms, i );
|
||||
PyObject* term = BinaryMul()( reinterpret_cast<Term*>( item ), second );
|
||||
if( !term )
|
||||
return 0;
|
||||
PyTuple_SET_ITEM( terms.get(), i, term );
|
||||
}
|
||||
expr->terms = terms.release();
|
||||
expr->constant = first->constant * second;
|
||||
return pyexpr.release();
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryMul::operator()( double first, Variable* second )
|
||||
{
|
||||
return operator()( second, first );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryMul::operator()( double first, Term* second )
|
||||
{
|
||||
return operator()( second, first );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryMul::operator()( double first, Expression* second )
|
||||
{
|
||||
return operator()( second, first );
|
||||
}
|
||||
|
||||
|
||||
struct BinaryDiv
|
||||
{
|
||||
template<typename T, typename U>
|
||||
PyObject* operator()( T first, U second )
|
||||
{
|
||||
Py_RETURN_NOTIMPLEMENTED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryDiv::operator()( Variable* first, double second )
|
||||
{
|
||||
if( second == 0.0 )
|
||||
{
|
||||
PyErr_SetString( PyExc_ZeroDivisionError, "float division by zero" );
|
||||
return 0;
|
||||
}
|
||||
return BinaryMul()( first, 1.0 / second );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryDiv::operator()( Term* first, double second )
|
||||
{
|
||||
if( second == 0.0 )
|
||||
{
|
||||
PyErr_SetString( PyExc_ZeroDivisionError, "float division by zero" );
|
||||
return 0;
|
||||
}
|
||||
return BinaryMul()( first, 1.0 / second );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryDiv::operator()( Expression* first, double second )
|
||||
{
|
||||
if( second == 0.0 )
|
||||
{
|
||||
PyErr_SetString( PyExc_ZeroDivisionError, "float division by zero" );
|
||||
return 0;
|
||||
}
|
||||
return BinaryMul()( first, 1.0 / second );
|
||||
}
|
||||
|
||||
|
||||
struct UnaryNeg
|
||||
{
|
||||
template<typename T>
|
||||
PyObject* operator()( T value )
|
||||
{
|
||||
Py_RETURN_NOTIMPLEMENTED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* UnaryNeg::operator()( Variable* value )
|
||||
{
|
||||
return BinaryMul()( value, -1.0 );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* UnaryNeg::operator()( Term* value )
|
||||
{
|
||||
return BinaryMul()( value, -1.0 );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* UnaryNeg::operator()( Expression* value )
|
||||
{
|
||||
return BinaryMul()( value, -1.0 );
|
||||
}
|
||||
|
||||
|
||||
struct BinaryAdd
|
||||
{
|
||||
template<typename T, typename U>
|
||||
PyObject* operator()( T first, U second )
|
||||
{
|
||||
Py_RETURN_NOTIMPLEMENTED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Expression* first, Expression* second )
|
||||
{
|
||||
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
|
||||
if( !pyexpr )
|
||||
return 0;
|
||||
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
|
||||
expr->constant = first->constant + second->constant;
|
||||
expr->terms = PySequence_Concat( first->terms, second->terms );
|
||||
if( !expr->terms )
|
||||
return 0;
|
||||
return pyexpr.release();
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Expression* first, Term* second )
|
||||
{
|
||||
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
|
||||
if( !pyexpr )
|
||||
return 0;
|
||||
PyObject* terms = PyTuple_New( PyTuple_GET_SIZE( first->terms ) + 1 );
|
||||
if( !terms )
|
||||
return 0;
|
||||
Py_ssize_t end = PyTuple_GET_SIZE( first->terms );
|
||||
for( Py_ssize_t i = 0; i < end; ++i )
|
||||
{
|
||||
PyObject* item = PyTuple_GET_ITEM( first->terms, i );
|
||||
PyTuple_SET_ITEM( terms, i, cppy::incref( item ) );
|
||||
}
|
||||
PyTuple_SET_ITEM( terms, end, cppy::incref( pyobject_cast( second ) ) );
|
||||
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
|
||||
expr->terms = terms;
|
||||
expr->constant = first->constant;
|
||||
return pyexpr.release();
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Expression* first, Variable* second )
|
||||
{
|
||||
cppy::ptr temp( BinaryMul()( second, 1.0 ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return operator()( first, reinterpret_cast<Term*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Expression* first, double second )
|
||||
{
|
||||
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
|
||||
if( !pyexpr )
|
||||
return 0;
|
||||
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
|
||||
expr->terms = cppy::incref( first->terms );
|
||||
expr->constant = first->constant + second;
|
||||
return pyexpr.release();
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Term* first, double second )
|
||||
{
|
||||
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
|
||||
if( !pyexpr )
|
||||
return 0;
|
||||
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
|
||||
expr->constant = second;
|
||||
expr->terms = PyTuple_Pack( 1, first );
|
||||
if( !expr->terms )
|
||||
return 0;
|
||||
return pyexpr.release();
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Term* first, Expression* second )
|
||||
{
|
||||
return operator()( second, first );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Term* first, Term* second )
|
||||
{
|
||||
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
|
||||
if( !pyexpr )
|
||||
return 0;
|
||||
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
|
||||
expr->constant = 0.0;
|
||||
expr->terms = PyTuple_Pack( 2, first, second );
|
||||
if( !expr->terms )
|
||||
return 0;
|
||||
return pyexpr.release();
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Term* first, Variable* second )
|
||||
{
|
||||
cppy::ptr temp( BinaryMul()( second, 1.0 ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Variable* first, double second )
|
||||
{
|
||||
cppy::ptr temp( BinaryMul()( first, 1.0 ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return operator()( reinterpret_cast<Term*>( temp.get() ), second );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Variable* first, Variable* second )
|
||||
{
|
||||
cppy::ptr temp( BinaryMul()( first, 1.0 ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return operator()( reinterpret_cast<Term*>( temp.get() ), second );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Variable* first, Term* second )
|
||||
{
|
||||
cppy::ptr temp( BinaryMul()( first, 1.0 ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return operator()( reinterpret_cast<Term*>( temp.get() ), second );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( Variable* first, Expression* second )
|
||||
{
|
||||
cppy::ptr temp( BinaryMul()( first, 1.0 ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return operator()( reinterpret_cast<Term*>( temp.get() ), second );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( double first, Variable* second )
|
||||
{
|
||||
return operator()( second, first );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( double first, Term* second )
|
||||
{
|
||||
return operator()( second, first );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinaryAdd::operator()( double first, Expression* second )
|
||||
{
|
||||
return operator()( second, first );
|
||||
}
|
||||
|
||||
|
||||
struct BinarySub
|
||||
{
|
||||
template<typename T, typename U>
|
||||
PyObject* operator()( T first, U second )
|
||||
{
|
||||
Py_RETURN_NOTIMPLEMENTED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Variable* first, double second )
|
||||
{
|
||||
return BinaryAdd()( first, -second );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Variable* first, Variable* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Variable* first, Term* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Variable* first, Expression* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Expression*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Term* first, double second )
|
||||
{
|
||||
return BinaryAdd()( first, -second );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Term* first, Variable* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Term* first, Term* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Term* first, Expression* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Expression*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Expression* first, double second )
|
||||
{
|
||||
return BinaryAdd()( first, -second );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Expression* first, Variable* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Expression* first, Term* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( Expression* first, Expression* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Expression*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( double first, Variable* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( double first, Term* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<> inline
|
||||
PyObject* BinarySub::operator()( double first, Expression* second )
|
||||
{
|
||||
cppy::ptr temp( UnaryNeg()( second ) );
|
||||
if( !temp )
|
||||
return 0;
|
||||
return BinaryAdd()( first, reinterpret_cast<Expression*>( temp.get() ) );
|
||||
}
|
||||
|
||||
|
||||
template<typename T, typename U>
|
||||
PyObject* makecn( T first, U second, kiwi::RelationalOperator op )
|
||||
{
|
||||
cppy::ptr pyexpr( BinarySub()( first, second ) );
|
||||
if( !pyexpr )
|
||||
return 0;
|
||||
cppy::ptr pycn( PyType_GenericNew( Constraint::TypeObject, 0, 0 ) );
|
||||
if( !pycn )
|
||||
return 0;
|
||||
Constraint* cn = reinterpret_cast<Constraint*>( pycn.get() );
|
||||
cn->expression = reduce_expression( pyexpr.get() );
|
||||
if( !cn->expression )
|
||||
return 0;
|
||||
kiwi::Expression expr( convert_to_kiwi_expression( cn->expression ) );
|
||||
new( &cn->constraint ) kiwi::Constraint( expr, op, kiwi::strength::required );
|
||||
return pycn.release();
|
||||
}
|
||||
|
||||
|
||||
struct CmpEQ
|
||||
{
|
||||
template<typename T, typename U>
|
||||
PyObject* operator()( T first, U second )
|
||||
{
|
||||
return makecn( first, second, kiwi::OP_EQ );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct CmpLE
|
||||
{
|
||||
template<typename T, typename U>
|
||||
PyObject* operator()( T first, U second )
|
||||
{
|
||||
return makecn( first, second, kiwi::OP_LE );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct CmpGE
|
||||
{
|
||||
template<typename T, typename U>
|
||||
PyObject* operator()( T first, U second )
|
||||
{
|
||||
return makecn( first, second, kiwi::OP_GE );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace kiwisolver
|
||||
229
kiwi/py/src/term.cpp
Normal file
229
kiwi/py/src/term.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) 2013-2019, Nucleic Development Team.
|
||||
|
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|
|
||||
| The full license is in the file LICENSE, distributed with this software.
|
||||
|----------------------------------------------------------------------------*/
|
||||
#include <sstream>
|
||||
#include <cppy/cppy.h>
|
||||
#include "symbolics.h"
|
||||
#include "types.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
namespace kiwisolver
|
||||
{
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_new( PyTypeObject* type, PyObject* args, PyObject* kwargs )
|
||||
{
|
||||
static const char *kwlist[] = { "variable", "coefficient", 0 };
|
||||
PyObject* pyvar;
|
||||
PyObject* pycoeff = 0;
|
||||
if( !PyArg_ParseTupleAndKeywords(
|
||||
args, kwargs, "O|O:__new__", const_cast<char**>( kwlist ),
|
||||
&pyvar, &pycoeff ) )
|
||||
return 0;
|
||||
if( !Variable::TypeCheck( pyvar ) )
|
||||
return cppy::type_error( pyvar, "Variable" );
|
||||
double coefficient = 1.0;
|
||||
if( pycoeff && !convert_to_double( pycoeff, coefficient ) )
|
||||
return 0;
|
||||
PyObject* pyterm = PyType_GenericNew( type, args, kwargs );
|
||||
if( !pyterm )
|
||||
return 0;
|
||||
Term* self = reinterpret_cast<Term*>( pyterm );
|
||||
self->variable = cppy::incref( pyvar );
|
||||
self->coefficient = coefficient;
|
||||
return pyterm;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Term_clear( Term* self )
|
||||
{
|
||||
Py_CLEAR( self->variable );
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
Term_traverse( Term* self, visitproc visit, void* arg )
|
||||
{
|
||||
Py_VISIT( self->variable );
|
||||
#if PY_VERSION_HEX >= 0x03090000
|
||||
// This was not needed before Python 3.9 (Python issue 35810 and 40217)
|
||||
Py_VISIT(Py_TYPE(self));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Term_dealloc( Term* self )
|
||||
{
|
||||
PyObject_GC_UnTrack( self );
|
||||
Term_clear( self );
|
||||
Py_TYPE( self )->tp_free( pyobject_cast( self ) );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_repr( Term* self )
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << self->coefficient << " * ";
|
||||
stream << reinterpret_cast<Variable*>( self->variable )->variable.name();
|
||||
return PyUnicode_FromString( stream.str().c_str() );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_variable( Term* self )
|
||||
{
|
||||
return cppy::incref( self->variable );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_coefficient( Term* self )
|
||||
{
|
||||
return PyFloat_FromDouble( self->coefficient );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_value( Term* self )
|
||||
{
|
||||
Variable* pyvar = reinterpret_cast<Variable*>( self->variable );
|
||||
return PyFloat_FromDouble( self->coefficient * pyvar->variable.value() );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_add( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinaryAdd, Term>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_sub( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinarySub, Term>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_mul( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinaryMul, Term>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_div( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinaryDiv, Term>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_neg( PyObject* value )
|
||||
{
|
||||
return UnaryInvoke<UnaryNeg, Term>()( value );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Term_richcmp( PyObject* first, PyObject* second, int op )
|
||||
{
|
||||
switch( op )
|
||||
{
|
||||
case Py_EQ:
|
||||
return BinaryInvoke<CmpEQ, Term>()( first, second );
|
||||
case Py_LE:
|
||||
return BinaryInvoke<CmpLE, Term>()( first, second );
|
||||
case Py_GE:
|
||||
return BinaryInvoke<CmpGE, Term>()( first, second );
|
||||
default:
|
||||
break;
|
||||
}
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"unsupported operand type(s) for %s: "
|
||||
"'%.100s' and '%.100s'",
|
||||
pyop_str( op ),
|
||||
Py_TYPE( first )->tp_name,
|
||||
Py_TYPE( second )->tp_name
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef
|
||||
Term_methods[] = {
|
||||
{ "variable", ( PyCFunction )Term_variable, METH_NOARGS,
|
||||
"Get the variable for the term." },
|
||||
{ "coefficient", ( PyCFunction )Term_coefficient, METH_NOARGS,
|
||||
"Get the coefficient for the term." },
|
||||
{ "value", ( PyCFunction )Term_value, METH_NOARGS,
|
||||
"Get the value for the term." },
|
||||
{ 0 } // sentinel
|
||||
};
|
||||
|
||||
|
||||
static PyType_Slot Term_Type_slots[] = {
|
||||
{ Py_tp_dealloc, void_cast( Term_dealloc ) }, /* tp_dealloc */
|
||||
{ Py_tp_traverse, void_cast( Term_traverse ) }, /* tp_traverse */
|
||||
{ Py_tp_clear, void_cast( Term_clear ) }, /* tp_clear */
|
||||
{ Py_tp_repr, void_cast( Term_repr ) }, /* tp_repr */
|
||||
{ Py_tp_richcompare, void_cast( Term_richcmp ) }, /* tp_richcompare */
|
||||
{ Py_tp_methods, void_cast( Term_methods ) }, /* tp_methods */
|
||||
{ Py_tp_new, void_cast( Term_new ) }, /* tp_new */
|
||||
{ Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */
|
||||
{ Py_tp_free, void_cast( PyObject_GC_Del ) }, /* tp_free */
|
||||
{ Py_nb_add, void_cast( Term_add ) }, /* nb_add */
|
||||
{ Py_nb_subtract, void_cast( Term_sub ) }, /* nb_subatract */
|
||||
{ Py_nb_multiply, void_cast( Term_mul ) }, /* nb_multiply */
|
||||
{ Py_nb_negative, void_cast( Term_neg ) }, /* nb_negative */
|
||||
{ Py_nb_true_divide, void_cast( Term_div ) }, /* nb_true_divide */
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// Initialize static variables (otherwise the compiler eliminates them)
|
||||
PyTypeObject* Term::TypeObject = NULL;
|
||||
|
||||
|
||||
PyType_Spec Term::TypeObject_Spec = {
|
||||
"kiwisolver.Term", /* tp_name */
|
||||
sizeof( Term ), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
Py_TPFLAGS_DEFAULT|
|
||||
Py_TPFLAGS_HAVE_GC|
|
||||
Py_TPFLAGS_BASETYPE, /* tp_flags */
|
||||
Term_Type_slots /* slots */
|
||||
};
|
||||
|
||||
|
||||
bool Term::Ready()
|
||||
{
|
||||
// The reference will be handled by the module to which we will add the type
|
||||
TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) );
|
||||
if( !TypeObject )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace kiwisolver
|
||||
138
kiwi/py/src/types.h
Normal file
138
kiwi/py/src/types.h
Normal file
@@ -0,0 +1,138 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) 2013-2019, Nucleic Development Team.
|
||||
|
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|
|
||||
| The full license is in the file LICENSE, distributed with this software.
|
||||
|----------------------------------------------------------------------------*/
|
||||
#pragma once
|
||||
#include <Python.h>
|
||||
#include <kiwi/kiwi.h>
|
||||
|
||||
|
||||
namespace kiwisolver
|
||||
{
|
||||
|
||||
extern PyObject* DuplicateConstraint;
|
||||
|
||||
extern PyObject* UnsatisfiableConstraint;
|
||||
|
||||
extern PyObject* UnknownConstraint;
|
||||
|
||||
extern PyObject* DuplicateEditVariable;
|
||||
|
||||
extern PyObject* UnknownEditVariable;
|
||||
|
||||
extern PyObject* BadRequiredStrength;
|
||||
|
||||
|
||||
struct strength
|
||||
{
|
||||
PyObject_HEAD;
|
||||
|
||||
static PyType_Spec TypeObject_Spec;
|
||||
|
||||
static PyTypeObject* TypeObject;
|
||||
|
||||
static bool Ready();
|
||||
};
|
||||
|
||||
|
||||
struct Variable
|
||||
{
|
||||
PyObject_HEAD
|
||||
PyObject* context;
|
||||
kiwi::Variable variable;
|
||||
|
||||
static PyType_Spec TypeObject_Spec;
|
||||
|
||||
static PyTypeObject* TypeObject;
|
||||
|
||||
static bool Ready();
|
||||
|
||||
static bool TypeCheck( PyObject* obj )
|
||||
{
|
||||
return PyObject_TypeCheck( obj, TypeObject ) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Term
|
||||
{
|
||||
PyObject_HEAD
|
||||
PyObject* variable;
|
||||
double coefficient;
|
||||
|
||||
static PyType_Spec TypeObject_Spec;
|
||||
|
||||
static PyTypeObject* TypeObject;
|
||||
|
||||
static bool Ready();
|
||||
|
||||
static bool TypeCheck( PyObject* obj )
|
||||
{
|
||||
return PyObject_TypeCheck( obj, TypeObject ) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Expression
|
||||
{
|
||||
PyObject_HEAD
|
||||
PyObject* terms;
|
||||
double constant;
|
||||
|
||||
static PyType_Spec TypeObject_Spec;
|
||||
|
||||
static PyTypeObject* TypeObject;
|
||||
|
||||
static bool Ready();
|
||||
|
||||
static bool TypeCheck( PyObject* obj )
|
||||
{
|
||||
return PyObject_TypeCheck( obj, TypeObject ) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Constraint
|
||||
{
|
||||
PyObject_HEAD
|
||||
PyObject* expression;
|
||||
kiwi::Constraint constraint;
|
||||
|
||||
static PyType_Spec TypeObject_Spec;
|
||||
|
||||
static PyTypeObject* TypeObject;
|
||||
|
||||
static bool Ready();
|
||||
|
||||
static bool TypeCheck( PyObject* obj )
|
||||
{
|
||||
return PyObject_TypeCheck( obj, TypeObject ) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Solver
|
||||
{
|
||||
PyObject_HEAD
|
||||
kiwi::Solver solver;
|
||||
|
||||
static PyType_Spec TypeObject_Spec;
|
||||
|
||||
static PyTypeObject* TypeObject;
|
||||
|
||||
static bool Ready();
|
||||
|
||||
static bool TypeCheck( PyObject* obj )
|
||||
{
|
||||
return PyObject_TypeCheck( obj, TypeObject ) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
bool init_exceptions();
|
||||
|
||||
|
||||
} // namespace kiwisolver
|
||||
203
kiwi/py/src/util.h
Normal file
203
kiwi/py/src/util.h
Normal file
@@ -0,0 +1,203 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) 2013-2019, Nucleic Development Team.
|
||||
|
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|
|
||||
| The full license is in the file LICENSE, distributed with this software.
|
||||
|----------------------------------------------------------------------------*/
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cppy/cppy.h>
|
||||
#include <kiwi/kiwi.h>
|
||||
#include "types.h"
|
||||
|
||||
|
||||
namespace kiwisolver
|
||||
{
|
||||
|
||||
inline bool
|
||||
convert_to_double( PyObject* obj, double& out )
|
||||
{
|
||||
if( PyFloat_Check( obj ) )
|
||||
{
|
||||
out = PyFloat_AS_DOUBLE( obj );
|
||||
return true;
|
||||
}
|
||||
if( PyLong_Check( obj ) )
|
||||
{
|
||||
out = PyLong_AsDouble( obj );
|
||||
if( out == -1.0 && PyErr_Occurred() )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
cppy::type_error( obj, "float, int, or long" );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
inline bool
|
||||
convert_pystr_to_str( PyObject* value, std::string& out )
|
||||
{
|
||||
out = PyUnicode_AsUTF8( value );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
inline bool
|
||||
convert_to_strength( PyObject* value, double& out )
|
||||
{
|
||||
if( PyUnicode_Check( value ) )
|
||||
{
|
||||
std::string str;
|
||||
if( !convert_pystr_to_str( value, str ) )
|
||||
return false;
|
||||
if( str == "required" )
|
||||
out = kiwi::strength::required;
|
||||
else if( str == "strong" )
|
||||
out = kiwi::strength::strong;
|
||||
else if( str == "medium" )
|
||||
out = kiwi::strength::medium;
|
||||
else if( str == "weak" )
|
||||
out = kiwi::strength::weak;
|
||||
else
|
||||
{
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"string strength must be 'required', 'strong', 'medium', "
|
||||
"or 'weak', not '%s'",
|
||||
str.c_str()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if( !convert_to_double( value, out ) )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
inline bool
|
||||
convert_to_relational_op( PyObject* value, kiwi::RelationalOperator& out )
|
||||
{
|
||||
if( !PyUnicode_Check( value ) )
|
||||
{
|
||||
cppy::type_error( value, "str" );
|
||||
return false;
|
||||
}
|
||||
std::string str;
|
||||
if( !convert_pystr_to_str( value, str ) )
|
||||
return false;
|
||||
if( str == "==" )
|
||||
out = kiwi::OP_EQ;
|
||||
else if( str == "<=" )
|
||||
out = kiwi::OP_LE;
|
||||
else if( str == ">=" )
|
||||
out = kiwi::OP_GE;
|
||||
else
|
||||
{
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"relational operator must be '==', '<=', or '>=', not '%s'",
|
||||
str.c_str()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
inline PyObject*
|
||||
make_terms( const std::map<PyObject*, double>& coeffs )
|
||||
{
|
||||
typedef std::map<PyObject*, double>::const_iterator iter_t;
|
||||
cppy::ptr terms( PyTuple_New( coeffs.size() ) );
|
||||
if( !terms )
|
||||
return 0;
|
||||
Py_ssize_t size = PyTuple_GET_SIZE( terms.get() );
|
||||
for( Py_ssize_t i = 0; i < size; ++i ) // zero tuple for safe early return
|
||||
PyTuple_SET_ITEM( terms.get(), i, 0 );
|
||||
Py_ssize_t i = 0;
|
||||
iter_t it = coeffs.begin();
|
||||
iter_t end = coeffs.end();
|
||||
for( ; it != end; ++it, ++i )
|
||||
{
|
||||
PyObject* pyterm = PyType_GenericNew( Term::TypeObject, 0, 0 );
|
||||
if( !pyterm )
|
||||
return 0;
|
||||
Term* term = reinterpret_cast<Term*>( pyterm );
|
||||
term->variable = cppy::incref( it->first );
|
||||
term->coefficient = it->second;
|
||||
PyTuple_SET_ITEM( terms.get(), i, pyterm );
|
||||
}
|
||||
return terms.release();
|
||||
}
|
||||
|
||||
|
||||
inline PyObject*
|
||||
reduce_expression( PyObject* pyexpr ) // pyexpr must be an Expression
|
||||
{
|
||||
Expression* expr = reinterpret_cast<Expression*>( pyexpr );
|
||||
std::map<PyObject*, double> coeffs;
|
||||
Py_ssize_t size = PyTuple_GET_SIZE( expr->terms );
|
||||
for( Py_ssize_t i = 0; i < size; ++i )
|
||||
{
|
||||
PyObject* item = PyTuple_GET_ITEM( expr->terms, i );
|
||||
Term* term = reinterpret_cast<Term*>( item );
|
||||
coeffs[ term->variable ] += term->coefficient;
|
||||
}
|
||||
cppy::ptr terms( make_terms( coeffs ) );
|
||||
if( !terms )
|
||||
return 0;
|
||||
PyObject* pynewexpr = PyType_GenericNew( Expression::TypeObject, 0, 0 );
|
||||
if( !pynewexpr )
|
||||
return 0;
|
||||
Expression* newexpr = reinterpret_cast<Expression*>( pynewexpr );
|
||||
newexpr->terms = terms.release();
|
||||
newexpr->constant = expr->constant;
|
||||
return pynewexpr;
|
||||
}
|
||||
|
||||
|
||||
inline kiwi::Expression
|
||||
convert_to_kiwi_expression( PyObject* pyexpr ) // pyexpr must be an Expression
|
||||
{
|
||||
Expression* expr = reinterpret_cast<Expression*>( pyexpr );
|
||||
std::vector<kiwi::Term> kterms;
|
||||
Py_ssize_t size = PyTuple_GET_SIZE( expr->terms );
|
||||
for( Py_ssize_t i = 0; i < size; ++i )
|
||||
{
|
||||
PyObject* item = PyTuple_GET_ITEM( expr->terms, i );
|
||||
Term* term = reinterpret_cast<Term*>( item );
|
||||
Variable* var = reinterpret_cast<Variable*>( term->variable );
|
||||
kterms.push_back( kiwi::Term( var->variable, term->coefficient ) );
|
||||
}
|
||||
return kiwi::Expression( kterms, expr->constant );
|
||||
}
|
||||
|
||||
|
||||
inline const char*
|
||||
pyop_str( int op )
|
||||
{
|
||||
switch( op )
|
||||
{
|
||||
case Py_LT:
|
||||
return "<";
|
||||
case Py_LE:
|
||||
return "<=";
|
||||
case Py_EQ:
|
||||
return "==";
|
||||
case Py_NE:
|
||||
return "!=";
|
||||
case Py_GT:
|
||||
return ">";
|
||||
case Py_GE:
|
||||
return ">=";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace kiwisolver
|
||||
270
kiwi/py/src/variable.cpp
Normal file
270
kiwi/py/src/variable.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) 2013-2019, Nucleic Development Team.
|
||||
|
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|
|
||||
| The full license is in the file LICENSE, distributed with this software.
|
||||
|----------------------------------------------------------------------------*/
|
||||
#include <cppy/cppy.h>
|
||||
#include <kiwi/kiwi.h>
|
||||
#include "symbolics.h"
|
||||
#include "types.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
namespace kiwisolver
|
||||
{
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_new( PyTypeObject* type, PyObject* args, PyObject* kwargs )
|
||||
{
|
||||
static const char *kwlist[] = { "name", "context", 0 };
|
||||
PyObject* context = 0;
|
||||
PyObject* name = 0;
|
||||
|
||||
if( !PyArg_ParseTupleAndKeywords(
|
||||
args, kwargs, "|OO:__new__", const_cast<char**>( kwlist ),
|
||||
&name, &context ) )
|
||||
return 0;
|
||||
|
||||
cppy::ptr pyvar( PyType_GenericNew( type, args, kwargs ) );
|
||||
if( !pyvar )
|
||||
return 0;
|
||||
|
||||
Variable* self = reinterpret_cast<Variable*>( pyvar.get() );
|
||||
self->context = cppy::xincref( context );
|
||||
|
||||
if( name != 0 )
|
||||
{
|
||||
if( !PyUnicode_Check( name ) )
|
||||
return cppy::type_error( name, "str" );
|
||||
std::string c_name;
|
||||
if( !convert_pystr_to_str(name, c_name) )
|
||||
return 0; // LCOV_EXCL_LINE
|
||||
new( &self->variable ) kiwi::Variable( c_name );
|
||||
}
|
||||
else
|
||||
{
|
||||
new( &self->variable ) kiwi::Variable();
|
||||
}
|
||||
|
||||
return pyvar.release();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Variable_clear( Variable* self )
|
||||
{
|
||||
Py_CLEAR( self->context );
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
Variable_traverse( Variable* self, visitproc visit, void* arg )
|
||||
{
|
||||
Py_VISIT( self->context );
|
||||
#if PY_VERSION_HEX >= 0x03090000
|
||||
// This was not needed before Python 3.9 (Python issue 35810 and 40217)
|
||||
Py_VISIT(Py_TYPE(self));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Variable_dealloc( Variable* self )
|
||||
{
|
||||
PyObject_GC_UnTrack( self );
|
||||
Variable_clear( self );
|
||||
self->variable.~Variable();
|
||||
Py_TYPE( self )->tp_free( pyobject_cast( self ) );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_repr( Variable* self )
|
||||
{
|
||||
return PyUnicode_FromString( self->variable.name().c_str() );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_name( Variable* self )
|
||||
{
|
||||
return PyUnicode_FromString( self->variable.name().c_str() );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_setName( Variable* self, PyObject* pystr )
|
||||
{
|
||||
if( !PyUnicode_Check( pystr ) )
|
||||
return cppy::type_error( pystr, "str" );
|
||||
std::string str;
|
||||
if( !convert_pystr_to_str( pystr, str ) )
|
||||
return 0;
|
||||
self->variable.setName( str );
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_context( Variable* self )
|
||||
{
|
||||
if( self->context )
|
||||
return cppy::incref( self->context );
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_setContext( Variable* self, PyObject* value )
|
||||
{
|
||||
if( value != self->context )
|
||||
{
|
||||
PyObject* temp = self->context;
|
||||
self->context = cppy::incref( value );
|
||||
Py_XDECREF( temp );
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_value( Variable* self )
|
||||
{
|
||||
return PyFloat_FromDouble( self->variable.value() );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_add( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinaryAdd, Variable>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_sub( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinarySub, Variable>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_mul( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinaryMul, Variable>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_div( PyObject* first, PyObject* second )
|
||||
{
|
||||
return BinaryInvoke<BinaryDiv, Variable>()( first, second );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_neg( PyObject* value )
|
||||
{
|
||||
return UnaryInvoke<UnaryNeg, Variable>()( value );
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
Variable_richcmp( PyObject* first, PyObject* second, int op )
|
||||
{
|
||||
switch( op )
|
||||
{
|
||||
case Py_EQ:
|
||||
return BinaryInvoke<CmpEQ, Variable>()( first, second );
|
||||
case Py_LE:
|
||||
return BinaryInvoke<CmpLE, Variable>()( first, second );
|
||||
case Py_GE:
|
||||
return BinaryInvoke<CmpGE, Variable>()( first, second );
|
||||
default:
|
||||
break;
|
||||
}
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"unsupported operand type(s) for %s: "
|
||||
"'%.100s' and '%.100s'",
|
||||
pyop_str( op ),
|
||||
Py_TYPE( first )->tp_name,
|
||||
Py_TYPE( second )->tp_name
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef
|
||||
Variable_methods[] = {
|
||||
{ "name", ( PyCFunction )Variable_name, METH_NOARGS,
|
||||
"Get the name of the variable." },
|
||||
{ "setName", ( PyCFunction )Variable_setName, METH_O,
|
||||
"Set the name of the variable." },
|
||||
{ "context", ( PyCFunction )Variable_context, METH_NOARGS,
|
||||
"Get the context object associated with the variable." },
|
||||
{ "setContext", ( PyCFunction )Variable_setContext, METH_O,
|
||||
"Set the context object associated with the variable." },
|
||||
{ "value", ( PyCFunction )Variable_value, METH_NOARGS,
|
||||
"Get the current value of the variable." },
|
||||
{ 0 } // sentinel
|
||||
};
|
||||
|
||||
|
||||
static PyType_Slot Variable_Type_slots[] = {
|
||||
{ Py_tp_dealloc, void_cast( Variable_dealloc ) }, /* tp_dealloc */
|
||||
{ Py_tp_traverse, void_cast( Variable_traverse ) }, /* tp_traverse */
|
||||
{ Py_tp_clear, void_cast( Variable_clear ) }, /* tp_clear */
|
||||
{ Py_tp_repr, void_cast( Variable_repr ) }, /* tp_repr */
|
||||
{ Py_tp_richcompare, void_cast( Variable_richcmp ) }, /* tp_richcompare */
|
||||
{ Py_tp_methods, void_cast( Variable_methods ) }, /* tp_methods */
|
||||
{ Py_tp_new, void_cast( Variable_new ) }, /* tp_new */
|
||||
{ Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */
|
||||
{ Py_tp_free, void_cast( PyObject_GC_Del ) }, /* tp_free */
|
||||
{ Py_nb_add, void_cast( Variable_add ) }, /* nb_add */
|
||||
{ Py_nb_subtract, void_cast( Variable_sub ) }, /* nb_subtract */
|
||||
{ Py_nb_multiply, void_cast( Variable_mul ) }, /* nb_multiply */
|
||||
{ Py_nb_negative, void_cast( Variable_neg ) }, /* nb_negative */
|
||||
{ Py_nb_true_divide, void_cast( Variable_div ) }, /* nb_true_divide */
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// Initialize static variables (otherwise the compiler eliminates them)
|
||||
PyTypeObject* Variable::TypeObject = NULL;
|
||||
|
||||
|
||||
PyType_Spec Variable::TypeObject_Spec = {
|
||||
"kiwisolver.Variable", /* tp_name */
|
||||
sizeof( Variable ), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
Py_TPFLAGS_DEFAULT|
|
||||
Py_TPFLAGS_HAVE_GC|
|
||||
Py_TPFLAGS_BASETYPE, /* tp_flags */
|
||||
Variable_Type_slots /* slots */
|
||||
};
|
||||
|
||||
|
||||
bool Variable::Ready()
|
||||
{
|
||||
// The reference will be handled by the module to which we will add the type
|
||||
TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) );
|
||||
if( !TypeObject )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace kiwisolver
|
||||
84
kiwi/py/tests/test_constraint.py
Normal file
84
kiwi/py/tests/test_constraint.py
Normal 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
kiwi/py/tests/test_expression.py
Normal file
271
kiwi/py/tests/test_expression.py
Normal 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
kiwi/py/tests/test_solver.py
Normal file
295
kiwi/py/tests/test_solver.py
Normal 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
kiwi/py/tests/test_strength.py
Normal file
27
kiwi/py/tests/test_strength.py
Normal 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
kiwi/py/tests/test_term.py
Normal file
202
kiwi/py/tests/test_term.py
Normal 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
kiwi/py/tests/test_variable.py
Normal file
163
kiwi/py/tests/test_variable.py
Normal 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)
|
||||
Reference in New Issue
Block a user