Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Petab Importer reforge #1442

Merged
merged 42 commits into from
Sep 24, 2024
Merged

Petab Importer reforge #1442

merged 42 commits into from
Sep 24, 2024

Conversation

PaulJonasJost
Copy link
Collaborator

@PaulJonasJost PaulJonasJost commented Aug 3, 2024

CHanged the way the petab importer works. It used to be tailored to amici models and contain a lot of functionality that were only necessary for objective function creation of amici. As we now support other simulators, changed it to a Importer-Factory design.

The importer only creates the problem now. You can however supply a simulator_type to it, which then creates the corresponding factory, creating the objective. All Functions that are simulator specific are in the factory.

This should make the Code more clear in my opinion, we are getting rid of the PetabImporter depending on the AmiciObjectBuilder and make it much more versatile to support a new simulator.

Also added a basic support for any PEtab simulator. This allows every simulator who is supporting PEtab to directly use pyPESTO. This is not an efficient implementation, but it is also not meant to be one, rather lowering hurdles to use pyPESTO.

  • Check that all tests work fine and adapt to new code structure

@codecov-commenter
Copy link

codecov-commenter commented Aug 3, 2024

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

Attention: Patch coverage is 88.59649% with 52 lines in your changes missing coverage. Please review.

Project coverage is 82.87%. Comparing base (2e57687) to head (809a8e0).

Files with missing lines Patch % Lines
pypesto/petab/objective_creator.py 88.88% 29 Missing ⚠️
pypesto/petab/importer.py 69.23% 12 Missing ⚠️
pypesto/objective/petab.py 90.47% 4 Missing ⚠️
pypesto/petab/util.py 94.44% 3 Missing ⚠️
.../objective/roadrunner/petab_importer_roadrunner.py 87.50% 2 Missing ⚠️
pypesto/objective/roadrunner/road_runner.py 87.50% 2 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1442      +/-   ##
===========================================
- Coverage    83.44%   82.87%   -0.57%     
===========================================
  Files          160      163       +3     
  Lines        13502    13760     +258     
===========================================
+ Hits         11267    11404     +137     
- Misses        2235     2356     +121     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@dweindl dweindl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Some first feedback:

Overall, it makes sense to split up the PEtab-related functionality.

I am not sure how much demand there really is for the petab.Simulator support, but fine to have it. (It should be relatively easy to include a new simulator, but for really using it during parameter estimation, one would probably want a more efficient implementation).

I am not so happy with the naming yet, see specific comments.

doc/example/getting_started.ipynb Outdated Show resolved Hide resolved
pypesto/petab/factory.py Outdated Show resolved Hide resolved
pypesto/petab/factory.py Outdated Show resolved Hide resolved
pypesto/petab/factory.py Outdated Show resolved Hide resolved
pypesto/petab/factory.py Outdated Show resolved Hide resolved
)
return mapping

