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

Add LeastCost and RandomWalk MovementModes #34

Open
wants to merge 33 commits into
base: alg_efficiency
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
437f935
add new graph measure original code
rafaqz Mar 10, 2025
26bccd3
reorganise graph measure type structure
rafaqz Mar 10, 2025
8813065
reorganise connectivity measure types
rafaqz Mar 10, 2025
d5d3f5b
compute and compute_target
rafaqz Mar 11, 2025
74e8705
fixes
rafaqz Mar 11, 2025
21a425a
add GridPrecalculations
rafaqz Mar 11, 2025
7b427a6
more grid precalculation
rafaqz Mar 11, 2025
beed493
move array definition methods to grid_preallocations.jl
rafaqz Mar 11, 2025
09b169c
reorganise, and use compute for everything
rafaqz Mar 12, 2025
8068d51
start adding sensitivity
rafaqz Mar 12, 2025
223ae60
move all algs to compute format
rafaqz Mar 12, 2025
d5c23bb
more compute
rafaqz Mar 13, 2025
325f5c4
more single-target conversions
rafaqz Mar 14, 2025
1b2174d
more single target
rafaqz Mar 15, 2025
1f9c2a2
bugfix and refactor
rafaqz Mar 16, 2025
2fa9691
huge refactor, connected hab working
rafaqz Mar 17, 2025
9a6aa2f
tests passing
rafaqz Mar 18, 2025
0ae99da
tests passing
rafaqz Mar 18, 2025
37aae45
delete, rename and clean up
rafaqz Mar 19, 2025
ec89867
delete gridrsp.jl
rafaqz Mar 19, 2025
dc036a7
move sims to simulations.jl
rafaqz Mar 19, 2025
1931218
refactor
rafaqz Mar 21, 2025
13d95cc
passing tests
rafaqz Mar 22, 2025
8ec62c2
put linearsolve in an extension
rafaqz Mar 23, 2025
9b73041
tweak window init
rafaqz Mar 23, 2025
e294e33
inits
rafaqz Mar 25, 2025
13ada21
test more init patterns
rafaqz Mar 25, 2025
66e416b
fix workspace race
rafaqz Mar 25, 2025
98074db
sparse_sizes
rafaqz Mar 25, 2025
6405723
fix workspaces for emtpy grids
rafaqz Mar 25, 2025
e615077
Merge branch 'new_movements' of https://github.com/ConScape/ConScape.…
rafaqz Mar 25, 2025
054b9e0
fix LinearSolve deps
rafaqz Mar 25, 2025
87cbd8e
bugfix betweenness overflow
rafaqz Mar 25, 2025
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
7 changes: 6 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
ProgressLogging = "33c8b6b6-d38a-422a-b730-caa89a2f386c"
Rasters = "a3a2b9e3-a471-40c9-b274-f788e487c689"
SimpleWeightedGraphs = "47aef6b3-ad0c-573a-a1e2-d07658019622"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[weakdeps]
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"

[extensions]
ConScapeLinearSolveExt = "LinearSolve"

[compat]
ArnoldiMethod = "0.0.4, 0.4"
CommonSolve = "0.2"
Expand Down
53 changes: 53 additions & 0 deletions ext/ConScapeLinearSolveExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module ConScapeLinearSolveExt

using ConScape
using LinearSolve
import CommonSolve

function CommonSolve.init(solver::LinearSolver, A::AbstractMatrix)
b = zeros(eltype(A), size(A, 2))
# Define and initialise the linear problem
linprob = LinearProblem(A, b)
linsolve = init(linprob, solver.args...; solver.keywords...)
# TODO what is needed here?
# Create a channel to store problem b vectors for threads
# see https://juliafolds2.github.io/OhMyThreads.jl/stable/literate/tls/tls/
if isthreaded(solver)
nbuffers = Threads.nthreads()
channel = Channel{Tuple{typeof(linsolve),Vector{Float64}}}(nbuffers)
for i in 1:nbuffers
# TODO fix this in LinearSolve.jl with batching
# We should not need to `deepcopy` the whole problem we
# just need to replicate the specific workspace arrays
# that will cause race conditions.
# But currently there is no parallel mode for LinearSolve.jl
# See https://github.com/SciML/LinearSolve.jl/issues/552
put!(channel, (deepcopy(linsolve), Vector{eltype(A)}(undef, size(A, 2))))
end
return channel
else
return linsolve
end
end

