Skip to content

Commit

Permalink
🚧 Rename Growth -> GrowthRate.
Browse files Browse the repository at this point in the history
  • Loading branch information
iago-lito committed Nov 9, 2023
1 parent 25d2498 commit 239e451
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 108 deletions.
83 changes: 42 additions & 41 deletions src/components/growth.jl → src/components/growth_rate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,41 @@

# ==========================================================================================

abstract type Growth <: ModelBlueprint end
abstract type GrowthRate <: ModelBlueprint end
# All subtypes must require(Foodweb).

# Construct either variant based on user input,
# but disallow direct allometric input in this constructor,
# because `Growth(a_p=1, b_p=2)` could either be written
# because `GrowthRate(a_p=1, b_p=2)` could either be written
# by a user wanting simple allometry
# or by a user wanting temperature allometry
# but having forgotten to specify a value for `E_a`.
# Better stop them with an error then than keep going wit non-temperature-dependent values.
function Growth(r)
function GrowthRate(r)

@check_if_symbol r (:Miele2019, :Binzer2016)

if r == :Miele2019
GrowthFromAllometry(r)
GrowthRateFromAllometry(r)
elseif r == :Binzer2016
GrowthFromTemperature(r)
GrowthRateFromTemperature(r)
else
GrowthFromRawValues(r)
GrowthRateFromRawValues(r)
end

end

export Growth
export GrowthRate

#-------------------------------------------------------------------------------------------
# First variant: user provides raw growth rates.

mutable struct GrowthFromRawValues <: Growth
mutable struct GrowthRateFromRawValues <: GrowthRate
r::@GraphData {Scalar, SparseVector, Map}{Float64}
GrowthFromRawValues(r) = new(@tographdata r SNK{Float64})
GrowthRateFromRawValues(r) = new(@tographdata r SNK{Float64})
end

function F.check(model, bp::GrowthFromRawValues)
function F.check(model, bp::GrowthRateFromRawValues)
(; _producers_mask, _producers_sparse_index) = model
(; r) = bp

Expand All @@ -59,10 +59,10 @@ function store_legacy_r!(model, r::SparseVector{Float64})
# The legacy format is a dense vector.
model.biorates.r = collect(r)
# Keep a true sparse version in the cache.
model._cache[:growth] = r
model._cache[:growth_rate] = r
end

function F.expand!(model, bp::GrowthFromRawValues)
function F.expand!(model, bp::GrowthRateFromRawValues)
(; _producers_mask, _species_index) = model
(; r) = bp

Expand All @@ -72,69 +72,69 @@ function F.expand!(model, bp::GrowthFromRawValues)
store_legacy_r!(model, r)
end

@component GrowthFromRawValues requires(Foodweb)
export GrowthFromRawValues
@component GrowthRateFromRawValues requires(Foodweb)
export GrowthRateFromRawValues

#-------------------------------------------------------------------------------------------
# Second variant: user provides allometric rates (no temperature).

miele2019_growth_allometry_rates() = Allometry(; producer = (a = 1, b = -1 / 4))

mutable struct GrowthFromAllometry <: Growth
mutable struct GrowthRateFromAllometry <: GrowthRate
allometry::Allometry
GrowthFromAllometry(; kwargs...) = new(parse_allometry_arguments(kwargs))
GrowthFromAllometry(allometry::Allometry) = new(allometry)
GrowthRateFromAllometry(; kwargs...) = new(parse_allometry_arguments(kwargs))
GrowthRateFromAllometry(allometry::Allometry) = new(allometry)
# Default values.
function GrowthFromAllometry(default::Symbol)
function GrowthRateFromAllometry(default::Symbol)
@check_if_symbol default (:Miele2019,)
@build_from_symbol default (:Miele2019 => new(miele2019_growth_allometry_rates()))
end
end

F.buildsfrom(::GrowthFromAllometry) = [BodyMass, MetabolicClass]
F.buildsfrom(::GrowthRateFromAllometry) = [BodyMass, MetabolicClass]

function F.check(_, bp::GrowthFromAllometry)
function F.check(_, bp::GrowthRateFromAllometry)
al = bp.allometry
check_template(al, miele2019_growth_allometry_rates(), "growth rate")
check_template(al, miele2019_growth_allometry_rates(), "growth rates")
end

