From f59883548b51150e9bb729d8beeb79e720c0f3f0 Mon Sep 17 00:00:00 2001 From: Caroline Malin-Mayor Date: Wed, 24 Jul 2024 13:16:24 -0400 Subject: [PATCH 1/3] Rename to add_cost and add_constraint in src and tests --- motile/costs/__init__.py | 4 +-- motile/costs/appear.py | 10 +++---- motile/costs/{costs.py => cost.py} | 8 +++--- motile/costs/disappear.py | 8 +++--- motile/costs/edge_distance.py | 8 +++--- motile/costs/edge_selection.py | 14 ++++----- motile/costs/node_selection.py | 14 ++++----- motile/costs/split.py | 8 +++--- motile/solver.py | 46 +++++++++++++++--------------- motile/variables/variable.py | 2 +- tests/test_api.py | 14 ++++----- tests/test_constraints.py | 24 ++++++++-------- tests/test_costs.py | 28 +++++++++--------- tests/test_plot.py | 8 +++--- tests/test_structsvm.py | 24 ++++++++-------- 15 files changed, 110 insertions(+), 110 deletions(-) rename motile/costs/{costs.py => cost.py} (67%) diff --git a/motile/costs/__init__.py b/motile/costs/__init__.py index 49abea1..9912c0b 100644 --- a/motile/costs/__init__.py +++ b/motile/costs/__init__.py @@ -1,5 +1,5 @@ from .appear import Appear -from .costs import Costs +from .cost import Cost from .disappear import Disappear from .edge_distance import EdgeDistance from .edge_selection import EdgeSelection @@ -11,7 +11,7 @@ __all__ = [ "Appear", - "Costs", + "Cost", "Disappear", "EdgeDistance", "EdgeSelection", diff --git a/motile/costs/appear.py b/motile/costs/appear.py index e9a344b..fa4e151 100644 --- a/motile/costs/appear.py +++ b/motile/costs/appear.py @@ -3,22 +3,22 @@ from typing import TYPE_CHECKING from ..variables import NodeAppear -from .costs import Costs +from .cost import Cost from .weight import Weight if TYPE_CHECKING: from motile.solver import Solver -class Appear(Costs): - """Costs for :class:`~motile.variables.NodeAppear` variables. +class Appear(Cost): + """Cost for :class:`~motile.variables.NodeAppear` variables. Args: weight: The weight to apply to the cost of each starting track. attribute: - The name of the attribute to use to look up costs. Default is + The name of the attribute to use to look up cost. Default is ``None``, which means that a constant cost is used. constant: @@ -26,7 +26,7 @@ class Appear(Costs): ignore_attribute: The name of an optional node attribute that, if it is set and - evaluates to ``True``, will not set the appear costs for that node. + evaluates to ``True``, will not set the appear cost for that node. This is useful to allow nodes in the first frame to appear at no cost. """ diff --git a/motile/costs/costs.py b/motile/costs/cost.py similarity index 67% rename from motile/costs/costs.py rename to motile/costs/cost.py index 0f31067..513f21f 100644 --- a/motile/costs/costs.py +++ b/motile/costs/cost.py @@ -7,18 +7,18 @@ from motile.solver import Solver -class Costs(ABC): - """A base class for costs that can be added to a solver.""" +class Cost(ABC): + """A base class for a cost that can be added to a solver.""" @abstractmethod def apply(self, solver: Solver) -> None: - """Apply costs to the given solver. + """Apply a cost to the given solver. Use :func:`motile.Solver.get_variables` and :func:`motile.Solver.add_variable_cost`. Args: solver: - The :class:`~motile.Solver` to create costs for. + The :class:`~motile.Solver` to create a cost for. """ pass diff --git a/motile/costs/disappear.py b/motile/costs/disappear.py index a364cde..0cf7e99 100644 --- a/motile/costs/disappear.py +++ b/motile/costs/disappear.py @@ -3,15 +3,15 @@ from typing import TYPE_CHECKING from ..variables import NodeDisappear -from .costs import Costs +from .cost import Cost from .weight import Weight if TYPE_CHECKING: from motile.solver import Solver -class Disappear(Costs): - """Costs for :class:`motile.variables.NodeDisappear` variables. +class Disappear(Cost): + """Cost for :class:`motile.variables.NodeDisappear` variables. Args: constant (float): @@ -19,7 +19,7 @@ class Disappear(Costs): ignore_attribute: The name of an optional node attribute that, if it is set and - evaluates to ``True``, will not set the disappear costs for that + evaluates to ``True``, will not set the disappear cost for that node. This is useful to allow nodes in the last frame to disappear at no cost. """ diff --git a/motile/costs/edge_distance.py b/motile/costs/edge_distance.py index 2b83be6..c17145e 100644 --- a/motile/costs/edge_distance.py +++ b/motile/costs/edge_distance.py @@ -5,7 +5,7 @@ import numpy as np from ..variables import EdgeSelected -from .costs import Costs +from .cost import Cost from .weight import Weight if TYPE_CHECKING: @@ -14,10 +14,10 @@ from motile.solver import Solver -class EdgeDistance(Costs): - """Costs for :class:`~motile.variables.EdgeSelected` variables. +class EdgeDistance(Cost): + """Cost for :class:`~motile.variables.EdgeSelected` variables. - Costs are based on the spatial distance of the incident nodes. + Cost is based on the spatial distance of the incident nodes. Args: position_attribute: diff --git a/motile/costs/edge_selection.py b/motile/costs/edge_selection.py index 9845d96..68365b6 100644 --- a/motile/costs/edge_selection.py +++ b/motile/costs/edge_selection.py @@ -3,31 +3,31 @@ from typing import TYPE_CHECKING from ..variables import EdgeSelected -from .costs import Costs +from .cost import Cost from .weight import Weight if TYPE_CHECKING: from motile.solver import Solver -class EdgeSelection(Costs): - """Costs for :class:`~motile.variables.EdgeSelected` variables. +class EdgeSelection(Cost): + """Cost for :class:`~motile.variables.EdgeSelected` variables. Args: weight: - The weight to apply to the cost given by the ``costs`` attribute of + The weight to apply to the cost given by the ``cost`` attribute of each edge. attribute: - The name of the edge attribute to use to look up costs. Default is - ``'costs'``. + The name of the edge attribute to use to look up cost. Default is + ``'cost'``. constant: A constant cost for each selected edge. Default is ``0.0``. """ def __init__( - self, weight: float, attribute: str = "costs", constant: float = 0.0 + self, weight: float, attribute: str = "cost", constant: float = 0.0 ) -> None: self.weight = Weight(weight) self.constant = Weight(constant) diff --git a/motile/costs/node_selection.py b/motile/costs/node_selection.py index 2d16b04..fb17bd0 100644 --- a/motile/costs/node_selection.py +++ b/motile/costs/node_selection.py @@ -3,31 +3,31 @@ from typing import TYPE_CHECKING from ..variables import NodeSelected -from .costs import Costs +from .cost import Cost from .weight import Weight if TYPE_CHECKING: from motile.solver import Solver -class NodeSelection(Costs): - """Costs for :class:`~motile.variables.NodeSelected` variables. +class NodeSelection(Cost): + """Cost for :class:`~motile.variables.NodeSelected` variables. Args: weight: - The weight to apply to the cost given by the ``costs`` attribute of + The weight to apply to the cost given by the ``cost`` attribute of each node. attribute: - The name of the node attribute to use to look up costs. Default is - ``'costs'``. + The name of the node attribute to use to look up cost. Default is + ``'cost'``. constant: A constant cost for each selected node. Default is ``0.0``. """ def __init__( - self, weight: float, attribute: str = "costs", constant: float = 0.0 + self, weight: float, attribute: str = "cost", constant: float = 0.0 ) -> None: self.weight = Weight(weight) self.constant = Weight(constant) diff --git a/motile/costs/split.py b/motile/costs/split.py index 19f1fc0..1f97627 100644 --- a/motile/costs/split.py +++ b/motile/costs/split.py @@ -3,22 +3,22 @@ from typing import TYPE_CHECKING from ..variables import NodeSplit -from .costs import Costs +from .cost import Cost from .weight import Weight if TYPE_CHECKING: from motile.solver import Solver -class Split(Costs): - """Costs for :class:`~motile.variables.NodeSplit` variables. +class Split(Cost): + """Cost for :class:`~motile.variables.NodeSplit` variables. Args: weight: The weight to apply to the cost of each split. attribute: - The name of the attribute to use to look up costs. Default is + The name of the attribute to use to look up the cost. Default is ``None``, which means that a constant cost is used. constant: diff --git a/motile/solver.py b/motile/solver.py index 09dc258..0e00acf 100644 --- a/motile/solver.py +++ b/motile/solver.py @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: - from motile.costs import Costs + from motile.costs import Cost from motile.variables import Variable V = TypeVar("V", bound=Variable) @@ -67,57 +67,57 @@ def __init__( self.num_variables: int = 0 self._costs = np.zeros((0,), dtype=np.float32) - self._costs_instances: dict[str, Costs] = {} + self._cost_instances: dict[str, Cost] = {} self.solution: ilpy.Solution | None = None if not skip_core_constraints: - self.add_constraints(SelectEdgeNodes()) + self.add_constraint(SelectEdgeNodes()) - def add_costs(self, costs: Costs, name: str | None = None) -> None: - """Add linear costs to the value of variables in this solver. + def add_cost(self, cost: Cost, name: str | None = None) -> None: + """Add linear cost to the value of variables in this solver. Args: - costs: - The costs to add. An instance of :class:`~motile.costs.Costs`. + cost: + The cost to add. An instance of :class:`~motile.costs.Cost`. name: - An optional name of the costs, used to refer to weights of - costs in an unambiguous manner. Defaults to the name of the - costs class, if not given. + An optional name of the , used to refer to weights of + cost in an unambiguous manner. Defaults to the name of the + cost class, if not given. """ - # default name of costs is the class name + # default name of the cost is the class name if name is None: - name = type(costs).__name__ + name = type(cost).__name__ - if name in self._costs_instances: + if name in self._cost_instances: raise RuntimeError( f"A cost instance with name '{name}' was already registered. " "Consider passing a different name with the 'name=' argument " - "to Solver.add_costs" + "to Solver.add_cost" ) - logger.info("Adding %s costs...", name) + logger.info("Adding %s cost...", name) - self._costs_instances[name] = costs + self._cost_instances[name] = cost # fish out all weights used in this cost object - for var_name, var in costs.__dict__.items(): + for var_name, var in cost.__dict__.items(): if not isinstance(var, Weight): continue self.weights.add_weight(var, (name, var_name)) - costs.apply(self) + cost.apply(self) - def add_constraints(self, constraints: Constraint) -> None: + def add_constraint(self, constraint: Constraint) -> None: """Add linear constraints to the solver. Args: constraints: The :class:`~motile.constraints.Constraint` to add. """ - logger.info("Adding %s constraints...", type(constraints).__name__) + logger.info("Adding %s constraint...", type(constraint).__name__) - for constraint in constraints.instantiate(self): + for constraint in constraint.instantiate(self): self.constraints.add(constraint) def solve( @@ -204,9 +204,9 @@ def get_variables(self, cls: type[V]) -> V: def add_variable_cost( self, index: int | ilpy.Variable, value: float, weight: Weight ) -> None: - """Add costs for an individual variable. + """Add cost for an individual variable. - To be used within implementations of :class:`motile.costs.Costs`. + To be used within implementations of :class:`motile.costs.Cost`. """ variable_index = index feature_index = self.weights.index_of(weight) diff --git a/motile/variables/variable.py b/motile/variables/variable.py index 24ecb84..4bafe48 100644 --- a/motile/variables/variable.py +++ b/motile/variables/variable.py @@ -49,7 +49,7 @@ class Variable(ABC, Mapping[_KT, ilpy.Variable]): This allows variables to be added lazily to the solver. :class:`Constraints` and - :class:`motile.costs.Costs` can ask for variables. + :class:`motile.costs.Cost` can ask for variables. """ # default variable type, replace in subclasses to override diff --git a/tests/test_api.py b/tests/test_api.py index a6510cb..fab1f27 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -58,16 +58,16 @@ def test_solver(): graph = arlo_graph() solver = motile.Solver(graph) - solver.add_costs(NodeSelection(weight=-1.0, attribute="score", constant=-100.0)) - solver.add_costs( + solver.add_cost(NodeSelection(weight=-1.0, attribute="score", constant=-100.0)) + solver.add_cost( EdgeSelection(weight=0.5, attribute="prediction_distance", constant=-1.0) ) - solver.add_costs(EdgeDistance(position_attribute=("x",), weight=0.5)) - solver.add_costs(Appear(constant=200.0, attribute="score", weight=-1.0)) - solver.add_costs(Split(constant=100.0, attribute="score", weight=1.0)) + solver.add_cost(EdgeDistance(position_attribute=("x",), weight=0.5)) + solver.add_cost(Appear(constant=200.0, attribute="score", weight=-1.0)) + solver.add_cost(Split(constant=100.0, attribute="score", weight=1.0)) - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(2)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(2)) callback = Mock() solution = solver.solve(on_event=callback) diff --git a/tests/test_constraints.py b/tests/test_constraints.py index c967bd7..6eb1baa 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -29,40 +29,40 @@ def test_pin(solver: motile.Solver) -> None: solver.graph.edges[(3, 6)]["pin_to"] = True # type: ignore assert _selected_edges(solver) != [(3, 6)], "test invalid" - solver.add_constraints(Pin("pin_to")) + solver.add_constraint(Pin("pin_to")) assert _selected_edges(solver) == [(3, 6)] def test_expression(solver: motile.Solver) -> None: solver.graph.nodes[5]["color"] = "red" # type: ignore - solver.add_costs(NodeSelection(weight=-1.0, attribute="score", constant=-1)) + solver.add_cost(NodeSelection(weight=-1.0, attribute="score", constant=-1)) assert _selected_nodes(solver) != [1, 6], "test invalid" # constrain solver based on attributes of nodes/edges expr = "x > 140 and t != 1 and color != 'red'" - solver.add_constraints(ExpressionConstraint(expr)) + solver.add_constraint(ExpressionConstraint(expr)) assert _selected_nodes(solver) == [1, 6] def test_max_children(solver: motile.Solver) -> None: - solver.add_costs( + solver.add_cost( EdgeSelection(weight=1.0, attribute="prediction_distance", constant=-100) ) expect = [(0, 2), (1, 3), (2, 4), (3, 5)] assert _selected_edges(solver) != expect, "test invalid" - solver.add_constraints(MaxChildren(1)) + solver.add_constraint(MaxChildren(1)) assert _selected_edges(solver) == expect def test_max_parents(solver: motile.Solver) -> None: - solver.add_costs( + solver.add_cost( EdgeSelection(weight=1.0, attribute="prediction_distance", constant=-100) ) expect = [(0, 2), (1, 3), (2, 4), (3, 5), (3, 6)] assert _selected_edges(solver) != expect, "test invalid" - solver.add_constraints(MaxParents(1)) + solver.add_constraint(MaxParents(1)) assert _selected_edges(solver) == expect @@ -73,12 +73,12 @@ def test_exlusive_nodes(solver: motile.Solver) -> None: [4, 5], ] - solver.add_costs( + solver.add_cost( EdgeSelection(weight=1.0, attribute="prediction_distance", constant=-100) ) - solver.add_costs(Appear(constant=2.0, attribute="score", weight=0)) - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(2)) - solver.add_constraints(ExclusiveNodes(exclusive_sets)) + solver.add_cost(Appear(constant=2.0, attribute="score", weight=0)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(2)) + solver.add_constraint(ExclusiveNodes(exclusive_sets)) assert _selected_nodes(solver) == [1, 3, 5, 6], "test invalid" diff --git a/tests/test_costs.py b/tests/test_costs.py index 681e5f1..57145d2 100644 --- a/tests/test_costs.py +++ b/tests/test_costs.py @@ -18,16 +18,16 @@ def test_ignore_attributes(): # first solve without ignore attribute: solver = motile.Solver(graph) - solver.add_costs(NodeSelection(weight=-1.0, attribute="score", constant=-100.0)) - solver.add_costs( + solver.add_cost(NodeSelection(weight=-1.0, attribute="score", constant=-100.0)) + solver.add_cost( EdgeSelection(weight=0.5, attribute="prediction_distance", constant=-1.0) ) - solver.add_costs(EdgeDistance(position_attribute=("x",), weight=0.5)) - solver.add_costs(Appear(constant=200.0, attribute="score", weight=-1.0)) - solver.add_costs(Split(constant=100.0, attribute="score", weight=1.0)) + solver.add_cost(EdgeDistance(position_attribute=("x",), weight=0.5)) + solver.add_cost(Appear(constant=200.0, attribute="score", weight=-1.0)) + solver.add_cost(Split(constant=100.0, attribute="score", weight=1.0)) - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(2)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(2)) solution = solver.solve() no_ignore_value = solution.get_value() @@ -38,12 +38,12 @@ def test_ignore_attributes(): graph.nodes[first_node]["ignore_appear_cost"] = True solver = motile.Solver(graph) - solver.add_costs(NodeSelection(weight=-1.0, attribute="score", constant=-100.0)) - solver.add_costs( + solver.add_cost(NodeSelection(weight=-1.0, attribute="score", constant=-100.0)) + solver.add_cost( EdgeSelection(weight=0.5, attribute="prediction_distance", constant=-1.0) ) - solver.add_costs(EdgeDistance(position_attribute="x", weight=0.5)) - solver.add_costs( + solver.add_cost(EdgeDistance(position_attribute="x", weight=0.5)) + solver.add_cost( Appear( constant=200.0, attribute="score", @@ -51,10 +51,10 @@ def test_ignore_attributes(): ignore_attribute="ignore_appear_cost", ) ) - solver.add_costs(Split(constant=100.0, attribute="score", weight=1.0)) + solver.add_cost(Split(constant=100.0, attribute="score", weight=1.0)) - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(2)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(2)) solution = solver.solve() ignore_value = solution.get_value() diff --git a/tests/test_plot.py b/tests/test_plot.py index 8a74b16..3fa047f 100644 --- a/tests/test_plot.py +++ b/tests/test_plot.py @@ -18,10 +18,10 @@ def graph() -> motile.TrackGraph: @pytest.fixture def solver(graph: motile.TrackGraph) -> motile.Solver: solver = motile.Solver(graph) - solver.add_costs(NodeSelection(weight=-1.0, attribute="score", constant=-100.0)) - solver.add_costs(EdgeSelection(weight=1.0, attribute="prediction_distance")) - solver.add_costs(Appear(constant=200.0)) - solver.add_costs(Split(constant=100.0)) + solver.add_cost(NodeSelection(weight=-1.0, attribute="score", constant=-100.0)) + solver.add_cost(EdgeSelection(weight=1.0, attribute="prediction_distance")) + solver.add_cost(Appear(constant=200.0)) + solver.add_cost(Split(constant=100.0)) return solver diff --git a/tests/test_structsvm.py b/tests/test_structsvm.py index c41b00e..eb0815f 100644 --- a/tests/test_structsvm.py +++ b/tests/test_structsvm.py @@ -43,12 +43,12 @@ def create_ssvm_noise_trackgraph() -> motile.TrackGraph: def create_toy_solver(graph): solver = motile.Solver(graph) - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(1)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(1)) - solver.add_costs(NodeSelection(weight=1.0, attribute="score", constant=-10.0)) - solver.add_costs(EdgeSelection(weight=10.0, attribute="score")) - solver.add_costs(Appear(constant=10.0)) + solver.add_cost(NodeSelection(weight=1.0, attribute="score", constant=-10.0)) + solver.add_cost(EdgeSelection(weight=10.0, attribute="score")) + solver.add_cost(Appear(constant=10.0)) logger.debug("====== Initial Weights ======") logger.debug(solver.weights) @@ -118,14 +118,14 @@ def test_structsvm_common_toy_example(): def create_noise_solver(graph): solver = motile.Solver(graph) - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(1)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(1)) - solver.add_costs(NodeSelection(weight=1.0, attribute="score", constant=-10.0)) - solver.add_costs(EdgeSelection(weight=10.0, attribute="score", constant=3.0)) - solver.add_costs(Appear(constant=10.0)) + solver.add_cost(NodeSelection(weight=1.0, attribute="score", constant=-10.0)) + solver.add_cost(EdgeSelection(weight=10.0, attribute="score", constant=3.0)) + solver.add_cost(Appear(constant=10.0)) - solver.add_costs( + solver.add_cost( NodeSelection( weight=2, constant=4, @@ -133,7 +133,7 @@ def create_noise_solver(graph): ), name="NodeNoise", ) - solver.add_costs( + solver.add_cost( EdgeSelection( weight=6, constant=8, From 1ab2b5696ded8adadd0602d5d8484c5b076ca254 Mon Sep 17 00:00:00 2001 From: Caroline Malin-Mayor Date: Wed, 24 Jul 2024 14:41:07 -0400 Subject: [PATCH 2/3] Update docs to use renamed functions and classes --- docs/source/api.rst | 2 +- docs/source/extending.rst | 36 ++++++++++++++++++------------------ docs/source/index.rst | 10 +++++----- docs/source/learning.rst | 24 ++++++++++++------------ docs/source/quickstart.rst | 16 ++++++++-------- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index 19797a4..3916322 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -78,7 +78,7 @@ All costs inherit from the following base class: .. automodule:: motile.costs - .. autoclass:: Costs + .. autoclass:: Cost :members: The following lists all costs that are already implemented in ``motile``. diff --git a/docs/source/extending.rst b/docs/source/extending.rst index 92ae927..abd0adc 100644 --- a/docs/source/extending.rst +++ b/docs/source/extending.rst @@ -81,12 +81,12 @@ model is: solver = motile.Solver(graph) - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(1)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(1)) - solver.add_costs(NodeSelection(weight=-1, attribute="score")) - solver.add_costs(EdgeSelection(weight=-1, attribute="score")) - solver.add_costs(Appear(constant=1)) + solver.add_cost(NodeSelection(weight=-1, attribute="score")) + solver.add_cost(EdgeSelection(weight=-1, attribute="score")) + solver.add_cost(Appear(constant=1)) solver.solve() @@ -155,7 +155,7 @@ instantiating the new class: .. jupyter-execute:: :hide-output: - solver.add_constraints(LimitNumTracks(1)) + solver.add_constraint(LimitNumTracks(1)) solver.solve() .. jupyter-execute:: @@ -170,18 +170,18 @@ Adding Costs We might want to add custom costs to ``motile`` that are not already covered by the existing ones. For the sake of this tutorial, let's say we want to make the selection of nodes cheaper the higher up the node is in space. For obvious -reasons, let's call this new cost ``SillyCosts``. +reasons, let's call this new cost ``SillyCost``. -Costs in ``motile`` are added by subclassing :class:`Costs -` and implementing the :func:`apply -` method: +Costs in ``motile`` are added by subclassing :class:`Cost +` and implementing the :func:`apply +` method: .. jupyter-execute:: from motile.variables import NodeSelected - class SillyCosts(motile.costs.Costs): + class SillyCost(motile.costs.Cost): def __init__(self, position_attribute, weight=1.0): self.position_attribute = position_attribute @@ -208,15 +208,15 @@ We can now add those costs in the same way others are added, i.e.: .. jupyter-execute:: - print("Before adding silly costs:") + print("Before adding silly cost:") print(solver.get_variables(NodeSelected)) - solver.add_costs(SillyCosts('x', weight=0.02)) + solver.add_cost(SillyCost('x', weight=0.02)) - print("After adding silly costs:") + print("After adding silly cost:") print(solver.get_variables(NodeSelected)) -As we can see, our new costs have been applied to each ``NodeSelected`` +As we can see, our new cost have been applied to each ``NodeSelected`` variable and nodes with a larger ``x`` value are now cheaper than others. Solving again will now select the upper one of the possible tracks in the track @@ -322,12 +322,12 @@ The complete variable declaration looks like this: Variables on their own, however, don't do anything yet. They only start to affect the solution if they are involved in constraints or have a cost. -The following defines costs on our new variables, which loosely approximate the +The following defines a cost on our new variables, which loosely approximate the local curvature of the track: .. jupyter-execute:: - class CurvatureCosts(motile.costs.Costs): + class CurvatureCost(motile.costs.Cost): def __init__(self, position_attribute, weight=1.0): self.position_attribute = position_attribute @@ -364,7 +364,7 @@ added variables: .. jupyter-execute:: :hide-output: - solver.add_costs(CurvatureCosts('x', weight=0.1)) + solver.add_cost(CurvatureCost('x', weight=0.1)) solver.solve() Let's inspect the solution! diff --git a/docs/source/index.rst b/docs/source/index.rst index bcc5d56..da58539 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -90,21 +90,21 @@ subject to certain constraints. This can be done with ``motile`` as follows: solver = motile.Solver(graph) # tell it how to compute costs for selecting nodes and edges - solver.add_costs( + solver.add_cost( NodeSelection( weight=-1.0, attribute='score')) - solver.add_costs( + solver.add_cost( EdgeSelection( weight=-1.0, attribute='score')) # add a small penalty to start a new track - solver.add_costs(Appear(constant=1.0)) + solver.add_cost(Appear(constant=1.0)) # add constraints on the solution (no splits, no merges) - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(1)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(1)) # solve solution = solver.solve() diff --git a/docs/source/learning.rst b/docs/source/learning.rst index 81f4be1..fcacc27 100644 --- a/docs/source/learning.rst +++ b/docs/source/learning.rst @@ -88,12 +88,12 @@ model is: solver = motile.Solver(graph) - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(1)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(1)) - solver.add_costs(NodeSelection(weight=1, attribute="score")) - solver.add_costs(EdgeSelection(weight=-2, attribute="score", constant=1)) - solver.add_costs(Appear(constant=1)) + solver.add_cost(NodeSelection(weight=1, attribute="score")) + solver.add_cost(EdgeSelection(weight=-2, attribute="score", constant=1)) + solver.add_cost(Appear(constant=1)) Each of those costs is calculated as the product of `weights` and `features`: @@ -158,10 +158,10 @@ Our initial weights are just a guess, let's solve... draw_solution(graph, solver, label_attribute="score") None of the nodes or edges were selected, which is indeed the cost minimal -solution: the costs for selecting nodes or edges is too high. +solution: the cost for selecting nodes or edges is too high. We can use the ``solver.weights`` object to directly modify the weights on the -costs. Here, we further lower the costs of edges for example: +costs. Here, we further lower the cost of edges for example: .. jupyter-execute:: :hide-output: @@ -252,12 +252,12 @@ To see whether those weights are any good, we will solve again... solver = motile.Solver(graph) - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(1)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(1)) - solver.add_costs(NodeSelection(weight=1, attribute="score")) - solver.add_costs(EdgeSelection(weight=-2, attribute="score", constant=1)) - solver.add_costs(Appear(constant=1)) + solver.add_cost(NodeSelection(weight=1, attribute="score")) + solver.add_cost(EdgeSelection(weight=-2, attribute="score", constant=1)) + solver.add_cost(Appear(constant=1)) solver.weights.from_ndarray(optimal_weights.to_ndarray()) diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index d67c359..a1a48c9 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -88,7 +88,7 @@ First, we have to create a :class:`Solver` for our track graph: solver = motile.Solver(graph) Once a solver is created, we can add costs and constraints to it. We start with -the most basic ones: the costs for selecting a node or an edge. Both nodes and +the most basic ones: the cost for selecting a node or an edge. Both nodes and edges in our example graph have an attribute ``score``, which indicates how likely the node or edge is a true positive and should therefore be selected. A higher score is better. @@ -104,11 +104,11 @@ classes :class:`costs.NodeSelection` and from motile.costs import NodeSelection, EdgeSelection - solver.add_costs( + solver.add_cost( NodeSelection( weight=-1.0, attribute='score')) - solver.add_costs( + solver.add_cost( EdgeSelection( weight=-1.0, attribute='score')) @@ -169,8 +169,8 @@ sure that tracks don't merge or split: from motile.constraints import MaxParents, MaxChildren - solver.add_constraints(MaxParents(1)) - solver.add_constraints(MaxChildren(1)) + solver.add_constraint(MaxParents(1)) + solver.add_constraint(MaxChildren(1)) If we solve again, the solution does now look like this: @@ -195,8 +195,8 @@ track consisting of just one node. To avoid those short tracks, we can add a constant cost for the appearance of a track: only tracks that are long enough to offset this cost will then be selected. -Adding Costs for Starting a Track ---------------------------------- +Adding a Cost for Starting a Track +---------------------------------- ``motile`` provides :class:`costs.Appear`, which we can add to our solver to discourage selection of short tracks. We add them similarly to how we added the @@ -206,7 +206,7 @@ node and edge selection costs: from motile.costs import Appear - solver.add_costs(Appear(constant=1.0)) + solver.add_cost(Appear(constant=1.0)) And if we solve the tracking problem again with those costs... From 60ff6b9342ad2174a2de04c684c0ec6b846cfed1 Mon Sep 17 00:00:00 2001 From: Caroline Malin-Mayor Date: Wed, 24 Jul 2024 14:49:16 -0400 Subject: [PATCH 3/3] Ruff fix: docstring typo --- motile/solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/motile/solver.py b/motile/solver.py index 0e00acf..6a4e782 100644 --- a/motile/solver.py +++ b/motile/solver.py @@ -112,7 +112,7 @@ def add_constraint(self, constraint: Constraint) -> None: """Add linear constraints to the solver. Args: - constraints: + constraint: The :class:`~motile.constraints.Constraint` to add. """ logger.info("Adding %s constraint...", type(constraint).__name__)