function LinearAlgebra.ldiv!(s::LinearSolver, B, init, B_copy)
# TODO: for now we define a Z matrix, but later modify ops
# to run column by column without materialising Z
if isthreaded(s)
channel = init
# Get column memory from the channel
linsolve = take!(channel)
# Update solver with new b values
reinit!(linsolve; b=vec(B_copy), reuse_precs=true)
sol = LinearSolve.solve!(vec(B), linsolve, s.args...; s.keywords...)
vec(B) .= sol.u
put!(channel, linsolve)
else
linsolve = init
reinit!(linsolve; b=vec(B_copy), reuse_precs=true)
sol = LinearSolve.solve(linsolve, s.args...; s.keywords...)
vec(B) .= sol.u
end
return B
end
end
38 changes: 19 additions & 19 deletions src/ConScape.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ using ArnoldiMethod
using ConstructionBase
using Graphs
using LinearAlgebra
using LinearSolve
using ProgressLogging
using Rasters
using SimpleWeightedGraphs
using SparseArrays
Expand All @@ -14,19 +12,21 @@ using Rasters.DimensionalData
import CommonSolve
import CommonSolve: solve, init

# Old funcion-based interface
abstract type ConnectivityFunction <: Function end
abstract type DistanceFunction <: ConnectivityFunction end
abstract type ProximityFunction <: ConnectivityFunction end
export RandomisedShortestPath, LeastCost, RandomWalk

struct least_cost_distance <: DistanceFunction end
struct expected_cost <: DistanceFunction end
struct free_energy_distance <: DistanceFunction end
export ExpectedCost, FreeEnergyDistance, SurvivalProbability, PowerMeanProximity, KullbackLeiblerDivergence

struct survival_probability <: ProximityFunction end
struct power_mean_proximity <: ProximityFunction end
export Betweenness, EdgeBetweenness, ConnectedHabitat, Criticality, EigMax, Sensitivity

# Need to define before loading files
export QualityWeighted, QualityAndProximityWeighted, ProximityWeighted

export VectorSolver, LinearSolver

export MinusLog, MinusLogAlpha, Inv, OddsFor, OddsAgainst, ExpMinus, ExpMinusAlpha

export solve, init, assess

export WindowedProblem, BatchProblem

"""
Solver
Expand All @@ -37,23 +37,23 @@ abstract type AbstractProblem end
abstract type Solver end

# Randomized shortest path algorithms
include("randomizedshortestpath.jl")
# Grid struct and methods
include("transformations.jl")
# Grid struct and methods
include("grid.jl")
# GridRSP (randomized shortest path) struct and methods
include("gridrsp.jl")
# IO
include("io.jl")
# Utilities
include("utils.jl")
# Problems
include("graph_measure.jl")
include("connectivity_measure.jl")
include("workspaces.jl")
include("measures.jl")
include("movement_modes.jl")
include("problem.jl")
include("initialisation.jl")
include("return.jl")
include("solvers.jl")
include("compute_measures.jl")
include("windows.jl")
include("assessment.jl")
include("simulations.jl")

end
63 changes: 34 additions & 29 deletions src/assessment.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ a `Problem`.
- `size::Tuple{Int,Int}`: the size of the input and output RasterStack
- `shape::Tuple{Int,Int}`: the shape of the windowing
- `njobs::Int`: the number of problem runs required to finish the problem
- `grid_sizes::Vector{Tuple{Int,Int}}`: the sizes of each window
- `sparse_sizes::Vector{Tuple{Int,Int}}`: the sizes of each window
- `mask::Vector{Bool}`: Vector{Bool} where `true` values are jobs that need to be run.
- `indices::Vector{Int}`: the indices of `mask` that are `true`.
"""
Expand All @@ -52,7 +52,7 @@ a `Problem`.
mask::Vector{Bool}
indices::Vector{Int}
warnings::AssessmentWarnings
grid_sizes::Vector{Tuple{Int,Int}}
sparse_sizes::Vector{Tuple{Int,Int}}
end

