I can't think of any alternatives off the top of my head. The usual
solution is to incorporate everything into your cost function, instead
of having two separate stopping criteria -- most problems lend
themselves to this pretty well.

And actually, now that I think of it: Dijkstra's algorithm (which is
all this graph traversal is running) fails with non-monotonic cost
functions. Since the algorithm only visits a given position one time
(on the minimum cost path), a shorter but more expensive path to the
same point will not be considered, even if that is part of the path
that gives a global optimum on your "minimum cost path below a certain
length" problem. It would be a good advanced algorithms problem set
question to consider if optimal solutions to that problem will
necessarily be exponential in complexity.

So that's why people usually try to blend their cost considerations
into a single cost for traversing each node...

That said, you might get OK solutions from this modified algorithm,
though there will be no guarantee of global optimality.

I can also think of one awful hack you could try to just see how well
it works. You could "store" the number of steps in the cumulative cost
by defining a custom travel_cost function.
https://github.com/scikit-image/scikit-image/blob/master/skimage/graph/_mcp.pyx#L885

Let's assume that your "actual" costs are all >> 1. Then, instead of
returning new_cost, you return new_cost + 0.001. Then goal_reached
could count the number of steps by just looking at the value of the
cumulative cost after the decimal point.

Again, this is gross, and there are lots of cases where it won't work
or will blow up badly. But for reasonably small maximum path lengths,
it should be OK. Also, for speed you'll still need to implement this
in a cython subclass. But you can use MCP_Flexible to proof out the
implementation in slow pure python.