function F.expand!(model, bp::GrowthFromAllometry)
function F.expand!(model, bp::GrowthRateFromAllometry)
(; _M, _metabolic_classes, _producers_mask) = model
r = sparse_nodes_allometry(bp.allometry, _producers_mask, _M, _metabolic_classes)
store_legacy_r!(model, r)
end

@component GrowthFromAllometry requires(Foodweb)
export GrowthFromAllometry
@component GrowthRateFromAllometry requires(Foodweb)
export GrowthRateFromAllometry

#-------------------------------------------------------------------------------------------
# Last variant: user provides allometric rates and activation energy (temperature).

binzer2016_growth_allometry_rates() =
(E_a = -0.84, allometry = Allometry(; producer = (a = exp(-15.68), b = -0.25)))

mutable struct GrowthFromTemperature <: Growth
mutable struct GrowthRateFromTemperature <: GrowthRate
E_a::Float64
allometry::Allometry
GrowthFromTemperature(E_a; kwargs...) = new(E_a, parse_allometry_arguments(kwargs))
GrowthFromTemperature(E_a, allometry::Allometry) = new(E_a, allometry)
function GrowthFromTemperature(default::Symbol)
GrowthRateFromTemperature(E_a; kwargs...) = new(E_a, parse_allometry_arguments(kwargs))
GrowthRateFromTemperature(E_a, allometry::Allometry) = new(E_a, allometry)
function GrowthRateFromTemperature(default::Symbol)
@check_if_symbol default (:Binzer2016,)
return @build_from_symbol default (
:Binzer2016 => new(binzer2016_growth_allometry_rates()...)
)
end
end

F.buildsfrom(::GrowthFromTemperature) = [Temperature, BodyMass, MetabolicClass]
F.buildsfrom(::GrowthRateFromTemperature) = [Temperature, BodyMass, MetabolicClass]

function F.check(_, bp::GrowthFromTemperature)
function F.check(_, bp::GrowthRateFromTemperature)
al = bp.allometry
(_, template) = binzer2016_growth_allometry_rates()
check_template(al, template, "growth rate from temperature")
check_template(al, template, "growth rates from temperature")
end