"""
Expand Down Expand Up @@ -80,7 +80,7 @@ that holds another `AbstractWindowedProblem`.
end

function Base.show(io::IO, mime::MIME"text/plain", a::ProblemAssessment)
println(io, "NestedAssessment")
summary(io, a)
println(io)
println(io, "Shape: $(a.shape)")
println(io, "Number of jobs: $(a.njobs)")
Expand Down Expand Up @@ -110,32 +110,32 @@ function assess(p::AbstractWindowedProblem{<:Problem}, rast::AbstractRasterStack
kw...
)
# Define the ranges of each window
window_ranges = _window_ranges(p, rast)
window_ranges = ConScape.window_ranges(p, rast)

# Convert everything to Bool at the batch level so window assessments are fast
inner_targets = view(rast.target_qualities, target_ranges...)
warnings = AssessmentWarnings(
any(isnan, rast.qualities),
any(isnan, rast.source_qualities),
any(isnan, inner_targets),
)
inner_target_bools = isnothing(inner_target_bools) ? _isvalid.(inner_targets) : inner_target_bools
qualities = _isvalid.(rast.qualities)
source_qualities = _isvalid.(rast.source_qualities)
target_qualities = falses(size(rast))
target_qualities[target_ranges...] .= inner_target_bools
bool_rast = RasterStack((; qualities, target_qualities), dims(rast))
bool_rast = RasterStack((; source_qualities, target_qualities), dims(rast))

# Calculate window sizes and allocations
grid_sizes = vec(_estimate_grid_sizes(p, bool_rast; window_ranges))
sparse_sizes = vec(_estimate_sparse_sizes(p, bool_rast; window_ranges))

# Organise stats for each window into vectors
window_mask = map(s -> prod(s) > 0, grid_sizes)
window_mask = map(s -> prod(s) > 0, sparse_sizes)
non_empty_indices = eachindex(window_mask)[window_mask]

# Calculate global stats
njobs = count(window_mask)
shape = size(window_ranges)

WindowAssessment(size(rast), shape, njobs, window_mask, non_empty_indices, warnings, grid_sizes)
WindowAssessment(size(rast), shape, njobs, window_mask, non_empty_indices, warnings, sparse_sizes)
end
function assess(
p::AbstractWindowedProblem{<:AbstractWindowedProblem},
Expand All @@ -145,7 +145,7 @@ function assess(
kw...
)
# Calculate outer window ranges
window_ranges = _window_ranges(p, rast)
window_ranges = ConScape.window_ranges(p, rast)
verbose && println("Assessing $(length(window_ranges)) jobs")

# Define a vector for all assessment data
Expand All @@ -163,11 +163,11 @@ function assess(
mask=Bool[],
indices=Int[],
warnings=AssessmentWarnings(false, false),
grid_sizes=Tuple{Int,Int}[],
sparse_sizes=Tuple{Int,Int}[],
)
end
# We only need qualities for the assessment
window_rast = rast[(:qualities, :target_qualities)][rs...]
window_rast = rast[(:source_qualities, :target_qualities)][rs...]
target_ranges = _target_ranges(p, window_rast)
# Convert targets to bool as early as possible
inner_targets = view(window_rast.target_qualities, target_ranges...)
Expand Down Expand Up @@ -227,23 +227,28 @@ end

# Accept ProblemAssessment as an argument to solve and init
# To used instead of keywords
solve(p::BatchProblem, rast::RasterStack, a::ProblemAssessment, i::Int...; verbose=false) =
solve!(init(p, rast, a), p, i...; verbose)
solve(p::BatchProblem, rast::RasterStack, a::ProblemAssessment, i::Int; kw...) =
solve(p, rast, i; _assessment_keywords(p, rast, a)..., kw...)

function init(p::BatchProblem{<:WindowedProblem}, rast::RasterStack, a::NestedAssessment, i::Int...; kw...)
batch_ranges = _window_ranges(p, rast)
grid_sizes = map(a.assessments) do a_w
a_w.grid_sizes
init(p::BatchProblem{<:WindowedProblem}, rast::RasterStack, a::NestedAssessment, i::Int...; kw...) =
init(p, rast, i...; _assessment_keywords(p, rast, a)..., kw...)
init(p::BatchProblem{<:Problem}, rast::RasterStack, a::WindowAssessment, i::Int...; kw...) =
init(p, rast, i...; batch_indices=a.indices)
init(p::WindowedProblem{<:Problem}, rast::RasterStack, a::WindowAssessment; kw...) =
init(p, rast; sparse_sizes=a.sparse_sizes, indices=a.indices, kw...)

# Keywords to pass from an Assessment to `init` or `solve`
# We don't use the `Assesment` directly to allow manual manipulation
# of the batch via keywords.
function _assessment_keywords(::BatchProblem, rast, a::WindowAssessment)
return (; batch_indices=a.indices)
end
function _assessment_keywords(p::BatchProblem, rast, a::NestedAssessment)
sparse_sizes = map(a.assessments) do a_w
a_w.sparse_sizes
end
selected_window_indices = map(a.assessments) do a_w
window_indices = map(a.assessments) do a_w
a_w.indices
end
init(p, rast, i...;
batch_ranges,
batch_indices=a.indices,
grid_sizes,
selected_window_indices,
)
end
init(p::BatchProblem{<:Problem}, rast::RasterStack, a::WindowAssessment, i::Int...; kw...) =
init(p, rast, i...; batch_indices=a.indices)
return (; batch_indices=a.indices, window_indices, sparse_sizes)
end
Loading