-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🚧 Nutrients.HalfSaturation upgraded: all data components upgraded!
- Loading branch information
Showing
5 changed files
with
212 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,65 +1,117 @@ | ||
# Set or generate half saturations for every producer-to-nutrient link in the model. | ||
# | ||
# Copied and adapted from concentrations. | ||
|
||
# ========================================================================================== | ||
abstract type HalfSaturation <: ModelBlueprint end | ||
# All subtypes must require(Nutrients.Nodes,Foodweb). | ||
# Mostly duplicated from HalfSaturation. | ||
|
||
HalfSaturation(h) = HalfSaturationFromRawValues(h) | ||
export HalfSaturation | ||
# (reassure JuliaLS) | ||
(false) && (local HalfSaturation, _HalfSaturation) | ||
|
||
# ========================================================================================== | ||
module HalfSaturation_ | ||
include("../blueprint_modules.jl") | ||
include("../blueprint_modules_identifiers.jl") | ||
import .EN: Foodweb, Nutrients | ||
|
||
#------------------------------------------------------------------------------------------- | ||
mutable struct HalfSaturationFromRawValues <: HalfSaturation | ||
h::@GraphData {Scalar, Matrix}{Float64} | ||
HalfSaturationFromRawValues(h) = new(@tographdata h SM{Float64}) | ||
mutable struct Raw <: Blueprint | ||
h::Matrix{Float64} | ||
nutrients::Brought(Nutrients.Nodes) | ||
Raw(h, nt = Nutrients._Nodes) = new(Float64.(h), nt) | ||
end | ||
F.implied_blueprint_for(bp::Raw, ::Nutrients._Nodes) = Nutrients.Nodes(size(bp.h)[2]) | ||
@blueprint Raw "producers × nutrients half-saturation matrix" | ||
export Raw | ||
|
||
F.can_imply(bp::HalfSaturationFromRawValues, ::Type{Nutrients.Nodes}) = !(bp.h isa Real) | ||
Nutrients.Nodes(bp::HalfSaturationFromRawValues) = Nutrients.Nodes(size(bp.h, 2)) | ||
F.early_check(bp::Raw) = check_edges(check, bp.h) | ||
check(h, ref = nothing) = check_value(>=(0), h, ref, :h, "Not a positive value") | ||
|
||
function F.check(model, bp::HalfSaturationFromRawValues) | ||
(; n_producers, n_nutrients) = model | ||
function F.late_check(raw, bp::Raw) | ||
(; h) = bp | ||
@check_size_if_matrix h (n_producers, n_nutrients) | ||
P = @get raw.producers.number | ||
N = @get raw.nutrients.number | ||
@check_size h (P, N) | ||
end | ||
|
||
function F.expand!(model, bp::HalfSaturationFromRawValues) | ||
(; n_producers, n_nutrients) = model | ||
(; h) = bp | ||
@to_size_if_scalar Real h (n_producers, n_nutrients) | ||
model._scratch[:nutrients_half_saturation] = h | ||
F.expand!(raw, bp::Raw) = expand!(raw, bp.h) | ||
expand!(raw, h) = raw._scratch[:nutrients_half_saturation] = h | ||
|
||
#------------------------------------------------------------------------------------------- | ||
mutable struct Flat <: Blueprint | ||
h::Float64 | ||
end | ||
@blueprint Flat "uniform half-saturation value" depends(Foodweb, Nutrients.Nodes) | ||
export Flat | ||
|
||
@component HalfSaturationFromRawValues requires(Foodweb) implies(Nutrients.Nodes) | ||
export HalfSaturationFromRawValues | ||
F.early_check(bp::Flat) = check(bp.h) | ||
function F.expand!(raw, bp::Flat) | ||
P = @get raw.producers.number | ||
N = @get raw.nutrients.number | ||
expand!(raw, to_size(bp.h, (P, N))) | ||
end | ||
|
||
#------------------------------------------------------------------------------------------- | ||
# Keep in case more alternate blueprints are added. | ||
# @conflicts(HalfSaturationFromRawValues) | ||
# Temporary semantic fix before framework refactoring. | ||
F.componentof(::Type{<:HalfSaturation}) = HalfSaturation | ||
mutable struct Adjacency <: Blueprint | ||
h::@GraphData Adjacency{Float64} | ||
nutrients::Brought(Nutrients.Nodes) | ||
Adjacency(h, nt = Nutrients._Nodes) = new(@tographdata(h, Adjacency{Float64}), nt) | ||
end | ||
function F.implied_blueprint_for(bp::Adjacency, ::Nutrients._Nodes) | ||
# HERE: this should've been done for every such adjacency implication, right? | ||
space = refspace_inner(bp.h) | ||
if space isa Integer | ||
Nutrients.Nodes(space) | ||
else | ||
Nutrients.Nodes(keys(space)) | ||
end | ||
end | ||
@blueprint Adjacency "[producer => [nutrient => half-saturation]] map" | ||
export Adjacency | ||
|
||
F.early_check(bp::Adjacency) = check_edges(check, bp.h) | ||
function F.late_check(raw, bp::Adjacency) | ||
(; h) = bp | ||
p_index = @ref raw.producers.sparse_index | ||
n_index = @ref raw.nutrients.index | ||
@check_list_refs h "producer trophic" (p_index, n_index) dense | ||
end | ||
|
||
function F.expand!(raw, bp::Adjacency) | ||
p_index = @ref raw.producers.dense_index | ||
n_index = @ref raw.nutrients.index | ||
h = to_dense_matrix(bp.h, p_index, n_index) | ||
expand!(raw, h) | ||
end | ||
|
||
# ========================================================================================== | ||
@expose_data edges begin | ||
property(nutrients_half_saturation) | ||
get(HalfSaturations{Float64}, "producer-to-nutrient link") | ||
ref(m -> m._scratch[:nutrients_half_saturation]) | ||
write!((m, rhs, i, j) -> (m._nutrients_half_saturation[i, j] = rhs)) | ||
row_index(m -> m._producers_dense_index) | ||
col_index(m -> m._nutrients_index) | ||
depends(HalfSaturation) | ||
end | ||
|
||
# ========================================================================================== | ||
display_short(bp::HalfSaturation; kwargs...) = display_short(bp, HalfSaturation; kwargs...) | ||
display_long(bp::HalfSaturation; kwargs...) = display_long(bp, HalfSaturation; kwargs...) | ||
function F.display(model, ::Type{<:HalfSaturation}) | ||
h = model.nutrients_half_saturation | ||
min, max = minimum(h), maximum(h) | ||
"Nutrients half-saturation: " * if min == max | ||
"$min" | ||
@component begin | ||
HalfSaturation{Internal} | ||
requires(Foodweb, Nutrients.Nodes) | ||
blueprints(HalfSaturation_) | ||
end | ||
export HalfSaturation | ||
|
||
function (::_HalfSaturation)(h) | ||
h = @tographdata h {Scalar, Matrix, Adjacency}{Float64} | ||
if h isa Real | ||
HalfSaturation.Flat(h) | ||
elseif h isa AbstractMatrix | ||
HalfSaturation.Raw(h) | ||
else | ||
"ranging from $min to $max." | ||
HalfSaturation.Adjacency(h) | ||
end | ||
end | ||
|
||
@expose_data edges begin | ||
property(nutrients.half_saturation) | ||
depends(HalfSaturation) | ||
@nutrients_index | ||
ref(raw -> raw._scratch[:nutrients_half_saturation]) | ||
get(HalfSaturations{Float64}, "producer-to-nutrient link") | ||
write!((raw, rhs::Real, i, j) -> HalfSaturation_.check(rhs, (i, j))) | ||
end | ||
|
||
function F.shortline(io::IO, model::Model, ::_HalfSaturation) | ||
print(io, "Nutrients half-saturation: ") | ||
showrange(io, model.nutrients._half_saturation) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 114 additions & 20 deletions
134
test/user/data_components/nutrients/half_saturation.jl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,45 +1,139 @@ | ||
FR = EN.Nutrients.HalfSaturationFromRawValues | ||
|
||
@testset "Nutrients half-saturation component." begin | ||
|
||
# Adapted from concentration. | ||
# Mostly duplicated from Concentration. | ||
|
||
fw = Foodweb([:a => [:b, :c]]) | ||
base = Model(fw, Nutrients.Nodes(3)) | ||
|
||
hs = Nutrients.HalfSaturation([ | ||
cn = Nutrients.HalfSaturation([ | ||
1 2 3 | ||
4 5 6 | ||
]) | ||
m = base + hs | ||
@test m.nutrients_half_saturation == [ | ||
m = base + cn | ||
@test m.nutrients.half_saturation == [ | ||
1 2 3 | ||
4 5 6 | ||
] | ||
@test typeof(hs) === FR | ||
@test typeof(cn) === Nutrients.HalfSaturation.Raw | ||
|
||
# Adjacency list. | ||
cn = Nutrients.HalfSaturation([ | ||
:b => [:n1 => 1, :n2 => 2, :n3 => 3], | ||
:c => [:n2 => 5, :n3 => 6, :n1 => 4], | ||
]) | ||
m = base + cn | ||
@test m.nutrients.half_saturation == [ | ||
1 2 3 | ||
4 5 6 | ||
] | ||
@test typeof(cn) === Nutrients.HalfSaturation.Adjacency | ||
|
||
# Scalar. | ||
cn = Nutrients.HalfSaturation(2) | ||
m = base + cn | ||
@test m.nutrients.half_saturation == [ | ||
2 2 2 | ||
2 2 2 | ||
] | ||
@test typeof(cn) === Nutrients.HalfSaturation.Flat | ||
|
||
#--------------------------------------------------------------------------------------- | ||
# Imply Nutrients. | ||
|
||
h = [ | ||
1 2 3 | ||
4 5 6 | ||
] | ||
m = Model(fw, Nutrients.HalfSaturation(h)) | ||
@test has_component(m, Nutrients.Nodes) | ||
@test m.nutrients.half_saturation == h | ||
@test m.nutrients.names == [:n1, :n2, :n3] | ||
@test Model( | ||
fw, | ||
Nutrients.HalfSaturation([ | ||
:b => [:x => 1, :y => 2, :z => 3], | ||
:c => [:z => 5, :x => 6, :y => 4], | ||
]), | ||
).nutrients.names == [:x, :y, :z] | ||
|
||
# ====================================================================================== | ||
# Input guards. | ||
|
||
# Invalid values. | ||
@sysfails( | ||
base + Nutrients.HalfSaturation([ | ||
0 1 -2 | ||
3 0 4 | ||
]), | ||
Check( | ||
early, | ||
[Nutrients.HalfSaturation.Raw], | ||
"Not a positive value: h[1, 3] = -2.0.", | ||
) | ||
) | ||
|
||
@sysfails( | ||
base + Nutrients.HalfSaturation([ | ||
:b => [:n1 => 1, :n2 => 2, :n3 => 3], | ||
:c => [:n2 => 5, :n3 => -6, :n1 => 4], | ||
]), | ||
Check( | ||
early, | ||
[Nutrients.HalfSaturation.Adjacency], | ||
"Not a positive value: h[:c, :n3] = -6.0.", | ||
) | ||
) | ||
|
||
@sysfails( | ||
base + Nutrients.HalfSaturation(-5), | ||
Check(early, [Nutrients.HalfSaturation.Flat], "Not a positive value: h = -5.0.") | ||
) | ||
|
||
# Invalid size. | ||
@sysfails( | ||
base + Nutrients.HalfSaturation([ | ||
0 1 | ||
3 0 | ||
]), | ||
Check(FR), | ||
"Invalid size for parameter 'h': expected (2, 3), got (2, 2).", | ||
Check( | ||
late, | ||
[Nutrients.HalfSaturation.Raw], | ||
"Invalid size for parameter 'h': expected (2, 3), got (2, 2).", | ||
) | ||
) | ||
|
||
# Implies nutrients component. | ||
base = Model(fw) | ||
m = base + Nutrients.HalfSaturation([ | ||
1 2 3 | ||
4 5 6 | ||
]) | ||
@test m.nutrients_names == [:n1, :n2, :n3] | ||
# Non-dense input. | ||
@sysfails( | ||
Model( | ||
fw, | ||
Nutrients.HalfSaturation([ | ||
:b => [:x => 1, :y => 2], | ||
:c => [:z => 5, :x => 6, :y => 4], | ||
]), | ||
), | ||
Check( | ||
late, | ||
[Nutrients.HalfSaturation.Adjacency], | ||
"Missing 'producer trophic' edge label in 'h': \ | ||
no value specified for [:b, :z].", | ||
) | ||
) | ||
|
||
# Unless we can't infer it. | ||
# Respect template. | ||
@sysfails( | ||
base + Nutrients.HalfSaturation(5), | ||
Check(FR), | ||
"missing a required component '$(Nutrients.Nodes)': implied." | ||
Model( | ||
fw, | ||
Nutrients.HalfSaturation([ | ||
:b => [:x => 1, :y => 2, :z => 3], | ||
:a => [:z => 5, :x => 6, :y => 4], | ||
]), | ||
), | ||
Check( | ||
late, | ||
[Nutrients.HalfSaturation.Adjacency], | ||
"Invalid 'producer trophic' edge label in 'h'. \ | ||
Expected either :b or :c, got instead: [:a] (5.0).", | ||
) | ||
) | ||
|
||
end |