Support PEtab SciML problems and enable linting#482
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #482 +/- ##
==========================================
- Coverage 75.29% 75.21% -0.08%
==========================================
Files 62 64 +2
Lines 6946 6755 -191
Branches 1229 1153 -76
==========================================
- Hits 5230 5081 -149
+ Misses 1245 1218 -27
+ Partials 471 456 -15 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
dilpath
left a comment
There was a problem hiding this comment.
Thanks! Overall it would be good to try to separate as much of the SciML things into separate files or subdirectory if possible. This will help later on when we design a nicer extension interface for libpetab-python, I think. It will also help with maintenance immediately.
| return sum(p.estimate for p in self.parameters) | ||
|
|
||
|
|
||
| class Hybridization(BaseModel): |
There was a problem hiding this comment.
Move SciML-specific things to a separate file or subdirectory e.g. sciml/core.py and import them here? Just for organization; might help to keep SciML things as separate as possible. e.g. do not import petab_sciml here, rather only in the separated file.
Nevermind this if it will lead to circular imports etc.
There was a problem hiding this comment.
I've split out the sciml specific classes and introduced petab/v2/base.py to resolve circular imports. Let me know what you think.
There is still a fair amount of sciml related code left in petab/v2/core.py because the sciml fields have been added to the Problem class. We could consider an inheritance pattern in the future i.e.
class SciMLProblem(Problem):
...
IMO that would be worth considering if/when we want to support another PEtab extension that requires more fields in Problem.
There was a problem hiding this comment.
Thanks a lot, that was very helpful. And I agree, no need to fully implement now.
| config: ProblemConfig = None, | ||
| ): | ||
| from ..v2.lint import default_validation_tasks | ||
| from ..v2.lint import default_validation_tasks, sciml_validation_tasks |
There was a problem hiding this comment.
Also this, import from ..v2.sciml.lint for example, for separation.
| for mapping in problem.mappings: | ||
| # petabEntityId is not referenced in any model | ||
| for model in problem.models: | ||
| if model.has_entity_with_id(mapping.petab_id): | ||
| messages.append( | ||
| f"`{mapping.petab_id}` is used in the mapping " | ||
| "table and referenced directly in the model " | ||
| f"`{model.model_id}`." | ||
| ) |
There was a problem hiding this comment.
The spec currently says that the petabEntityId can be the same as a modelEntityId, hence the petabEntityIds can appear in the model, right? @dweindl what do you think?
The petabEntityId may be the same as the modelEntityId, but it must not be used to alias an entity that already has a valid PEtab identifier. This restriction is to avoid unnecessary complexity in the PEtab problem files.
There was a problem hiding this comment.
There is some discussion of this here PEtab-dev/PEtab#669
There was a problem hiding this comment.
I added another clause to the conditional here and below to allow the case where petabEntityId == modelEntityId. Please double check that I've understood the nuance correctly.
| # Add petab ids from mapping table if they are used for aliasing | ||
| for mapping in problem.mappings: | ||
| if mapping.model_id and mapping.model_id in parameter_ids.keys(): | ||
| if mapping.petab_id not in invalid: | ||
| parameter_ids[mapping.petab_id] = None | ||
| # An aliased model id is not a valid parameter id | ||
| if mapping.model_id and mapping.model_id in parameter_ids: | ||
| del parameter_ids[mapping.model_id] |
There was a problem hiding this comment.
As above, I think it's OK to map a model ID to a PEtab ID in the mapping table as long as it's not an alias, i.e. only in the special case where petabEntityId == modelEntityId. I think the reason for this in the spec is for support use of the mapping table for annotation, i.e. via additional user-defined annotation columns.
There was a problem hiding this comment.
As above, if we allow using the mapping table for general annotation of petab entities, which I think we do, then the current implementation would incorrectly consider, e.g., observables to be allowed in the parameter table, wouldn't it?
dilpath
left a comment
There was a problem hiding this comment.
Thanks very much! I would be happy to get @dweindl 's approval before merging, since it touches his v2 code a bit.
Currently I don't think CheckHybridizationTable is properly tested, but fine for now since it's just a placeholder before we implement all SciML checks.
| @property | ||
| def hybridizations(self) -> list[Hybridization]: | ||
| """List of hybridizations in the hybridization table(s).""" | ||
| return list( | ||
| chain.from_iterable( | ||
| ht.hybridizations for ht in self.hybridization_tables | ||
| ) | ||
| ) | ||
|
|
||
| @property | ||
| def hybridization_df(self) -> pd.DataFrame | None: | ||
| """Combined SciML hybridization tables as DataFrame.""" | ||
| return ( | ||
| HybridizationTable(hybridizations).to_df() | ||
| if (hybridizations := self.hybridizations) | ||
| else None | ||
| ) | ||
|
|
||
| @hybridization_df.setter | ||
| def hybridization_df(self, value: pd.DataFrame): | ||
| self.hybridization_tables = [HybridizationTable.from_df(value)] |
There was a problem hiding this comment.
Add some "Specific to PEtab SciML." comment to all docstrings of methods that are implemented in core PEtab?
Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com>
There was a problem hiding this comment.
I'd prefer moving all extension subpackages to .ext.{extension_id} to avoid collisions with other petab modules.
|
|
||
| import petab.v2.C as C | ||
|
|
||
| from ...v1.math.sympify import sympify_petab |
There was a problem hiding this comment.
| from ...v1.math.sympify import sympify_petab | |
| from ..math.sympify import sympify_petab |
Currently this will import the same, but it's safer in case we separate v1/v2 math processing.
| @@ -0,0 +1,225 @@ | |||
| """Base classes shared across petab.v2 to avoid circular imports.""" | |||
There was a problem hiding this comment.
Hm... .core was meant for that already.
| #: Extensions key in the YAML file | ||
| EXTENSIONS = "extensions" | ||
| #: PEtab SciML extension | ||
| SCIML = "sciml" |
There was a problem hiding this comment.
| SCIML = "sciml" | |
| EXT_ID_SCIML = "sciml" |
Feels safer. We might have extension IDs that collide with existing constants here.
| else None | ||
| ) | ||
|
|
||
| neural_networks = None |
There was a problem hiding this comment.
I am not super happy with adding all that to Problem. I see some of the changes are to invasive to fully isolate them, but for many things it should be possible.
How about having some Problem.ext.sciml: petab.v2.ext.sciml.SciMlExt, loading all those things in SciMlExt.__init__ and accessing all those entities through problem.ext.sciml.{hybridizations,neural_networks,...}? Too verbose?
This would also circumvent the circular import issues. Those extension classes would only have to imported at the function level, not module level, and we wouldn't have to move things to base.py.
| model.has_entity_with_id(mapping.petab_id) | ||
| and mapping.petab_id != mapping.model_id |
There was a problem hiding this comment.
Swap conditions - the second one is way cheaper to evaluate.
| # Add petab ids from mapping table if they are used for aliasing | ||
| for mapping in problem.mappings: | ||
| if mapping.model_id and mapping.model_id in parameter_ids.keys(): | ||
| if mapping.petab_id not in invalid: | ||
| parameter_ids[mapping.petab_id] = None | ||
| # An aliased model id is not a valid parameter id | ||
| if mapping.model_id and mapping.model_id in parameter_ids: | ||
| del parameter_ids[mapping.model_id] |
There was a problem hiding this comment.
As above, if we allow using the mapping table for general annotation of petab entities, which I think we do, then the current implementation would incorrectly consider, e.g., observables to be allowed in the parameter table, wouldn't it?
| def test_check_mapping_table(): | ||
| """Test checks related to the mapping table.""" | ||
| problem = Problem() | ||
| # PySB model from PEtab test suite |
There was a problem hiding this comment.
| # PySB model from PEtab test suite | |
| # SBML model from PEtab test suite |
and not really from the test suite?
| """Test checks related to the mapping table.""" | ||
| problem = Problem() | ||
| # PySB model from PEtab test suite | ||
| problem.model = SbmlModel.from_antimony("a.mean = 1") |
There was a problem hiding this comment.
This doesn't create a parameter with ID a.mean, but creates an sbml-distrib construct. Is this intended? If so, please describe the rationale in more detail.
If it's just about creating some model with a petab-incompatible ID, then it would have to be a PySB model. In SBML, we don't have that issue and the mapping table will only ever be used for annotation.
| "scipy" | ||
| ] | ||
| sciml = [ | ||
| "petab_sciml @ git+https://github.com/PEtab-dev/petab_sciml.git", |
There was a problem hiding this comment.
Both git/github references added here will break PyPI deployment. petab_sciml will have to be installed manually until we can reference a pypi package here.
WIP