Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

63 rename solver functions #108

Merged
merged 3 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``.
Expand Down
36 changes: 18 additions & 18 deletions docs/source/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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::
Expand All @@ -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
<motile.costs.Costs>` and implementing the :func:`apply
<motile.costs.Costs.apply>` method:
Costs in ``motile`` are added by subclassing :class:`Cost
<motile.costs.Cost>` and implementing the :func:`apply
<motile.costs.Cost.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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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!
Expand Down
10 changes: 5 additions & 5 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
24 changes: 12 additions & 12 deletions docs/source/learning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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())

Expand Down
16 changes: 8 additions & 8 deletions docs/source/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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'))
Expand Down Expand Up @@ -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:

Expand All @@ -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
Expand All @@ -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...

Expand Down
4 changes: 2 additions & 2 deletions motile/costs/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,7 +11,7 @@

__all__ = [
"Appear",
"Costs",
"Cost",
"Disappear",
"EdgeDistance",
"EdgeSelection",
Expand Down
10 changes: 5 additions & 5 deletions motile/costs/appear.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,30 @@
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:
A constant cost for each node that starts a track.

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.
"""
Expand Down
8 changes: 4 additions & 4 deletions motile/costs/costs.py → motile/costs/cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 4 additions & 4 deletions motile/costs/disappear.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@
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):
A constant cost for each node that ends a track.

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.
"""
Expand Down
8 changes: 4 additions & 4 deletions motile/costs/edge_distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
14 changes: 7 additions & 7 deletions motile/costs/edge_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading