Iron proof of concept (scalar module)#352
Conversation
* Add Scalar overloads for relevant functions * Add scalar iron_tendency which does not depend on grid lookups or PISCES-specific objects * Add IronTendencyArgs struct * Expand test for new scalar iron_tendency
|
Hi @nanophyto, Thank you for sharing this progress. I've started going through to try and understand and will review once I've understood more. I had a few questions to start with:
function (bgc::IronPISCES)(i, j, k, ...)
iron_args = ...
small_particle_reminersation = small_particle_reminersation(bgc, iron_args) # perhaps just a subset of the relivant arguments?
aggregation_of_colloidal_iron = aggregation_of_colloidal_iron(bgc, iron_args)
...
return small_particle_reminiersation - aggregation_of_colloidal_iron + ...
endWhen I have written the models in the past I have found that with scalar forms it can be very hard to put different versions together because you have the big long lists of arguments. If I understand right then I think we would achieve the goal of being able to reuse the parameterisations by still writing discrete form top level tendencies that directly use continuous/scalar form parameterisations, because then in a new model combination we would just have to pull out the correct values/parameters and call the parameterisation. |
|
In summary I think I'm not sure why we need the intermediate scalar form |
|
Both great questions. Yes, IronTendencyArgs could definitely just be a NamedTuple... You are right that having an scalar iron_tendency does not add a lot at the moment and in your example could be avoided. The main purpose at this point is to make the maths ( But to make things more concrete, the eventual vision (and point of scalar iron_tendency) is something like this: Hopefully, a scalar version in that context makes more sense? |
|
Sorry it took me a while to reply to this. I'm not sure I understand the new design still and why going I think we can achieve the end goal of modularity like your describing by maintaining something more similar to the existing code. Starting with the current version: if we were to put this somewhere with more abstraction like I've made in #341 ( BiologyNutrientDetritus) then we can have something like (bgc::BiologyNutrientsDetritus{<:Any, NutrientsWithIron})(i, j, k... where NutrientsWithIron points to some nutrient model that specifies that it uses a PISCES like iron scheme.
Then we can abstract the function like: @inline function (bgc::SimpleIronPISCES)(i, j, k, grid, val_name::Val{:Fe}, clock, fields, auxiliary_fields)
λ̄ = bgc.iron.excess_scavenging_enhancement
Fe = @inbounds fields.Fe[i, j, k]
λFe = iron_scavenging_rate(bgc.particulate_organic_matter, i, j, k, grid, bgc, clock, fields, auxiliary_fields)
Fe′ = free_iron(bgc.iron, i, j, k, grid, bgc, clock, fields, auxiliary_fields)
total_ligand_concentration = ligand_concentration(bgc.iron, i, j, k, grid, bgc, clock, fields, auxiliary_fields)
# terminal process which removes iron from the ocean
ligand_aggregation = λ̄ * λFe * max(0, Fe - total_ligand_concentration) * Fe′
biological_uptake = iron_uptake(bgc, i, j, k, ...
biological_waste = biology_inorganic_iron_loss(bgc, i, j, k, ...
detritus_waste = detritus_inorganic_iron_loss(bgc, i, j, k, ...
return (biological_waste - biological_uptake - ligand_aggregation)
end where the biology tendencies belong to the biology, so without changing the rest of the PISCES code: iron_uptake(bgc::BiologyNutrientDetritus{<:PISCESBiology}, i, j, k, ... ) = uptake(bgc.phytoplankton, Val(:Fe), i, j, k, grid, bgc, clock, fields, auxiliary_fields) (probably this isn't the best and we should do some refactoring to the PISCES code to maybe just define iron_uptake directly instead of uptake Then the waste terms are: biology_inorganic_iron_loss(bgc::BiologyNutrientDetritus{<:PISCESBiology}, i, j, ...) =
non_assimilated_iron(bgc.zooplankton, i, j, k, grid, bgc, clock, fields, auxiliary_fields) +
upper_trophic_dissolved_iron(bgc.zooplankton, i, j, k, grid, bgc, clock, fields, auxiliary_fields) and detritus_inorganic_iron_loss(bgc::BiologyNutrientDetritus{<:Any, <:Any, <:PISCESDetritus}, i, k, ...) =
aggregation_of_colloidal_iron(bgc.dissolved_organic_matter, i, j, k, grid, bgc, clock, fields, auxiliary_fields) +
iron_scavenging(bgc.particulate_organic_matter, i, j, k, grid, bgc, clock, fields, auxiliary_fields) +
bacterial_iron_uptake(bgc.particulate_organic_matter, i, j, k, grid, bgc, clock, fields, auxiliary_fields) +
degradation(bgc.particulate_organic_matter, Val(:SFe), i, j, k, grid, bgc, clock, fields, auxiliary_fields) (again we probably want to refactor PISCES, and if we were to put PISCES into the BND format we would need to do e.g.
iron_uptake(bgc::BiologyNutrientDetritus{<:AgatePZ}, i, j, ...) =
sum(...etc. I don't know that this is the best abstraction but I think something like this were we encode the minimum constraints while keeping it as simple as possible would be better. Again, I'm not tied to this idea but maybe a good step would be to take the iron formulation from PISCES and port it into the format in #341? Then we could work on interfacing Agate PZ into that format too so it can interchange different nutrient and detritus formulations? For example, as that PR is now we could define it as iron_uptake(bgc::BiologyNutrientDetritus{<:PhytoZoo}, args...) = nutrient_uptake(bgc, args...)
biology_inorganic_iron_loss(bgc::...., args...) = bgc.biology.iron_ration * biology_inorganic_nitrogen_waste(bgc, args...)
detritus_inorganic_iron_loss(bgc::BiologyNutrientDetritus{<:Any, <:Any, <:TwoParticleAndDissolved}, args...) = bgc.biology.iron_ratio * detritus_inorganic_nitrogen_waste(bgc, args...)since its fixed Redfield ratio but can be limited by iron (would probably need to change a couple of other things so that |
@jagoosw @johnryantaylor
Following our initial discussion (#343), I've started on a PISCES iron proof of concept refactor. Note that is just the first step of the iron refactor, but due to the complexity a good point to touch base.
I've gone through the dissolved iron module (and it's dependencies) and made everything scalar, such that there is no value look-ups inside the module. In addition, I've made sure that the module does not depend on PISCES-specific structs such as
dom,pom,zooetc.Instead, there are now two functions:
For implementation, I have added "scalar overloads" for all the relevant functions (e.g.
non_assimilated_iron), and removed the "grid lookup" forms as appopriate. Since some functions are not iron specific, they now have both a scalar and grid-lookup form. The idea is to remove the latter forms, once all the PISCES module have been updated.I've also included an
IronTendencyArgsstruct which holds all the args needed for the scalariron_tendency(). This is optional, but tidier since this arg list is rather long. In this context, I have also used long-form arguments instead of symbols to keep the struct more readable/generalizable.What is currently missing is that the model still hard codes e.g. microzooplankton(P), mesozooplankton (M), nanophytoplankton (P), and diatoms (D). It is thus not currently composable with e.g. a MARBL model that has 8P-4Z, or DARWIN type models... This next step still requires some more discussion and thought which I will post in (#343), but I'm aware the diff file is already quite large, so it might be worth finalizing this PR before moving on to the next step?
To keep the main branch tidy, I started a new branch called
PISCES-refactorwhile we are still figuring out the final model structure.Reminder to myself: