-
Notifications
You must be signed in to change notification settings - Fork 2
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
add lazy compute operations #25
base: dev
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## dev #25 +/- ##
======================================
Coverage ? 82.32%
======================================
Files ? 8
Lines ? 764
Branches ? 0
======================================
Hits ? 629
Misses ? 135
Partials ? 0 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there, I think this is going in the right direction. My major comment relates to more abstraction from the RSP framework, from which future developments may follow more naturally. I have other things in mind, that I will post in separate issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would argue that memory use and flops is also related to the prob::Problem
. It could be easy to assess the expected number of flops in VectorSolve()
where we can do the calculation for one node and multiply by the number of targets, but it may be more difficult for a MatrixSolve()
without performing the operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes makes sense, memory can just be checked by running one column.
Flops may need us to get some values I'm not sure we have access to (but e.g. UMFPACK dose track flops internally).
src/gridrsp.jl
Outdated
@@ -305,6 +370,18 @@ function least_cost_kl_divergence(grsp::GridRSP, target::Tuple{Int,Int}) | |||
return reshape(div, grsp.g.nrows, grsp.g.ncols) | |||
end | |||
|
|||
|
|||
@kwdef struct ConnectedHabitat{CV,DT,DV} <: RSPOperation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this could be better abstracted - the "equivalent connected habitat" metrics extends beyond the RSP framework. The calculation of connected habitat is simply qˢ .* (S*qᵗ)
, where S
is a proximity matrix. A proximity matrix is derived from a distance matrix, by applying a function distance_transformation
that usually involves the dispersal ability of the species considered.
It would be nice to have a structure like AbstractDistance
, and from that we could define a RSPDistance
(which boils down to the expected_cost
) but also a LCPDistance
, a ResistanceDistance
and what not. Those could be an input to ConnectedHabitat
, and would be blindly used to calculate the distance matrix.
More generally, a distance object should be provided to the prob::Problem
, and be used to inform the specific operation requested. If no operation is requested, then compute
could output an object of the sort ExplicitGrid
(I feel like this is a bad name, we should reflect on this), which is essentially a graph with edge weights corresponding to the distance evaluation. This object would somewhat be a sort of RSPGrid
, but more interpretable and generic.
This reflection brings me to a second point: the GridRSP
object is too specific. The primary goal of this is to checkpoint some calculations, but this should be the role of the Problem
structure. RSPGrid
is only a buffer with no interest from the user. This buffer could be hidden in prob::Problem
. Notice that this is very similar to the solution used in SciML and LinearSolve.jl, where init(prob)
allows to do some caching for e.g. the LU factors when doing LU solve (https://docs.sciml.ai/SciMLBase/dev/interfaces/Init_Solve/ and https://docs.sciml.ai/LinearSolve/stable/tutorials/caching_interface/)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes GridRSP is pretty much hidden now in this PR. You call compute
on a Problem
and Grid
and GridRSP is internal.
I'm working towards preallocating the whole problem up front like LinearSolve does but most of the code is unfortunately not written in that style. The first step will be to separate out non-allocating !
methods for everything so the workspace arrays can be passed in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also good point about using more appropriate abstractions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a good discussion with @ninbrm about refactoring distances and fixing the abstract type structure to better represent the range of problems. Theta really doesn't need to be a parameter but the fact that distance functions are curreently used like functions rather than constructed objects currently makes it awkward to put parameters in them. So we should change them to be types like you describe.
I'll implement this new abstract type hierarchy in the next iteration of this PR and we can discuss from there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, looking forward to that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok the new abstractions are kind of implemented now :)
It works in the landmarks.jmd example at least, but needs some more polish and thought. Eventually we can refactor some of the complexity away but for now Im trying to build it on top of the current methods without changing them too much, so the old tests still run and we can use them to check the new methods.
src/problem.jl
Outdated
@show size(A) size(B) size(b) | ||
|
||
# Define and initialise the linear problem | ||
linprob = LinearProblem(A, b) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment above, this should appear in the construction of the Problem. We should additionally allow the user to provide a solver, with possibly some warnings or advices on suitable solvers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sure, this is just hacked in for now to get it working while I learn LinearSolve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something that should also be addressed, and that I do not fully understand: in many of the methods in randomizedshortestpath.jl
, there is a second linear solve with the use of landmarks
ConScape.jl/src/randomizedshortestpath.jl
Line 194 in c18ca9b
C̄ = (I - W)\((C .* W)*Z) |
This may have consequences on how we want to construct a Problem
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I saw that too. These methods will need to be handled but I don't totally understand them yet either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the LinearSolve part there is now a LinearSolver
object that takes the args and keywords for solve
and init
so you can use any solver from LinearSolve.jl in conscape.
One issue will be maybe specifying if a solver should be used for all RSP solves as well as the Z matrix, or supplying a different solver for different components as they don't necessarily have the same properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Did you reflect on how to treat the \
in randomizedshortestpaths.jl
? This may be an important thing to consider, to make sure that the proposed architecture and solver modes are also used there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not having something like
(d::LeastCostDistance)(args...; kwargs...) = least_cost_distance(args...; kwargs...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've allowed passing solver modes to \
in rsp, but playing around I found some situations where LinearSolve solvers were very fast on Z but stalled in RSP, so maybe we want different solvers per problem?
We also want to factor out some of the solves as the same thing is being done in multiple places if you want multiple kinds of betweenness metrics in the same run.
diagvalue::DV=nothing | ||
end | ||
|
||
@kwdef struct ConnectedHabitat{DV} <: GraphMeasure |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this should be a topological measure
@@ -0,0 +1,38 @@ | |||
# New type-based interface | |||
# Easier to add parameters to these | |||
abstract type ConnectivityMeasure end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you describe what is the difference between those? I feel like the type system should be as close as possible to the mathematical classification of these measures.
I suggest that we could follow the classification of e.g. Graphs.jl or that of networkx: https://networkx.org/documentation/stable/reference/algorithms/index.html
i.e., DistanceMeasure, ConnectivityMeasure, CentralityMeasure, etc...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds like a good idea, the GraphMeasures that we discussed would then be CentralityMeasures (connected_habitat is a weighted closeness centrality and the betweennesses are betweenness centralities)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets do a round or two of documenting and reorganising/renaming the type structure when differences get clearer, the objects here can be seen as place holders for better names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you are refactoring this, it would make sense to already think of #29. Maybe add a field connected_components
that is a vector of vectors, where each vector corresponds to a list of node of the associated connected component.
src/problem.jl
Outdated
function compute(m::VectorSolve, p::AbstractProblem, g::Grid) | ||
# Compute Z column by column | ||
_, targetnodes = _targetidx_and_nodes(g) | ||
function solve(m::VectorSolve, cm::FundamentalMeasure, p::AbstractProblem, g::Grid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent, this looks good! But I think that this should be a DirectVectorMode
, and we likely need to implement something similar for IndirectVectorMode
, where instead of the init(linprob)
, we calculate a preconditioner, and then dispatch the calculations over the threads. This mode will make the solve free from any matrix materialization. It would be awesome to also have in the future an IndirectMatrixMode
, this would work great on GPUs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, why not call those XxVectorMode
and XxBatchMode
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See here for the use of the preconditioner: https://github.com/Circuitscape/Circuitscape.jl/blob/fff12fe43e5af5be00f4056a87460ad07966e432/src/core.jl#L155-L160
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ive refactored this so that VectorSolver
is just using UMFPACK directly column by column, and LinearSolver
lets you use anything from LinearSolve.jl by passing the args and keywords you would pass to init
and solve
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The preconditioner part will be done automatically by LinearSolve.jl in init
if you pass a precs
argument in your solver definition, as far as I can tell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we really need that batch mode added to LinearSolve.jl to push this much further, currently I'm running init
and then deepcopy
on it for each thread. But there are surely more efficient ways were only the mutated parts are copied rather than the whole thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about having a folder connectivity_measures
and then files that contain specialised methods belonging to a certain framework, such as e.g. the RSP framework but also possibly to other frameworks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should reorganise all the code like that, but in this PR im trying to add new things without completely reorganising the old ones so that changes to the old code diff properly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So for now its just a file with struct definitions and the methods are left in their current place.
This PR defines the bones of lazy computation objects and a
Problem
wrapper, following SciML patterns(although we could also swapwhere acompute
here tosolve
in retrospect)Problem
is passed tosolve
The idea is that instead of calling ConScape.jl functions directly, we define
Operation
objects with their parameters, and chain multiple of these into a combined computationProblem
. Then this object can be computed efficiently with e.g. one generation ofGridRSP
or just its columns, or even over multiple windowed grids (without explicitly coding the windowing loop and calling these functions).On
AbstractProblem
we can callcompute(p::Problem, g::Grid)
to do the actual computation.(There is also an idea to do
assess(p::Problem, ::Grid)
to get information about the computation for a givenGrid
, like total memory use, flops (if we need that and can calculate it) and memory requirements so we can preallocate it or book it on clusters - but this can be fleshed out later on)I've added a
WindowedProblem
andTiledProblem
to organise running operations over tiles and windows for very large data (this is still WIP).@vboussange this might be ready for a look if you want to give some early feedback? Especially in regards to anything I have missed for autodiff capacity and iterative solvers. Its built on LinearSolve.jl now, but still actually materializes a Z array becayse it needs some work on the methods to allow them all to work column by column.