def create_objective(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think anything besides this function could/should be private.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could if we view the sole purpose of the factory as the objective generation. If we view the other objects like datas and/or model part of its tasks as well, they should not be private. The petabImporter had them previously also as not private. If we want them to be private, there would be no need to have a lot of arguments in them allowing to separately call them as they anways were not meant to be used that way?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will have to have a closer look what might be useful elsewhere. Not everything that was public before should have been public before. If the other functions are to be used separately, then they should not be in this class, or this class should not be called ObjectiveFactory or similar. A XFactory has (or should have) a single purpose, which is to produce an instance of X.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, in this case we can make them private any day (would also introduce potential breaking changes?) i am also very much in favor of removing some of these functions at least in the future as i dont really see a point in such light wrappers (see e.g. rdatas_to_measurement_df). For now i moved everything away from the importer, but happy to also remove not needed functions (i already removed check gradients match).

pypesto/petab/importer.py Show resolved Hide resolved
pypesto/petab/importer.py Outdated Show resolved Hide resolved
pypesto/petab/importer.py Outdated Show resolved Hide resolved
rdatas, model, measurement_df
"""See :meth:`AmiciFactory.rdatas_to_measurement_df`."""
raise NotImplementedError(
"This function has been moved to `AmiciFactory`."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this function really belongs to AmiciFactory. Also this would be a breaking change without deprecation warning, which would require pypesto 1.0 (there are a couple more cases like that).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'll be honest, i dont think they even belong to pyPESTO, as it is a really light wrapper around it. Can create a deprecation warning as well.

Why would it require a 1.0? we could go with a 0.6.0 as we have done before on breaking changes?

@PaulJonasJost PaulJonasJost marked this pull request as ready for review August 9, 2024 13:35
self.simulator.set_parameters(x_unscaled)
except AttributeError:
self.simulator = self.simulator.__class__(
petab_problem=self.petab_problem

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PaulJonasJost The issue with the reinitialization here, is that any parameters set before using custom constructors will be lost. I'm not sure how bad that is, but at the very lest, the working directory should be passed along as well.

just something to think about

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is not feasible to accurately reinitialise, thus i removed the tryExcept and we expect the Simulator to have such a functionality.

@PaulJonasJost PaulJonasJost self-assigned this Aug 14, 2024
Copy link
Member

@dilpath dilpath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks nice, thanks 🙂

doc/example/petab_import.ipynb Outdated Show resolved Hide resolved
pypesto/petab/factory.py Outdated Show resolved Hide resolved
pypesto/petab/importer.py Outdated Show resolved Hide resolved
Comment on lines +169 to +170
self.simulator_type = simulator_type
self.simulator = simulator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the importer only needs to use this in create_objective, why not remove these attributes and implement them as arguments of create_objective instead?

e.g. if I want to compare the libroadrunner and AMICI objectives, why do I need two importer objects? I could use the same importer like importer.create_objective_creator(..., simulator_id=AMICI) and importer.create_objective_creator(..., simulator_id=ROADRUNNER)

No strong opinion on this though, fine as is

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1
Think this makes sense. Also not strong opinion.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having this would make it imo much easier to

  1. init the importer -> 2. importer.create_problem()
    without having to pass these arguments constantly to create_problem. Could add it as additional possibility to create_objective such that people can do either? Argument in create_objective would then take precedence

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the cleanest implementation would be that default behavior (AMICI simulator) is usable as you said like

importer = PetabImporter(petab_problem)
problem = importer.create_problem()

but custom behavior is like

importer = PetabImporter(petab_problem)
obj = importer.create_objective(AMICI)
obj.amici_solver.setAbsoluteTolerance(1e-8)
problem = importer.create_problem(obj)

Since it's quite common for me to customize things like simulator tolerances, passing obj to create_problem is no change for me.

Just to explain my (weak) preference.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

passing the objective to problem is still possible. And the other behavior would look like (+- details)

importer = PetabImporter(petab_problem, AMICI)
obj = importer.create_objective()
obj.amici_solver.setAbsoluteTolerance(1e-8)
problem = importer.create_problem(obj)

I think going with allowing both would be best solution to be able to adjust to individual preferences?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also prefer to only provide users with one way of doing things 😅 So my preference would be to only permit specification via __init__ xor create_objective, and between those I prefer create_objective.

Since you have done the work, I'll defer to whatever you decide.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By now, PetabImporter doesn't have much functionality or internal state left. Maybe we could directly go for something like

importer = AmiciPetabImporter(petab_problem, ...)
problem = importer.create_problem()
...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By now, PetabImporter doesn't have much functionality or internal state left. Maybe we could directly go for something like

importer = AmiciPetabImporter(petab_problem, ...)
problem = importer.create_problem()
...

I think currently the main advantage is that we can easily switch by just changing an argument in the Importer to switch simulators if needed/wanted which we would lose with the above. additionally the create_problem function is for all of them kind of the same which just in my mind creates this natural hierarchy of "Importer -> Inner thing (=Objective creator)"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

easily switch by just changing an argument

In the other case, it's just changing the class name.
It has the advantage of better static code analysis support and avoids having PetabImporter.__init__ with the superset of arguments for all simulators.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean like this?

class AmiciPetabImporter(PetabImporter):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    def create_objective(self, *args, **kwargs):
        super().create_objective(simulator_type=AMICI, *args, **kwargs)

Fine for me. Having the argument in the PetabImporter.create_objective makes sense to me since that's where it's used, but the other helpful alternatives (like PetabImporter(simulator_type=...) or AmiciPetabImporter) also make sense 👍

Copy link
Contributor

@Doresic Doresic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. Good changes.

Comment on lines +169 to +170
self.simulator_type = simulator_type
self.simulator = simulator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1
Think this makes sense. Also not strong opinion.

pypesto/petab/importer.py Outdated Show resolved Hide resolved
doc/example/petab_import.ipynb Outdated Show resolved Hide resolved
@PaulJonasJost
Copy link
Collaborator Author

As no one had a strong opinion, I would put it up to vote how to organize the code:

  1. Keep the PetabImporter and the objective creator separate but don't enforce setting the simulator_type in the init of the importer, rather also allowing it to be put in the create_objective_creator as well. (Vote with 🎉)
    Mentioned Up-/Downsides:
    - (+) Clear separation of tasks, one creates the objective and anything auxiliary, the other only has to create the problem from the petab problem
    - (-) More overloading of arguments in the PetabImporter
    - (+) Easy switching of underlying objective and/or potential to easily create two different problems to compare
  2. For each simulator type create its own SimulatorPetabImporter (Vote with 🚀 )
    Mentioned Up-/Downsides:
    - (+) Loose some arguments and no overload of arguments
    - (+/-) Clear functionality tailored to exactly one simulator type
    - (-) Strong break with current codepipelines. Any current code having previously used the importer will have to be revised

I would keep the poll open till end of tomorrow :)

PS: sorry for the emojis of vote, these were the two most neutral ones I saw one can react with under a comment.

@PaulJonasJost PaulJonasJost merged commit 0264a1e into develop Sep 24, 2024
18 checks passed
@PaulJonasJost PaulJonasJost deleted the petab_simulator_support branch September 24, 2024 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Decouple PEtab and AMICI
7 participants