Skip to content

Commit

Permalink
Introduce Topology values to address #151.
Browse files Browse the repository at this point in the history
These values describe the model under a topological perspective:
nodes and their neibouhring relations.
Nodes and edges are typed into various 'compartments'.
Nodes can be "removed" from topologies
while leaving tombstones to maintain indices validity.
This enables various topological analyses of the model network
like `disconnected_components()`,
`isolated_producers()` or `starving_consumers()`.
  • Loading branch information
iago-lito committed Aug 2, 2024
1 parent 69629aa commit eef9415
Show file tree
Hide file tree
Showing 39 changed files with 3,205 additions and 106 deletions.
14 changes: 9 additions & 5 deletions src/EcologicalNetworksDynamics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ using Crayons
using MacroTools
using OrderedCollections
using SparseArrays
using Graphs

#-------------------------------------------------------------------------------------------
# Shared API internals.
Expand Down Expand Up @@ -39,6 +40,11 @@ using .AliasingDicts
include("./multiplex_api.jl")
using .MultiplexApi

# Types to represent the model under a pure topological perspective.
include("./Topologies/Topologies.jl")
using .Topologies
# (will be part of the internals after their refactoring)

#-------------------------------------------------------------------------------------------
# "Inner" parts: legacy internals.

Expand Down Expand Up @@ -88,15 +94,13 @@ include("./expose_data.jl")
# The actual user-facing components of the package are defined there,
# connecting them to the internals via the framework.
include("./components/main.jl")
include("./methods/main.jl")

# Additional exposed utils built on top of components and methods.
include("./default_model.jl")
include("./nontrophic_layers.jl")

#-------------------------------------------------------------------------------------------
# Analysis tools working on the output of the simulation.
include("output-analysis.jl")
include("./simulate.jl")
include("./topology.jl")
include("./diversity.jl")

# Avoid Revise interruptions when redefining methods and properties.
Framework.REVISING = true
Expand Down
2 changes: 2 additions & 0 deletions src/Framework/Framework.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ module Framework
# - [ ] `depends(other_method_name)` to inherit all dependent components.
# - [ ] Recurring pattern: various blueprints types provide 'the same component': reify.
# - [ ] Namespace properties into like system.namespace.namespace.property.
# - [ ] Hooks need to trigger when special components combination become available.
# See for instance the expansion of `Nutrients.Nodes`.

using Crayons
using MacroTools
Expand Down
1 change: 1 addition & 0 deletions src/GraphDataInputs/GraphDataInputs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ using OrderedCollections
import ..SparseMatrix
import ..argerr
import ..checkfails
import ..join_elided

# Unhygienically define `loc` variable in macros to point to invocation line.
# Assumes __source__ is available.
Expand Down
2 changes: 1 addition & 1 deletion src/GraphDataInputs/check.jl
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ function outspace((i, j), (n, m))
end
either(symbols) =
length(symbols) == 1 ? "$(repr(first(symbols)))" :
"either " * join(repr.(sort(collect(symbols))), ", ", " or ")
"either " * join_elided(sort(collect(symbols)), ", ", " or "; max = 12)

#-------------------------------------------------------------------------------------------
# Assuming the above check passed, check references against a template.
Expand Down
74 changes: 72 additions & 2 deletions src/GraphDataInputs/convert.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,31 @@ function graphdataconvert(::Type{BinMap{<:Any}}, input; expected_I = nothing)
res
end

# The binary case *can* accept boolean masks.
function graphdataconvert(
::Type{BinMap{<:Any}},
input::AbstractVector{Bool};
expected_I = Int64,
)
res = BinMap{expected_I}()
for (i, val) in enumerate(input)
val && push!(res, i)
end
res
end

function graphdataconvert(
::Type{BinMap{<:Any}},
input::AbstractSparseVector{Bool,I};
expected_I = I,
) where {I}
res = BinMap{expected_I}()
for i in findnz(input)[1]
push!(res, i)
end
res
end

#-------------------------------------------------------------------------------------------
# Similar, nested logic for adjacency maps.

Expand Down Expand Up @@ -193,6 +218,38 @@ function graphdataconvert(::Type{BinAdjacency{<:Any}}, input; expected_I = nothi
res
end

# The binary case *can* accept boolean matrices.
function graphdataconvert(
::Type{BinAdjacency{<:Any}},
input::AbstractMatrix{Bool},
expected_I = Int64,
)
res = BinAdjacency{expected_I}()
for (i, row) in enumerate(eachrow(input))
adj_line = BinMap(j for (j, val) in enumerate(row) if val)
isempty(adj_line) && continue
res[i] = adj_line
end
res
end

function graphdataconvert(
::Type{BinAdjacency{<:Any}},
input::AbstractSparseMatrix{Bool,I},
expected_I = I,
) where {I}
res = BinAdjacency{expected_I}()
nzi, nzj, _ = findnz(input)
for (i, j) in zip(nzi, nzj)
if haskey(res, i)
push!(res[i], j)
else
res[i] = BinMap([j])
end
end
res
end

# Alias if types matches exactly.
graphdataconvert(::Type{Map{<:Any,T}}, input::Map{Symbol,T}) where {T} = input
graphdataconvert(::Type{Map{<:Any,T}}, input::Map{Int64,T}) where {T} = input
Expand Down Expand Up @@ -273,9 +330,11 @@ end
# Example usage:
# @tographdata var {Sym, Scal, SpVec}{Float64}
# @tographdata var YSN{Float64}
macro tographdata(var, input)
macro tographdata(var::Symbol, input)
@defloc
var isa Symbol || argerr("Not a variable: $(repr(var)) at $loc.")
tographdata(loc, var, input)
end
function tographdata(loc, var, input)
@capture(input, types_{Target_} | types_{})
isnothing(types) && argerr("Invalid @tographdata target types at $loc.\n\
Expected @tographdata var {aliases...}{Target}. \
Expand Down Expand Up @@ -317,3 +376,14 @@ function _tographdata(vsym, var, targets)
The value received is $(repr(var)) ::$(typeof(var)).")
end
export @tographdata

# Convenience to re-bind in local scope, avoiding the akward following pattern:
# long_var_name = @tographdata long_var_name <...>
# In favour of:
# @tographdata! long_var_name <...>
macro tographdata!(var::Symbol, input)
@defloc
evar = esc(var)
:($evar = $(tographdata(loc, var, input)))
end
export @tographdata!
8 changes: 4 additions & 4 deletions src/GraphDataInputs/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ inspace((a, b), (x, y)) = inspace(a, x) && inspace(b, y)
# Pretty display for maps and adjacency lists.

display_short(map::Map) = "{$(join(("$(repr(k)): $v" for (k, v) in map), ", "))}"
function display_long(map::Map, level = 0)
function display_long(map::Map; level = 0)
res = "{"
ind(n) = "\n" * repeat(" ", level + n)
for (k, v) in map
Expand All @@ -227,7 +227,7 @@ function display_long(map::Map, level = 0)
end

display_short(map::BinMap) = "{$(join(("$(repr(k))" for k in map), ", "))}"
function display_long(map::BinMap, level = 0)
function display_long(map::BinMap; level = 0)
res = "{"
ind(n) = "\n" * repeat(" ", level + n)
for k in map
Expand All @@ -238,11 +238,11 @@ end

display_short(adj::Union{Adjacency,BinAdjacency}) =
"{$(join(("$(repr(k)): $(display_short(list))" for (k, list) in adj), ", "))}"
function display_long(adj::Union{Adjacency,BinAdjacency}, level = 0)
function display_long(adj::Union{Adjacency,BinAdjacency}; level = 0)
res = "{"
ind(n) = "\n" * repeat(" ", level + n)
for (k, list) in adj
res *= ind(1) * "$(repr(k)) => $(display_long(list, level + 1)),"
res *= ind(1) * "$(repr(k)) => $(display_long(list; level = level + 1)),"
end
res * ind(0) * "}"
end
3 changes: 3 additions & 0 deletions src/Internals/Internals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ const Option{T} = Union{Nothing,T}
using ..EcologicalNetworksDynamics
const equal_fields = EcologicalNetworksDynamics.equal_fields

# Part of future refactoring here.
const Topology = EcologicalNetworksDynamics.Topologies.Topology

include("./macros.jl")
include("./inputs/foodwebs.jl")
include("./inputs/nontrophic_interactions.jl")
Expand Down
3 changes: 3 additions & 0 deletions src/Internals/model/model_parameters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mutable struct ModelParameters
# These don't exactly have an 'empty' variant.
network::Option{EcologicalNetwork}
biorates::BioRates # (this one does but all values are initially 'nothing' inside)
_topology::Topology # This will actually be part of the future refactoring.
functional_response::Option{FunctionalResponse}
producer_growth::Option{ProducerGrowth}
# Since 'foodweb' is still a mandatory input to construct interaction layers,
Expand Down Expand Up @@ -38,6 +39,7 @@ mutable struct ModelParameters
NoTemperatureResponse(),
nothing,
BioRates(),
Topology(),
repeat([nothing], 3)...,
Dict(),
Dict(),
Expand Down Expand Up @@ -181,6 +183,7 @@ function ModelParameters(
temperature_response,
network,
biorates,
Topology(),
functional_response,
producer_growth,
nothing,
Expand Down
Loading

0 comments on commit eef9415

Please sign in to comment.