diff --git a/docs/source/extending.rst b/docs/source/extending.rst index f147e36..92ae927 100644 --- a/docs/source/extending.rst +++ b/docs/source/extending.rst @@ -104,7 +104,7 @@ Adding Constraints New constraints are introduced by subclassing :class:`Constraint ` and implementing the :func:`instantiate ` method. This method should return -a list of ``ilpy.LinearConstraint``. +a list of ``ilpy.Constraint``. Imagine we know precisely that we want to track at most :math:`k` objects, but we don't know beforehand which of the many objects in the track graph those @@ -128,7 +128,7 @@ This can be done with a constraint as follows: appear_indicators = solver.get_variables(NodeAppear) - constraint = ilpy.LinearConstraint() + constraint = ilpy.Constraint() for appear_indicator, index in appear_indicators.items(): constraint.set_coefficient(index, 1.0) constraint.set_relation(ilpy.Relation.LessEqual) @@ -301,7 +301,7 @@ The complete variable declaration looks like this: out_edge_index = edge_indicators[out_edge] # edge pair indicator = 1 <=> in edge = 1 and out edge = 1 - constraint = ilpy.LinearConstraint() + constraint = ilpy.Constraint() constraint.set_coefficient(pair_index, 2) constraint.set_coefficient(in_edge_index, -1) constraint.set_coefficient(out_edge_index, -1) @@ -309,7 +309,7 @@ The complete variable declaration looks like this: constraint.set_value(0) constraints.append(constraint) - constraint = ilpy.LinearConstraint() + constraint = ilpy.Constraint() constraint.set_coefficient(pair_index, -1) constraint.set_coefficient(in_edge_index, 1) constraint.set_coefficient(out_edge_index, 1) diff --git a/motile/constraints/constraint.py b/motile/constraints/constraint.py index 721cf54..8dc3b2c 100644 --- a/motile/constraints/constraint.py +++ b/motile/constraints/constraint.py @@ -11,7 +11,7 @@ class Constraint(ABC): @abstractmethod - def instantiate(self, solver: Solver) -> Iterable[ilpy.LinearConstraint]: + def instantiate(self, solver: Solver) -> Iterable[ilpy.Constraint]: """Create and return specific linear constraints for the given solver. Args: @@ -21,5 +21,5 @@ def instantiate(self, solver: Solver) -> Iterable[ilpy.LinearConstraint]: Returns: - An iterable of :class:`ilpy.LinearConstraint`. + An iterable of :class:`ilpy.Constraint`. """ diff --git a/motile/constraints/max_children.py b/motile/constraints/max_children.py index 612a559..2782702 100644 --- a/motile/constraints/max_children.py +++ b/motile/constraints/max_children.py @@ -30,11 +30,11 @@ class MaxChildren(Constraint): def __init__(self, max_children: int) -> None: self.max_children = max_children - def instantiate(self, solver: Solver) -> Iterable[ilpy.LinearConstraint]: + def instantiate(self, solver: Solver) -> Iterable[ilpy.Constraint]: edge_indicators = solver.get_variables(EdgeSelected) for node in solver.graph.nodes: - constraint = ilpy.LinearConstraint() + constraint = ilpy.Constraint() # all outgoing edges for edge in solver.graph.next_edges[node]: diff --git a/motile/constraints/max_parents.py b/motile/constraints/max_parents.py index 33c2768..b261017 100644 --- a/motile/constraints/max_parents.py +++ b/motile/constraints/max_parents.py @@ -30,11 +30,11 @@ class MaxParents(Constraint): def __init__(self, max_parents: int) -> None: self.max_parents = max_parents - def instantiate(self, solver: Solver) -> Iterable[ilpy.LinearConstraint]: + def instantiate(self, solver: Solver) -> Iterable[ilpy.Constraint]: edge_indicators = solver.get_variables(EdgeSelected) for node in solver.graph.nodes: - constraint = ilpy.LinearConstraint() + constraint = ilpy.Constraint() # all incoming edges for edge in solver.graph.prev_edges[node]: diff --git a/motile/constraints/pin.py b/motile/constraints/pin.py index abc6f1b..1acf4da 100644 --- a/motile/constraints/pin.py +++ b/motile/constraints/pin.py @@ -33,7 +33,7 @@ class Pin(Constraint): def __init__(self, attribute: str) -> None: self.attribute = attribute - def instantiate(self, solver: Solver) -> list[ilpy.LinearConstraint]: + def instantiate(self, solver: Solver) -> list[ilpy.Constraint]: node_indicators = solver.get_variables(NodeSelected) edge_indicators = solver.get_variables(EdgeSelected) @@ -57,8 +57,8 @@ def instantiate(self, solver: Solver) -> list[ilpy.LinearConstraint]: if self.attribute in attributes and not attributes[self.attribute] ] - must_select_constraint = ilpy.LinearConstraint() - must_not_select_constraint = ilpy.LinearConstraint() + must_select_constraint = ilpy.Constraint() + must_not_select_constraint = ilpy.Constraint() for index in must_select: must_select_constraint.set_coefficient(index, 1) diff --git a/motile/constraints/select_edge_nodes.py b/motile/constraints/select_edge_nodes.py index 93d08ad..d05c016 100644 --- a/motile/constraints/select_edge_nodes.py +++ b/motile/constraints/select_edge_nodes.py @@ -24,7 +24,7 @@ class SelectEdgeNodes(Constraint): This constraint will be added by default to any :class:`Solver` instance. """ - def instantiate(self, solver: Solver) -> Iterable[ilpy.LinearConstraint]: + def instantiate(self, solver: Solver) -> Iterable[ilpy.Constraint]: node_indicators = solver.get_variables(NodeSelected) edge_indicators = solver.get_variables(EdgeSelected) @@ -34,7 +34,7 @@ def instantiate(self, solver: Solver) -> Iterable[ilpy.LinearConstraint]: ind_e = edge_indicators[edge] nodes_ind = [node_indicators[node] for node in nodes] - constraint = ilpy.LinearConstraint() + constraint = ilpy.Constraint() constraint.set_coefficient(ind_e, len(nodes_ind)) for node_ind in nodes_ind: constraint.set_coefficient(node_ind, -1) diff --git a/motile/solver.py b/motile/solver.py index d62dc5c..fccc6d7 100644 --- a/motile/solver.py +++ b/motile/solver.py @@ -12,7 +12,6 @@ from .ssvm import fit_weights logger = logging.getLogger(__name__) -ILPY_V03 = ilpy.__version__.split(".")[:2] >= ["0", "3"] if TYPE_CHECKING: from motile.costs import Costs @@ -48,9 +47,9 @@ def __init__( self._weights_changed = True self.features = Features() - self.ilp_solver: ilpy.LinearSolver | None = None - self.objective: ilpy.LinearObjective | None = None - self.constraints = ilpy.LinearConstraints() + self.ilp_solver: ilpy.Solver | None = None + self.objective: ilpy.Objective | None = None + self.constraints = ilpy.Constraints() self.num_variables: int = 0 self._costs = np.zeros((0,), dtype=np.float32) @@ -131,13 +130,13 @@ def solve(self, timeout: float = 0.0, num_threads: int = 1) -> ilpy.Solution: vector. """ - self.objective = ilpy.LinearObjective(self.num_variables) + self.objective = ilpy.Objective(self.num_variables) for i, c in enumerate(self.costs): logger.debug("Setting cost of var %d to %.3f", i, c) self.objective.set_coefficient(i, c) # TODO: support other variable types - self.ilp_solver = ilpy.LinearSolver( + self.ilp_solver = ilpy.Solver( self.num_variables, ilpy.VariableType.Binary, variable_types=self.variable_types, @@ -153,13 +152,8 @@ def solve(self, timeout: float = 0.0, num_threads: int = 1) -> ilpy.Solution: self.ilp_solver.set_verbose(False) - solution = self.ilp_solver.solve() - - if ILPY_V03: - self.solution, message = solution, solution.get_status() - else: - self.solution, message = solution - if message: + self.solution = self.ilp_solver.solve() + if message := self.solution.get_status(): logger.info("ILP solver returned with: %s", message) return self.solution diff --git a/motile/variables/node_appear.py b/motile/variables/node_appear.py index 08ac791..63e01af 100644 --- a/motile/variables/node_appear.py +++ b/motile/variables/node_appear.py @@ -39,7 +39,7 @@ def instantiate(solver: Solver) -> Collection[NodeId]: return solver.graph.nodes @staticmethod - def instantiate_constraints(solver: Solver) -> Iterable[ilpy.LinearConstraint]: + def instantiate_constraints(solver: Solver) -> Iterable[ilpy.Constraint]: appear_indicators = solver.get_variables(NodeAppear) node_indicators = solver.get_variables(NodeSelected) edge_indicators = solver.get_variables(EdgeSelected) @@ -51,7 +51,7 @@ def instantiate_constraints(solver: Solver) -> Iterable[ilpy.LinearConstraint]: if num_prev_edges == 0: # special case: no incoming edges, appear indicator is equal to # selection indicator - constraint = ilpy.LinearConstraint() + constraint = ilpy.Constraint() constraint.set_coefficient(node_indicators[node], 1.0) constraint.set_coefficient(appear_indicators[node], -1.0) constraint.set_relation(ilpy.Relation.Equal) @@ -72,8 +72,8 @@ def instantiate_constraints(solver: Solver) -> Iterable[ilpy.LinearConstraint]: # (1) s - appear <= num_prev - 1 # (2) s - appear * num_prev >= 0 - constraint1 = ilpy.LinearConstraint() - constraint2 = ilpy.LinearConstraint() + constraint1 = ilpy.Constraint() + constraint2 = ilpy.Constraint() # set s for both constraints: diff --git a/motile/variables/node_split.py b/motile/variables/node_split.py index 374f01f..8a68d13 100644 --- a/motile/variables/node_split.py +++ b/motile/variables/node_split.py @@ -36,7 +36,7 @@ def instantiate(solver: Solver) -> Collection[NodeId]: return solver.graph.nodes @staticmethod - def instantiate_constraints(solver: Solver) -> Iterable[ilpy.LinearConstraint]: + def instantiate_constraints(solver: Solver) -> Iterable[ilpy.Constraint]: split_indicators = solver.get_variables(NodeSplit) edge_indicators = solver.get_variables(EdgeSelected) @@ -53,8 +53,8 @@ def instantiate_constraints(solver: Solver) -> Iterable[ilpy.LinearConstraint]: # (1) 2 * split - sum(next_selected) <= 0 # (2) (num_next - 1) * split - sum(next_selected) >= -1 - constraint1 = ilpy.LinearConstraint() - constraint2 = ilpy.LinearConstraint() + constraint1 = ilpy.Constraint() + constraint2 = ilpy.Constraint() constraint1.set_coefficient(split_indicators[node], 2.0) constraint2.set_coefficient(split_indicators[node], len(next_edges) - 1.0) diff --git a/motile/variables/variable.py b/motile/variables/variable.py index 44a99c9..e10bbd6 100644 --- a/motile/variables/variable.py +++ b/motile/variables/variable.py @@ -89,7 +89,7 @@ def instantiate(solver): pass @staticmethod - def instantiate_constraints(solver: Solver) -> Iterable[ilpy.LinearConstraint]: + def instantiate_constraints(solver: Solver) -> Iterable[ilpy.Constraint]: """Add linear constraints to the solver to ensure that these variables are coupled to other variables of the solver. @@ -100,7 +100,7 @@ def instantiate_constraints(solver: Solver) -> Iterable[ilpy.LinearConstraint]: Returns: - A iterable of :class:`ilpy.LinearConstraint`. See + A iterable of :class:`ilpy.Constraint`. See :class:`motile.constraints.Constraint` for how to create linear constraints. """ diff --git a/pyproject.toml b/pyproject.toml index 5f7a3d7..7381183 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ authors = [ { name = 'Florian Jug', email = 'florian.jug@fht.org' }, ] dynamic = ["version"] -dependencies = ['networkx', 'ilpy', 'numpy', 'structsvm'] +dependencies = ['networkx', 'ilpy>=0.3.0', 'numpy', 'structsvm'] [project.optional-dependencies] dev = ["pre-commit", "pytest", "pytest-cov", "ruff", "twine", "build"] @@ -50,6 +50,15 @@ select = [ "RUF", # ruff specific rules ] +# https://docs.pytest.org/en/6.2.x/customize.html +[tool.pytest.ini_options] +minversion = "6.0" +testpaths = ["tests"] +filterwarnings = [ + "error:::motile", + "ignore:ilpy.*:DeprecationWarning:structsvm", +] + # https://coverage.readthedocs.io/en/6.4/config.html [tool.coverage.report] exclude_lines = [ @@ -61,8 +70,8 @@ exclude_lines = [ [tool.mypy] files = "motile" -strict = true # feel free to relax this if it's annoying -allow_untyped_defs = true # TODO: can eventually fill out typing and remove this +strict = true # feel free to relax this if it's annoying +allow_untyped_defs = true # TODO: can eventually fill out typing and remove this # allow_untyped_calls = true # TODO: can eventually fill out typing and remove this disallow_any_generics = false ignore_missing_imports = true