On Tue, May 2, 2017 at 2:43 PM, Spencer Gardner
<spencergard...@gmail.com> wrote:
> Thanks. That's extremely helpful. I think I'll be able to work out the
> subclassing. I've never worked in cython but there's a first time for
> everything.
>
> Is there some alternative solution to my problem that I've missed? I've
> scoured the documentation from various software projects (GIS and non-GIS)
> to try to find something that implements an algorithm like this but I
> haven't come across anything. This seems odd to me, since it's a problem I
> assume others have had to solve before.
>
> In any case, I'm sure I'll be back with more questions as I dive in.
>
> Spencer
>
> On Tue, May 2, 2017 at 2:11 PM, Zach Pincus <zpin...@gmail.com> wrote:
>>
>> > I need to implement a minimum cost path through an array of costs. The
>> > added
>> > twist is that I need to ensure the path does not exceed some maximum
>> > geometric distance (represented as a total number of traversed cells).
>> > So I
>> > need my goal_reached() method to check whether the current path has
>> > exceeded
>> > the maximum number of cells and return 1 if true (which tells MCP to
>> > stop
>> > checking neighbors).
>> >
>> > Is there a good example implementation somewhere that I can refer to for
>> > some guidance? I've sketched out a rough outline but I'm not sure I'm on
>> > the
>> > right track:
>> >
>> > graph = MCP(costSurfaceArray,fully_connected=False)
>> >
>> > def goal_reached(index, cumcost):
>> >    '''code to check total distance'''
>> >
>> > graph.goal_reached = goal_reached
>>
>> First, note that MCP is implemented in cython, so you can't
>> monkeypatch goal-reached like you could with a pure-python class.
>> Instead you have to subclass:
>>
>> class MyMCP(MCP):
>> def goal_reached(self, index, cumcost):
>> print(index, cumcost)
>> return 0
>>
>> m = MyMCP(costSurfaceArray)
>>
>> Second, let's figure out about casting your problem in a way that
>> goal_reached can address. Here's the documentation for goal_reached:
>>
>>        """ int goal_reached(int index, float cumcost)
>>        This method is called each iteration after popping an index
>>        from the heap, before examining the neighbours.
>>        This method can be overloaded to modify the behavior of the MCP
>>        algorithm. An example might be to stop the algorithm when a
>>        certain cumulative cost is reached, or when the front is a
>>        certain distance away from the seed point.
>>        This method should return 1 if the algorithm should not check
>>        the current point's neighbours and 2 if the algorithm is now
>>        done.
>>        """
>>
>> So basically, the function receives the "current" index (i.e. position
>> in the array) under consideration, along with the minimum cost to get
>> there. If the algorithm should not continue this portion of the search
>> past that index, the function needs to return 1. If the algorithm
>> should terminate wholesale, return 2.
>>
>> If all you needed to do was stop searching after you'd gotten a
>> certain euclidian distance from the starting point, you could
>> calculate that with just the index and knowledge of the starting
>> points. However, you want to find out the current path length
>> terminating in the current index.
>>
>> For this, you will need to deal with the MCP object's
>> traceback_offsets attribute, which stores the paths to each point. For
>> speed reasons, this is only available to subclasses written in cython,
>> rather than pure-python. So you'll have to write it as a cpdef
>> function in a cython subclass of MCP.
>>
>> For the implementation, you'll need to consult how MCP.traceback(self,
>> end) is implemented, and run the same procedure in goal_reached() to
>> simply count the length of the path and return 1 if the path exceeds
>> your acceptable length:
>>
>>
>> https://github.com/scikit-image/scikit-image/blob/master/skimage/graph/_mcp.pyx#L648
>>
>> This won't be totally trivial, however. You'll need to understand a
>> bit of cython, and a bit about how MCP works under the hood: it
>> doesn't store indices as n-dimensional tuples like (x,y,z) coordinates
>> or whatever. Instead it stores them as 1-dimensional indices into the
>> flattened cost array. So much of the logic in the traceback() function
>> is converting those indices around. Basically you'll just need to
>> copy-paste traceback() into goal_reached() but instead of
>> un-flattening the indices to append them to a traceback list, just
>> count the number of links required to get to a starting point.
>>
>> Let me know if this helps.
>>
>> Zach
>>
>> On Tue, May 2, 2017 at 12:14 PM, Spencer Gardner
>> <spencergard...@gmail.com> wrote:
>> > The skimage.graph documentation states that the goal_reached method can
>> > be
>> > overloaded to introduce additional constraints on finding the minimum
>> > cost
>> > path. I'm having some trouble implementing this but I can't find any
>> > useful
>> > examples to follow online. I'm hoping someone can provide some guidance.
>> >
>> > I need to implement a minimum cost path through an array of costs. The
>> > added
>> > twist is that I need to ensure the path does not exceed some maximum
>> > geometric distance (represented as a total number of traversed cells).
>> > So I
>> > need my goal_reached() method to check whether the current path has
>> > exceeded
>> > the maximum number of cells and return 1 if true (which tells MCP to
>> > stop
>> > checking neighbors).
>> >
>> > Is there a good example implementation somewhere that I can refer to for
>> > some guidance? I've sketched out a rough outline but I'm not sure I'm on
>> > the
>> > right track:
>> >
>> > graph = MCP(costSurfaceArray,fully_connected=False)
>> >
>> > def goal_reached(index, cumcost):
>> >    '''code to check total distance'''
>> >
>> > graph.goal_reached = goal_reached
>> >
>> > Thanks for any help!
>> >
>> > PS I posted this also on a Google Group that appears to be defunct.
>> > Apologies if that list is actually active and you got a double post from
>> > me.
>> >
>> >
>> > _______________________________________________
>> > scikit-image mailing list
>> > scikit-image@python.org
>> > https://mail.python.org/mailman/listinfo/scikit-image
>> >
>> _______________________________________________
>> scikit-image mailing list
>> scikit-image@python.org
>> https://mail.python.org/mailman/listinfo/scikit-image
>
>
>
> _______________________________________________
> scikit-image mailing list
> scikit-image@python.org
> https://mail.python.org/mailman/listinfo/scikit-image
>
_______________________________________________
scikit-image mailing list
scikit-image@python.org
https://mail.python.org/mailman/listinfo/scikit-image

Reply via email to