function F.expand!(model, bp::GrowthFromTemperature)
function F.expand!(model, bp::GrowthRateFromTemperature)
(; _M, T, _metabolic_classes, _producers_mask) = model
(; E_a) = bp
r = sparse_nodes_allometry(
Expand All @@ -148,33 +148,34 @@ function F.expand!(model, bp::GrowthFromTemperature)
store_legacy_r!(model, r)
end

@component GrowthFromTemperature requires(Foodweb)
export GrowthFromTemperature
@component GrowthRateFromTemperature requires(Foodweb)
export GrowthRateFromTemperature

#-------------------------------------------------------------------------------------------
# Don't specify simultaneously.
@conflicts(GrowthFromRawValues, GrowthFromAllometry, GrowthFromTemperature)
@conflicts(GrowthRateFromRawValues, GrowthRateFromAllometry, GrowthRateFromTemperature)
# Temporary semantic fix before framework refactoring.
F.componentof(::Type{<:Growth}) = Growth
F.componentof(::Type{<:GrowthRate}) = GrowthRate

# ==========================================================================================
# These rates are terminal (yet): they can be both queried and modified.

@expose_data nodes begin
property(growth, r)
property(growth_rate, r)
get(GrowthRates{Float64}, sparse, "producer")
ref_cache(m -> nothing) # Cache loaded on component expansion.
template(m -> m._producers_mask)
write!((m, rhs, i) -> (m.biorates.r[i] = rhs))
@species_index
depends(Growth)
depends(GrowthRate)
end

# ==========================================================================================
# Display.

# Highjack display to make it like all blueprints provide the same component.
display_short(bp::Growth; kwargs...) = display_short(bp, Growth; kwargs...)
display_long(bp::Growth; kwargs...) = display_long(bp, Growth; kwargs...)
display_short(bp::GrowthRate; kwargs...) = display_short(bp, GrowthRate; kwargs...)
display_long(bp::GrowthRate; kwargs...) = display_long(bp, GrowthRate; kwargs...)

F.display(model, ::Type{<:Growth}) = "Growth: [$(join_elided(model._growth, ", "))]"
F.display(model, ::Type{<:GrowthRate}) =
"GrowthRate: [$(join_elided(model._growth_rate, ", "))]"
2 changes: 1 addition & 1 deletion src/components/main.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ include("./allometry.jl")

# Models (with comments etc.)
include("./hill_exponent.jl") # Example graph-level data.
include("./growth.jl") # Example nodes-level data.
include("./growth_rate.jl") # Example nodes-level data.
include("./efficiency.jl") # Example edges-level data.

# Replicated/adapted from the above.
Expand Down
6 changes: 3 additions & 3 deletions src/components/producer_growth.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export ProducerGrowth
# Simple logistic growth.

mutable struct LogisticGrowth <: ProducerGrowth
r::Option{Growth}
r::Option{GrowthRate}
K::Option{CarryingCapacity}
producers_competition::Option{ProducersCompetition}
LogisticGrowth(; kwargs...) = new(
Expand All @@ -29,7 +29,7 @@ function F.expand!(model, ::LogisticGrowth)
# Alias so values gets updated on component `write!`.
s[:producers_competition],
s[:carrying_capacity],
# Growth rate is already stored in `model.biorates` at this point.
# Growth rates are already stored in `model.biorates` at this point.
)
model.producer_growth = lg
end
Expand All @@ -44,7 +44,7 @@ export LogisticGrowth
# Alternately, the number of nodes can be inferred
# from the non-scalar values if any is given.
mutable struct NutrientIntake <: ProducerGrowth
r::Option{Growth}
r::Option{GrowthRate}
nodes::Option{Nutrients.Nodes}
turnover::Option{Nutrients.Turnover}
supply::Option{Nutrients.Supply}
Expand Down
6 changes: 3 additions & 3 deletions src/default_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -222,21 +222,21 @@ function default_model(
ProducerGrowth,
() -> if nutrients_given
NutrientIntake(;
r = tb!(Growth, temperature_given ? :Binzer2016 : :Miele2019),
r = tb!(GrowthRate, temperature_given ? :Binzer2016 : :Miele2019),
turnover = tb!(N.Turnover, 0.25),
supply = tb!(N.Supply, 10),
concentration = tb!(N.Concentration, 1),
half_saturation = tb!(N.HalfSaturation, 1),
)
elseif temperature_given
LogisticGrowth(;
r = tb!(Growth, :Binzer2016),
r = tb!(GrowthRate, :Binzer2016),
K = tb!(CarryingCapacity, :Binzer2016),
producers_competition = tb!(ProducersCompetition, (; diag = 1)),
)
else
LogisticGrowth(;
r = tb!(Growth, :Miele2019),
r = tb!(GrowthRate, :Miele2019),
K = tb!(CarryingCapacity, 1),
producers_competition = tb!(ProducersCompetition, (; diag = 1)),
)
Expand Down
36 changes: 18 additions & 18 deletions test/user/03-exposed_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -158,47 +158,47 @@ end
m = default_model(Foodweb([:a => [:b, :c], :b => [:c, :d]]))

# Allow single-value write.
@test m.growth == [0, 0, 1, 1]
m.growth[3] = 2
@test m.growth == [0, 0, 2, 1]
@test m.r == [0, 0, 2, 1] # (no matter the alias)
@test m.r == [0, 0, 1, 1]
m.r[3] = 2
@test m.r == [0, 0, 2, 1]
@test m.growth_rate == [0, 0, 2, 1] # (no matter the alias)

m.growth[:c] = 3
@test m.growth == m.r == [0, 0, 3, 1]
m.r[:c] = 3
@test m.r == m.growth_rate == [0, 0, 3, 1]

# Allow range-writes, feeling like a regular array.
m.growth[3:4] .= 4
@test m.growth == m.r == [0, 0, 4, 4]
m.growth[3:4] = [5, 6]
@test m.growth == m.r == [0, 0, 5, 6]
m.r[3:4] .= 4
@test m.r == m.growth_rate == [0, 0, 4, 4]
m.r[3:4] = [5, 6]
@test m.r == m.growth_rate == [0, 0, 5, 6]

# Lock the lid of the following pandora box.
@sysfails(m.growth = [0, 0, 7, 8], Property(growth), "This property is read-only.")
@sysfails(m.r = [0, 0, 7, 8], Property(r), "This property is read-only.")
# (allowing the above would easily lead to leaking references or invalidating views)

# But here one correct way to replace the whole data in-place.
m.growth[m.producers_mask] = [9, 10]
@test m.growth == m.r == [0, 0, 9, 10]
m.r[m.producers_mask] = [9, 10]
@test m.r == m.growth_rate == [0, 0, 9, 10]

# So that aliasing views work as expected:
view = m.growth
view = m.r
@test view == [0, 0, 9, 10]
m.growth[3:4] = [11, 12]
m.r[3:4] = [11, 12]
@test view == [0, 0, 11, 12]

# Disallow meaningless writes outside the template.
@viewfails(
m.growth[2] = 1,
m.r[2] = 1,
GR,
"Invalid producer index '2' to write data. Valid indices are:\n [3, 4]"
)
@viewfails(
m.growth[:b] = 1,
m.r[:b] = 1,
GR,
"Invalid producer label 'b' to write data. Valid labels are:\n [:c, :d]"
)
@viewfails(
m.growth[2:3] .= 1,
m.r[2:3] .= 1,
GR,
"Invalid producer index '2' to write data. Valid indices are:\n [3, 4]"
)
Expand Down
16 changes: 10 additions & 6 deletions test/user/04-default_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
# Override default values
@test m.r == [0, 0, 1, 1]
@test m.K == [0, 0, 1, 1]
m = default_model(fw, Growth([0, 0, 2, 3]))
m = default_model(fw, GrowthRate([0, 0, 2, 3]))
@test m.r == [0, 0, 2, 3]
@test m.K == [0, 0, 1, 1] # Unchanged.

# Override default body mass.
m = default_model(fw, BodyMass(1.5))
@test m.growth [0, 0, 0.9036020036098449, 0.9036020036098449] # Allometry update.
@test m.growth_rate [0, 0, 0.9036020036098449, 0.9036020036098449] # Allometry update.
@test m.K [0, 0, 1, 1]

# Override default allometric rates.
m = default_model(fw, BodyMass(1.5), GrowthFromAllometry(; a_p = 0.5, b_p = 0.8))
@test m.growth [0, 0, 0.6915809336112958, 0.6915809336112958]
m = default_model(fw, BodyMass(1.5), GrowthRateFromAllometry(; a_p = 0.5, b_p = 0.8))
@test m.growth_rate [0, 0, 0.6915809336112958, 0.6915809336112958]
@test m.K == [0, 0, 1, 1]

# Pick another functional response.
Expand All @@ -48,13 +48,17 @@
@test has_component(m, ClassicResponse)
g = 1.0799310794944612e-7
@test m.T == 290
@test m.growth [0, 0, g, g]
@test m.growth_rate [0, 0, g, g]

# Opt-out of some default components.
m = default_model(fw; without = ProducerGrowth)
@test !has_component(m, LogisticGrowth)
@test !has_component(m, ProducerGrowth)
@sysfails(m.r, Property(r), "A component '$Growth' is required to read this property.")
@sysfails(
m.r,
Property(r),
"A component '$GrowthRate' is required to read this property."
)

# Still, add it later.
m += LogisticGrowth(; r = 2, K = 4)
Expand Down
6 changes: 3 additions & 3 deletions test/user/code_components/logistic_growth.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@

# Cannot bring blueprints if corresponding components are already there.
@sysfails(
base + Growth(5) + LogisticGrowth(; r = 1),
base + GrowthRate(5) + LogisticGrowth(; r = 1),
Check(LogisticGrowth),
"blueprint also brings '$Growth', which is already in the system."
"blueprint also brings '$GrowthRate', which is already in the system."
)

# In this situation, just stop bringing.
m = base + Growth(5) + LogisticGrowth(; r = nothing)
m = base + GrowthRate(5) + LogisticGrowth(; r = nothing)
@test m.r == [5, 5, 5, 0, 0]

end
Loading

0 comments on commit 239e451

Please sign in to comment.