Feature/stoichiometry for Animals#726
Conversation
… functional groups, explicitly tracked elemental masses in cohorts, and a grow() cohort-method for constraining mass gain.
…lantresources, and decay for working with stoichiometric dictionaries.
… grow inside of the eat method.
…f stoichiometry explicitly.
…y testing rework.
…el with soil and data.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #726 +/- ##
===========================================
- Coverage 94.93% 94.68% -0.26%
===========================================
Files 73 74 +1
Lines 4998 5135 +137
===========================================
+ Hits 4745 4862 +117
- Misses 253 273 +20 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
I've looked at the exchanges with the litter/soil model and I am happy that information will still get passed into my models in the format I want it in. I'm not approving as I don't feel well placed to give feedback on the overall structure/approach.
I had one specific comment which directly contradicts what I told you on teams earlier, sorry!
| self.leaf_waste_pools[cell_id].mass_cnp["carbon"] | ||
| / self.leaf_waste_pools[cell_id].mass_cnp["phosphorus"] | ||
| if self.leaf_waste_pools[cell_id].mass_cnp["phosphorus"] > 0 | ||
| else 0.0 |
There was a problem hiding this comment.
Sorry despite what I said earlier using 0.0 here doesn't really work, but obviously using inf for the divide by zero case would break the downstream calculations. Not 100% sure what the best approach is here, I guess this is a value that you should never expect, so maybe we just accept this as a weird edge case?
There was a problem hiding this comment.
Okay actually think you want to add the following between the if and the else
elif self.leaf_waste_pools[cell_id].mass_cnp["carbon"] > 0 np.inf```
There was a problem hiding this comment.
(the above might not be valid, but you get the idea)
This means that if there is a carbon flow with no associated nitrogen/phosphorus an infinite value gets returned (which then raises an error downstream). And this makes sense because biological matter always contains N and P.
But this handles the "there's no phosphorus because there's no organic matter at all" case gracefully
| efficiency = detritivore.functional_group.mechanical_efficiency | ||
| actual_consumed_mass = actually_available_mass * efficiency | ||
|
|
||
| # Avoid division by zero in nutrient fractions calculation |
There was a problem hiding this comment.
You actually take a step to avoid division errors here, is there a reason not to do this in the plant waste products case above?
dalonsoa
left a comment
There was a problem hiding this comment.
I have not gone into the detail of the code, but don't have much time today and wanted to give you some early feedback. This very much calls for a dedicated class in which to put together all the cnp related manipulations. Something like the following will also facilitate manipulation and type checks:
@dataclass
class CNP:
carbon: float
nitrogen: float
phosphorus: float
@property
def total(self) -> float:
return self.carbon + self.nitrogen + self.phosphorus
def __getitem__(self, key):
"""In case you want dictionary-style access."""
return getattr(self, key)Or, conversely, you can make it inherit from dict and add properties, as needed:
class CNP(dict):
@property
def carbon(self) -> float:
return self["carbon"]
@property
def nitrogen(self) -> float:
return self["nitrogen"]
@property
def phosphorus(self) -> float:
return self["phosphorus"]
@property
def total(self) -> float:
return sum(self.values())Given that CNP is something that seems to have some entity as a group, I think it deserves to be treated as such in the code, also facilitating its documentation.
@dalonsoa This is a really good idea and I'm annoyed that I didn't think of it! I'll make the adjustments. |
…ost of the module to use it.
jacobcook1995
left a comment
There was a problem hiding this comment.
I'm happy with how the links to soil/litter now work!
dalonsoa
left a comment
There was a problem hiding this comment.
Very nice change! It looks cleaner and more robust. I only one comment, although it is a big one.
| def add(self, carbon: float, nitrogen: float, phosphorus: float) -> None: | ||
| """Add the provided amounts of C, N, and P to the current CNP object. | ||
|
|
||
| Args: | ||
| carbon (float): The mass of carbon to add. | ||
| nitrogen (float): The mass of nitrogen to add. | ||
| phosphorus (float): The mass of phosphorus to add. | ||
| """ | ||
| self.carbon += carbon | ||
| self.nitrogen += nitrogen | ||
| self.phosphorus += phosphorus | ||
|
|
||
| def subtract(self, carbon: float, nitrogen: float, phosphorus: float) -> None: | ||
| """Subtract the provided amounts of C, N, and P from the current CNP object. | ||
|
|
||
| Args: | ||
| carbon (float): The mass of carbon to subtract. | ||
| nitrogen (float): The mass of nitrogen to subtract. | ||
| phosphorus (float): The mass of phosphorus to subtract. | ||
| """ | ||
| self.carbon -= carbon | ||
| self.nitrogen -= nitrogen | ||
| self.phosphorus -= phosphorus |
There was a problem hiding this comment.
First, why not just using negative number for subtracting and merging these two functions in a single update one?
Second, you might want to force these to be keyword argument-only functions and provide default values of 0 for all of them. Might not save typing, but will make using them more robust and less prone to error as you need to be explicit about what input is what.
| def add(self, carbon: float, nitrogen: float, phosphorus: float) -> None: | |
| """Add the provided amounts of C, N, and P to the current CNP object. | |
| Args: | |
| carbon (float): The mass of carbon to add. | |
| nitrogen (float): The mass of nitrogen to add. | |
| phosphorus (float): The mass of phosphorus to add. | |
| """ | |
| self.carbon += carbon | |
| self.nitrogen += nitrogen | |
| self.phosphorus += phosphorus | |
| def subtract(self, carbon: float, nitrogen: float, phosphorus: float) -> None: | |
| """Subtract the provided amounts of C, N, and P from the current CNP object. | |
| Args: | |
| carbon (float): The mass of carbon to subtract. | |
| nitrogen (float): The mass of nitrogen to subtract. | |
| phosphorus (float): The mass of phosphorus to subtract. | |
| """ | |
| self.carbon -= carbon | |
| self.nitrogen -= nitrogen | |
| self.phosphorus -= phosphorus | |
| def add(self, *, carbon: float = 0, nitrogen: float = 0, phosphorus: float = 0) -> None: | |
| """Add the provided amounts of C, N, and P to the current CNP object. | |
| Args: | |
| carbon (float): The mass of carbon to add. | |
| nitrogen (float): The mass of nitrogen to add. | |
| phosphorus (float): The mass of phosphorus to add. | |
| """ | |
| self.carbon += carbon | |
| self.nitrogen += nitrogen | |
| self.phosphorus += phosphorus | |
| def subtract(self, *, carbon: float = 0, nitrogen: float = 0, phosphorus: float = 0) -> None: | |
| """Subtract the provided amounts of C, N, and P from the current CNP object. | |
| Args: | |
| carbon (float): The mass of carbon to subtract. | |
| nitrogen (float): The mass of nitrogen to subtract. | |
| phosphorus (float): The mass of phosphorus to subtract. | |
| """ | |
| self.carbon -= carbon | |
| self.nitrogen -= nitrogen | |
| self.phosphorus -= phosphorus |
So you can do things like cnp.add(phosphorus=42) or cnp.substract(nitrogen=17). But, AS I said, I think it will be cleaner to have cnp.update(phosphorus=42) and cnp.update(nitrogen=-17).
Finally, does it make sense to have a any of these negative? If not, there should be an error or a warning whenever the total of any of these quantities become negative.
There was a problem hiding this comment.
That makes sense. I'll make the change.
… added verification to ensure cnp mass pools cannot be negative.
dalonsoa
left a comment
There was a problem hiding this comment.
Just a minor change, but this looks good to me!
Small code improvement in cnp._validate_non_negative Co-authored-by: Diego Alonso Álvarez <6095790+dalonsoa@users.noreply.github.com>
Description
This PR contains the transition from a purely carbon mass system to a carbon, nitrogen, phosphorus (cnp) system.
Key Changes:
Fixes # (issue)
Type of change
Key checklist
pre-commitchecks:$ pre-commit run -a$ poetry run pytestFurther checks