Skip to content

Commit

Permalink
🚧 Efficiency upgraded.
Browse files Browse the repository at this point in the history
  • Loading branch information
iago-lito committed Oct 28, 2024
1 parent adea5e7 commit 615e2f5
Show file tree
Hide file tree
Showing 19 changed files with 297 additions and 101 deletions.
30 changes: 20 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
to transfer input to correct blueprint constructors
- Blueprints typically have different types based on their inputs.

```julia
```julia-repl
julia> Species
Species (component for <internals>, expandable from:
Names: raw species names,
Expand All @@ -32,7 +32,7 @@
- Equivalent `get_*` and `set_*!` methods may still exist
but they are no longer exposed or recommended:
use direct property accesses instead.
```julia
```julia-repl
julia> m = Model(Species(3))
julia> m.species.number # (no more `.n_species` or `get_n_species(m)`)
3
Expand All @@ -48,14 +48,17 @@
The alias `model.A` is still available.
- Likewise,
`model.herbivorous_links` becomes `model.trophic.herbivory_matrix` *etc.*
- Akward plurals like `model.body_masses` and `model.metabolic_classes`
become `model.body_mass` and `model.metabolic_class`.


## New features

- Model properties available with `<tab>`-completion within the REPL.

- Every blueprint *brought* by another is available as a brought field
to be either *embedded*, *implied* or *unbrought*:
```julia
```julia-repl
julia> fw = Foodweb.Matrix([0 0; 1 0]) # Implied (brought if missing).
blueprint for <Foodweb>: Matrix {
A: 1 trophic link,
Expand All @@ -80,26 +83,33 @@
- Every "leaf" "geometrical" model property *i.e.* a property whose futher
model topology does not depend on or is not planned to depend on
is now writeable.
```julia
```julia-repl
julia> m = Model(fw, BodyMass(2));
m.M[1] *= 10;
m.M == [20, 2]
true
```

- Values are checked prior to expansion:
```julia
```julia-repl
julia> m = Model(fw, Efficiency(1.5))
TODO
ERROR: Blueprint value cannot be expanded:
Not a value within [0, 1]: e = 1.5.
```

- Efficiency from a matrix implies a Foodweb.
```julia
```julia-repl
julia> e = 0.5;
m = Model(fw, Efficiency([
m = Model(Efficiency([
0 e e
0 0 e
e 0 0
]))
TODO
]));
has_component(m, Foodweb)
true
julia> m.A
3×3 EcologicalNetworksDynamics.TrophicMatrix:
0 1 1
0 0 1
1 0 0
```
19 changes: 18 additions & 1 deletion src/GraphDataInputs/convert.jl
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,23 @@ graphdataconvert(::Type{Adjacency{<:Any,T}}, input::Adjacency{Int64,T}) where {T
graphdataconvert(::Type{BinAdjacency{<:Any}}, input::BinAdjacency{Symbol}) = input
graphdataconvert(::Type{BinAdjacency{<:Any}}, input::BinAdjacency{Int64}) = input

#-------------------------------------------------------------------------------------------
# Extract binary maps/adjacency from regular ones.
function graphdataconvert(::Type{BinMap}, input::Map{I}) where {I}
res = BinMap{I}()
for (k, _) in input
push!(res, k)
end
res
end
function graphdataconvert(::Type{BinAdjacency{<:Any}}, input::Adjacency{I}) where {I}
res = BinAdjacency{I}()
for (i, sub) in input
res[i] = graphdataconvert(BinMap, sub)
end
res
end

#-------------------------------------------------------------------------------------------
# Conversion helpers.

Expand Down Expand Up @@ -366,7 +383,7 @@ function _tographdata(vsym, var, targets)
argerr("Error while attempting to convert \
'$vsym' to $Target \
(details further down the stacktrace). \
Received $(repr(var))::$(typeof(var)).")
Received $(repr(var)) ::$(typeof(var)).")
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion src/GraphDataInputs/expand.jl
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ macro to_sparse_matrix_if_adjacency(var::Symbol, i_index, j_index)
var, i_index, j_index = esc.((var, i_index, j_index))
quote
$var isa
Union{AbstractDict{<:Any,<:AbstrsactDict},AbstractDict{<:Any,<:AbstractSet}} &&
Union{AbstractDict{<:Any,<:AbstractDict},AbstractDict{<:Any,<:AbstractSet}} &&
($var = to_sparse_matrix($var, $i_index, $j_index))
end
end
Expand Down
2 changes: 1 addition & 1 deletion src/components/body_mass.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ end

# Basic query.
@expose_data nodes begin
property(body_masses, M)
property(body_mass, M)
depends(BodyMass)
@species_index
ref(raw -> raw._foodweb.M)
Expand Down
24 changes: 16 additions & 8 deletions src/components/efficiency.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ F.implied_blueprint_for(bp::Raw, ::_Foodweb) = Foodweb(bp.e .!= 0)
@blueprint Raw "matrix"
export Raw

F.early_check(bp::Raw) = check_edges(bp.e, check)
F.early_check(bp::Raw) = check_edges(check, bp.e)
check(e, ref = nothing) =
check_value(e -> 0 <= e <= 1, e, ref, :e, "Not a value within [0, 1]")

function F.late_check(raw, bp::Raw)
(; e) = bp
A = @ref raw.trophic.matrix
@check_template e A :trophic_links
@check_template e A "trophic links"
end

F.expand!(raw, bp::Raw) = expand!(raw, bp.e)
Expand Down Expand Up @@ -65,16 +65,19 @@ mutable struct Adjacency <: Blueprint
foodweb::Brought(Foodweb)
Adjacency(e, foodweb = _Foodweb) = new(@tographdata(e, Adjacency{Float64}), foodweb)
end
F.implied_blueprint_for(bp::Adjacency, ::_Foodweb) = Foodweb(refs(bp.e))
function F.implied_blueprint_for(bp::Adjacency, ::_Foodweb)
(; e) = bp
Foodweb(@tographdata e Adjacency{:bin})
end
@blueprint Adjacency "[predactor => [prey => efficiency]] adjacency list"
export Adjacency

F.early_check(bp::Adjacency) = check_edges(bp.e, check)
F.early_check(bp::Adjacency) = check_edges(check, bp.e)
function F.late_check(raw, bp::Adjacency)
(; e) = bp
index = @ref raw.species.index
A = @ref raw.trophic.matrix
@check_list_refs e :trophic_link index template(A)
@check_list_refs e "trophic link" index template(A)
end

function F.expand!(raw, bp::Adjacency)
Expand Down Expand Up @@ -124,12 +127,17 @@ export Efficiency

function (::_Efficiency)(e; kwargs...)

e = @tographdata e {Symbol, SparseMatrix, Adjacency}{Float64}
e = @tographdata e {Symbol, Scalar, SparseMatrix, Adjacency}{Float64}
@check_if_symbol e (:Miele2019,)
@kwargs_helpers kwargs

if e == :Miele2019
Efficiency.Miele2019(; kwargs...)
elseif e isa SparseMatrix
return Efficiency.Miele2019(; kwargs...)
end

no_unused_arguments()

if e isa SparseMatrix
Efficiency.Raw(e)
elseif e isa Real
Efficiency.Flat(e)
Expand Down
6 changes: 3 additions & 3 deletions src/components/growth_rate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ end

function F.expand!(raw, bp::Allometric)
M = @ref raw.M
mc = @ref raw.metabolic_classes
mc = @ref raw.metabolic_class
prods = @ref raw.producers.mask
r = sparse_nodes_allometry(bp.allometry, prods, M, mc)
expand!(raw, r)
Expand Down Expand Up @@ -154,7 +154,7 @@ function F.expand!(raw, bp::Temperature)
(; E_a) = bp
T = @get raw.T
M = @ref raw.M
mc = @ref raw.metabolic_classes
mc = @ref raw.metabolic_class
prods = @ref raw.producers.mask
r = sparse_nodes_allometry(bp.allometry, prods, M, mc; E_a, T)
expand!(raw, r)
Expand Down Expand Up @@ -203,7 +203,7 @@ end
get(GrowthRates{Float64}, sparse, "producer")
template(raw -> @ref raw.producers.mask)
write!((raw, rhs::Real, i) -> begin
GrowthRate_.check(rhs)
GrowthRate_.check(rhs, i)
rhs = Float64(rhs)
raw.biorates.r[i] = rhs
rhs
Expand Down
1 change: 1 addition & 0 deletions src/components/macros_keywords.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ if (false)
#! format: off
(
local
A,
E,
Scalar,
V,
Expand Down
4 changes: 2 additions & 2 deletions src/components/metabolic_class.jl
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ end

# Basic query.
@expose_data nodes begin
property(metabolic_classes)
property(metabolic_class)
depends(MetabolicClass)
@species_index
ref_cached(raw -> Symbol.(raw._foodweb.metabolic_class)) # Legacy reverse conversion.
Expand All @@ -170,5 +170,5 @@ end

# Display.
function F.shortline(io::IO, model::Model, ::_MetabolicClass)
print(io, "Metabolic classes: [$(join_elided(model.metabolic_classes, ", "))]")
print(io, "Metabolic classes: [$(join_elided(model.metabolic_class, ", "))]")
end
10 changes: 10 additions & 0 deletions src/dedicate_framework_to_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,16 @@ function F.display_blueprint_field_short(
end
print(io, "{$(join_elided(it, ", "; repr = false))}")
end
function F.display_blueprint_field_short(
io::IO,
map::@GraphData(Adjacency{T}),
bp::Blueprint,
) where {T}
it = imap(map) do (k, v)
"$k: $(sprint(F.display_blueprint_field_short, v, bp))"
end
print(io, "{$(join_elided(it, ", "; repr = false))}")
end

F.display_blueprint_field_long(io::IO, v, bp::Blueprint) =
F.display_blueprint_field_short(io, v, bp)
Expand Down
45 changes: 30 additions & 15 deletions src/graph_views.jl
Original file line number Diff line number Diff line change
Expand Up @@ -189,30 +189,45 @@ function check_sparse_index(
item = item_name(v)
level = level_name(v)
n = length(index)
valids = valid_refs(v, template, labels)
refs, vrefs = if isnothing(labels)
("index $index", "indices")
refs = if isnothing(labels)
"index $(inline(index))"
else
("label$(n > 1 : "s" : "") $labels ($index)", "labels")
"label$(n > 1 : "s" : "") $labels ($index)"
end
throw(
ViewError(
typeof(v),
"Invalid $item $refs to write $level data. Valid $vrefs $valids",
"Invalid $item $refs to write $level data. " *
valid_refs_phrase(v, template, labels),
),
)
end
valid_refs(_, template::Vector, ::Nothing) =
"are " * join_elided(findnz(template)[1], ", ", " and "; max = 100)
valid_refs(_, template::Matrix, ::Nothing) =
"are " *
join_elided((ij for ij in zip(findnz(template)[1:2]...)), ", ", " and "; max = 50)
function valid_refs(v, template::Vector, ::Any)
valids = valid_refs(v, template, nothing)
"are " * join_elided((l for (l, i) in v._index if i in valids), ", ", " and "; max = 10)

function valid_refs_phrase(v, template, labels)
valids = collect(valid_refs(v, template, labels))
if isempty(valids)
"There is no valid $(vref(labels)) for this template."
elseif length(valids) == 1
"The only valid $(vref(labels)) for this template is $(first(valids))."
else
max = isnothing(labels) ? (template isa AbstractVector ? 100 : 50) : 10
"Valid $(vrefs(labels)) for this template \
are $(join_elided(valids, ", ", " and "; max))"
end
end
valid_refs_phrase(_, template::AbstractMatrix, ::Nothing) =
"Valid indices must comply to the following template:\n\
$(repr(MIME("text/plain"), template))"
vref(::Nothing) = "index"
vrefs(::Nothing) = "indices"
vref(::Any) = "label"
vrefs(::Any) = "labels"
valid_refs(_, template::AbstractVector, ::Nothing) = findnz(template)[1]
valid_refs(_, template::AbstractMatrix, ::Nothing) = zip(findnz(template)[1:2]...)
function valid_refs(v, template::AbstractVector, ::Any)
valids = Set(valid_refs(v, template, nothing))
(l for (l, i) in v._index if i in valids)
end
valid_refs(_, template::Matrix, ::Any) =
" must comply to the following template:\n$(repr(MIME("text/plain"), template))"


#-------------------------------------------------------------------------------------------
Expand Down
17 changes: 16 additions & 1 deletion test/graph_data_inputs/convert.jl
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@
(@tographdata input YSV{Bool}),
"Error while attempting to convert 'input' to Vector{Bool} \
(details further down the stacktrace). \
Received [0, 1, 2]::Vector{Int64}.",
Received [0, 1, 2] ::Vector{Int64}.",
)
# And down the stacktrace:
@failswith(
Expand Down Expand Up @@ -690,6 +690,7 @@ end
check_nodes(e, [])
check_nodes(oe, [])
# With actual values.
#! format: off
check_edges(OrderedDict([
:a => Dict([:b => 5, :c => 8]),
:b => Dict([:a => 2]),
Expand Down Expand Up @@ -739,5 +740,19 @@ end
((:b,), e),
((:c,), 0),
])
#! format: on

#---------------------------------------------------------------------------------------
# Drop values info to produce binary maps/adjacency lists.

input = [:a => 5, :c => 8]
input = @tographdata input Map{Float64}
result = @tographdata input Map{:bin}
@test result == OrderedSet([:a, :c])

input = [:a => [:b => 5, :d => 9], :c => [:a => 8]]
input = @tographdata input Adjacency{Float64}
result = @tographdata input Adjacency{:bin}
@test result == OrderedDict([:a => OrderedSet([:b, :d]), :c => OrderedSet([:a])])

end
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ sep("Test API utils.")
# include("./topologies.jl")
# include("./aliasing_dicts.jl")
# include("./multiplex_api.jl")
include("./graph_data_inputs/runtests.jl")
# include("./graph_data_inputs/runtests.jl")

sep("Test user-facing behaviour.")
include("./user/runtests.jl")
Expand Down
9 changes: 3 additions & 6 deletions test/user/02-graphviews.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import .EN: WriteError
# Get a graphview type.
fw = Foodweb([:a => :b, :b => :c])
m = Model(fw, BodyMass([1, 2, 3]))
bm = m.body_masses
bm = m.body_mass

# Use as a vector.
@test bm[1] == 1
Expand Down Expand Up @@ -79,13 +79,10 @@ import .EN: WriteError
@viewfails(bm[:a, :b] = 1, BM, m)

# Guard rhs.
@failswith(
(bm[1] = 'a'),
WriteError("not a value of type Real", :body_masses, (1,), 'a')
)
@failswith((bm[1] = 'a'), WriteError("not a value of type Real", :body_mass, (1,), 'a'))
@failswith(
(bm[2:3] *= -10),
WriteError("Not a positive value: M[2] = -50.0.", :body_masses, (2,), -50)
WriteError("Not a positive value: M[2] = -50.0.", :body_mass, (2,), -50)
)

end
Expand Down
Loading

0 comments on commit 615e2f5

Please sign in to comment.