diff --git a/src/components/growth.jl b/src/components/growth_rate.jl similarity index 66% rename from src/components/growth.jl rename to src/components/growth_rate.jl index 79290ef70..39b6bd695 100644 --- a/src/components/growth.jl +++ b/src/components/growth_rate.jl @@ -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 @@ -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 @@ -72,40 +72,40 @@ 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). @@ -113,12 +113,12 @@ export GrowthFromAllometry 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()...) @@ -126,15 +126,15 @@ mutable struct GrowthFromTemperature <: Growth 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( @@ -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, ", "))]" diff --git a/src/components/main.jl b/src/components/main.jl index e48644d95..d8584cf6e 100644 --- a/src/components/main.jl +++ b/src/components/main.jl @@ -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. diff --git a/src/components/producer_growth.jl b/src/components/producer_growth.jl index 3fe5b0a51..056584d4e 100644 --- a/src/components/producer_growth.jl +++ b/src/components/producer_growth.jl @@ -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( @@ -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 @@ -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} diff --git a/src/default_model.jl b/src/default_model.jl index 1b6362592..e99957a43 100644 --- a/src/default_model.jl +++ b/src/default_model.jl @@ -222,7 +222,7 @@ 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), @@ -230,13 +230,13 @@ function default_model( ) 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)), ) diff --git a/test/user/03-exposed_data.jl b/test/user/03-exposed_data.jl index e1f6d44ee..8c118ae49 100644 --- a/test/user/03-exposed_data.jl +++ b/test/user/03-exposed_data.jl @@ -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]" ) diff --git a/test/user/04-default_model.jl b/test/user/04-default_model.jl index 78f2e63a2..97e066deb 100644 --- a/test/user/04-default_model.jl +++ b/test/user/04-default_model.jl @@ -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. @@ -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) diff --git a/test/user/code_components/logistic_growth.jl b/test/user/code_components/logistic_growth.jl index da1ed5717..d05732cc4 100644 --- a/test/user/code_components/logistic_growth.jl +++ b/test/user/code_components/logistic_growth.jl @@ -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 diff --git a/test/user/data_components/growth.jl b/test/user/data_components/growth_rate.jl similarity index 54% rename from test/user/data_components/growth.jl rename to test/user/data_components/growth_rate.jl index 72ea11a74..390b5d0c0 100644 --- a/test/user/data_components/growth.jl +++ b/test/user/data_components/growth_rate.jl @@ -1,34 +1,34 @@ -FR = GrowthFromRawValues -FA = GrowthFromAllometry -FT = GrowthFromTemperature +FR = GrowthRateFromRawValues +FA = GrowthRateFromAllometry +FT = GrowthRateFromTemperature -@testset "Growth component." begin +@testset "GrowthRate component." begin base = Model(Foodweb([:a => [:b, :c], :b => :c])) #--------------------------------------------------------------------------------------- # Construct from raw values. - g = Growth([:c => 3]) - m = base + g - @test m.growth == [0, 0, 3] == m.r - @test typeof(g) === FR + gr = GrowthRate([:c => 3]) + m = base + gr + @test m.growth_rate == [0, 0, 3] == m.r + @test typeof(gr) === FR # Only producers indices allowed. @sysfails( - base + Growth([:a => 1]), + base + GrowthRate([:a => 1]), Check(FR), "Invalid 'producers' node label in 'r'. Expected :c, got instead: :a." ) - g = Growth([0, 0, 4]) - m = base + g - @test m.growth == [0, 0, 4] == m.r - @test typeof(g) === FR + gr = GrowthRate([0, 0, 4]) + m = base + gr + @test m.growth_rate == [0, 0, 4] == m.r + @test typeof(gr) === FR # Only producers values allowed. @sysfails( - base + Growth([4, 5, 7]), + base + GrowthRate([4, 5, 7]), Check(FR), "Non-missing value found for 'r' at node index [1] (4.0), \ but the template for 'producers' only allows values \ @@ -40,48 +40,48 @@ FT = GrowthFromTemperature base += BodyMass(; Z = 1) + MetabolicClass(:all_invertebrates) - g = Growth(:Miele2019) - @test typeof(g) == FA - @test g.allometry[:p][:a] == 1 - @test g.allometry[:p][:b] == -1 / 4 + gr = GrowthRate(:Miele2019) + @test typeof(gr) == FA + @test gr.allometry[:p][:a] == 1 + @test gr.allometry[:p][:b] == -1 / 4 # Alternative explicit input. - @test g == GrowthFromAllometry(; a_p = 1, b_p = -0.25) + @test gr == GrowthRateFromAllometry(; a_p = 1, b_p = -0.25) - m = base + g - @test m.growth == [0, 0, 1] + m = base + gr + @test m.growth_rate == [0, 0, 1] # Forbid unnecessary allometric parameters. @sysfails( - base + GrowthFromAllometry(; p = (a = 1, b = 0.25, c = 8)), + base + GrowthRateFromAllometry(; p = (a = 1, b = 0.25, c = 8)), Check(FA), "Allometric parameter 'c' (target_exponent) for 'producer' \ - is meaningless in the context of calculating growth rate: 8.0." + is meaningless in the context of calculating growth rates: 8.0." ) @sysfails( - base + GrowthFromAllometry(; p = (a = 1, b = 0.25), i_a = 8), + base + GrowthRateFromAllometry(; p = (a = 1, b = 0.25), i_a = 8), Check(FA), "Allometric rates for 'invertebrate' \ - are meaningless in the context of calculating growth rate: (a: 8.0)." + are meaningless in the context of calculating growth rates: (a: 8.0)." ) #--------------------------------------------------------------------------------------- # Construct from temperature. - g = Growth(:Binzer2016) - @test typeof(g) == FT - @test g.E_a == -0.84 # This one also has an activation energy. - @test g.allometry == Allometry(; p = (a = 1.5497531357028967e-7, b = -1 / 4)) + gr = GrowthRate(:Binzer2016) + @test typeof(gr) == FT + @test gr.E_a == -0.84 # This one also has an activation energy. + @test gr.allometry == Allometry(; p = (a = 1.5497531357028967e-7, b = -1 / 4)) # Alternative explicit input. - @test g == GrowthFromTemperature(-0.84; p_a = 1.5497531357028967e-7, p_b = -1 / 4) + @test gr == GrowthRateFromTemperature(-0.84; p_a = 1.5497531357028967e-7, p_b = -1 / 4) - m = base + Temperature(298.5) + g - @test m.growth == [0, 0, 2.812547966878026e-7] + m = base + Temperature(298.5) + gr + @test m.growth_rate == [0, 0, 2.812547966878026e-7] # Forbid if no temperature is available. @sysfails( - base + g, + base + gr, Check(FT), "blueprint cannot expand without component '$Temperature'." )