-
Notifications
You must be signed in to change notification settings - Fork 231
C++-based CHRR implementation #1453
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
Open
ripaul
wants to merge
8
commits into
opencobra:devel
Choose a base branch
from
ripaul:hopsy-backend
base: devel
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
cfb18b4
hopsy backend
ripaul 729b21c
adding multichain support to all samplers
ripaul e9aeead
misc
ripaul 666ac2d
make everything (hopefully) CI conform
ripaul d9c0e48
change back to no additional chain kwarg, adapt docstrings
ripaul 8e290dc
update docs and refs, change name to 'chrr'
ripaul c179027
rename optional install to chrr, adjust hopsy import
ripaul c40194e
add 'auto' method option
ripaul File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,6 +84,8 @@ cobra = | |
| [options.extras_require] | ||
| array = | ||
| scipy | ||
| chrr = | ||
| hopsy | ||
| development = | ||
| black | ||
| bumpversion | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,11 @@ | ||
| from .hr_sampler import HRSampler, shared_np_array | ||
| from .achr import ACHRSampler | ||
| from .core import step | ||
|
|
||
| from .hopsy import hopsy_is_available | ||
|
|
||
| if hopsy_is_available: | ||
| from .hopsy import HopsySampler | ||
|
|
||
| from .optgp import OptGPSampler | ||
| from .sampling import sample |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| """Provide hopsy samplers.""" | ||
|
|
||
| from typing import TYPE_CHECKING, Optional | ||
|
|
||
|
|
||
| if TYPE_CHECKING: | ||
| from cobra import Model | ||
|
|
||
| from ..core.configuration import Configuration | ||
| from .hr_sampler import HRSampler | ||
|
|
||
|
|
||
| __all__ = ( | ||
| "HopsySampler", | ||
| "hopsy_is_available", | ||
| ) | ||
|
|
||
| configuration = Configuration() | ||
|
|
||
| try: | ||
| import hopsy | ||
| import numpy as np | ||
| import pandas as pd | ||
|
|
||
| hopsy_is_available = True | ||
|
|
||
| # for the time being while the updated hopsy version isn't released | ||
| def back_transform(problem, samples): | ||
| """ | ||
| Transform samples from the sampling space to the original parameter space. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| problem : hopsy.Problem | ||
| A hopsy.Problem with populated `problem.transformation` and `problem.shift` of | ||
| shape `(m, d)` and `(d, )`, respectively. If not populated, the identity | ||
| operation is performed. | ||
| samples : np.array | ||
| Parameter samples of shape `(..., d)` which are to be transformed. | ||
|
|
||
| Returns | ||
| ------- | ||
| np.array | ||
| Transformed samples of shape `(..., m)` | ||
| """ | ||
| S, h = problem.transformation, problem.shift | ||
| transformed = samples @ S.T if S is not None else samples | ||
| shifted = transformed + h if h is not None else transformed | ||
| return shifted | ||
|
|
||
| class HopsySampler(HRSampler): | ||
| """ | ||
| Generic sampling wrapper for hopsy samplers. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| model : cobra.Model | ||
| The cobra model from which to generate samples. | ||
| sampler : hopsy.Proposal, optional | ||
| The hopsy sampler for sampling the polytope | ||
| (default hopsy.UniformCoordinateHitAndRunProposal). | ||
| processes: int, optional | ||
| The number of processes used during sampling | ||
| (default cobra.Configuration.processes). | ||
| thinning : int, optional | ||
| The thinning factor of the generated sampling chain. A thinning of | ||
| 10 means samples are returned every 10 steps (default 100). | ||
| nproj : int > 0, optional | ||
| How often to reproject the sampling point into the feasibility | ||
| space. Avoids numerical issues at the cost of lower sampling. If | ||
| you observe many equality constraint violations with | ||
| `sampler.validate` you should lower this number (default None). | ||
| rounding : bool, optional | ||
| Whether to precondition the sampling problem by applying an offline rounding | ||
| transformation. | ||
| seed : int > 0, optional | ||
| Sets the random number seed. Initialized to the current time stamp | ||
| if None (default None). | ||
|
|
||
| Attributes | ||
| ---------- | ||
| n_samples : int | ||
| The total number of samples that have been generated by this | ||
| sampler instance. | ||
| problem : typing.NamedTuple | ||
| A NamedTuple whose attributes define the entire sampling problem in | ||
| matrix form. | ||
| fwd_idx : numpy.array | ||
| A numpy array having one entry for each reaction in the model, | ||
| containing the index of the respective forward variable. | ||
| rev_idx : numpy.array | ||
| A numpy array having one entry for each reaction in the model, | ||
| containing the index of the respective reverse variable. | ||
| sampler : | ||
| The hopsy sampler type. | ||
| mcs : list | ||
| List of length `processes` of hopsy.MarkovChain objects. | ||
| rngs : list | ||
| List of length `processes` of hopsy.RandomNumberGenerator objects. | ||
|
|
||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| model: "Model", | ||
| sampler: hopsy.Proposal = hopsy.UniformCoordinateHitAndRunProposal, | ||
| thinning: int = 100, | ||
| processes: Optional[int] = None, | ||
| nproj: Optional[int] = None, | ||
| seed: Optional[int] = None, | ||
| rounding: bool = True, | ||
| **kwargs, | ||
| ) -> None: | ||
| """Initialize a new HopsySampler.""" | ||
| super().__init__(model, thinning, nproj=nproj, seed=seed, **kwargs) | ||
|
|
||
| if self.problem.inequalities.shape[0] > 0: | ||
| A = self.problem.inequalities | ||
| b = self.problem.bounds | ||
| else: | ||
| # empty dummy constraint to set problem dimension | ||
| A = np.ones((0, self.problem.equalities.shape[1])) | ||
| b = np.ones(0) | ||
|
|
||
| problem = hopsy.Problem(A, b) | ||
| problem = hopsy.add_box_constraints( | ||
| problem, | ||
| self.problem.variable_bounds[0], | ||
| self.problem.variable_bounds[1], | ||
| simplify=False, | ||
| ) | ||
| problem = hopsy.add_equality_constraints( | ||
| problem, self.problem.equalities, self.problem.b | ||
| ) | ||
| problem = hopsy.round(problem, simplify=False) if rounding else problem | ||
|
|
||
| self.sampler = sampler | ||
|
|
||
| # batched transformation to better exploit vectorization | ||
| self.transformation = problem.transformation.copy() | ||
| self.shift = problem.shift.copy() | ||
|
|
||
| problem.transformation = None | ||
| problem.shift = None | ||
|
|
||
| self._problem = problem | ||
|
|
||
| if processes is None: | ||
| self.processes = configuration.processes | ||
| else: | ||
| self.processes = processes | ||
|
|
||
| self.mcs = None | ||
| self.rngs = None | ||
|
|
||
| self.mcs = [ | ||
| hopsy.MarkovChain(self._problem, self.sampler) for i in range(processes) | ||
| ] | ||
| self.rngs = [ | ||
| hopsy.RandomNumberGenerator(self._seed, i) for i in range(processes) | ||
| ] | ||
|
|
||
| def sample(self, n: int, fluxes: bool = True) -> pd.DataFrame: | ||
| """Generate a set of samples by calling hopsy. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| n : int | ||
| The minimum number of samples that are generated at once. | ||
| fluxes : bool, optional | ||
| Whether to return fluxes or the internal solver variables. If | ||
| set to False, will return a variable for each forward and | ||
| backward flux as well as all additional variables you might | ||
| have defined in the model (default True). | ||
|
|
||
| Returns | ||
| ------- | ||
| pandas.DataFrame | ||
| Returns a pandas DataFrame with `n` rows, each containing a | ||
| flux sample. | ||
| """ | ||
| _, samples = hopsy.sample( | ||
| self.mcs, | ||
| self.rngs, | ||
| n_samples=n // self.processes, | ||
| thinning=self.thinning, | ||
| n_procs=self.processes, | ||
| ) | ||
|
|
||
| # batched transformation to better exploit vectorization | ||
| self._problem.transformation = self.transformation | ||
| self._problem.shift = self.shift | ||
|
|
||
| samples = back_transform(self._problem, samples) | ||
|
|
||
| self._problem.transformation = None | ||
| self._problem.shift = None | ||
|
|
||
| samples = samples.reshape(-1, samples.shape[-1]) # flatten chain dim | ||
|
|
||
| if fluxes: | ||
| names = [r.id for r in self.model.reactions] | ||
|
|
||
| return pd.DataFrame( | ||
| samples[:, self.fwd_idx] - samples[:, self.rev_idx], | ||
| columns=names, | ||
| ) | ||
| else: | ||
| names = [v.name for v in self.model.variables] | ||
|
|
||
| return pd.DataFrame(samples, columns=names) | ||
|
|
||
| except ModuleNotFoundError: | ||
| hopsy_is_available = False |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the default should work on any installation. Maybe add an "auto" option that used hopsy if installed and optgp otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an "auto" option and set it to be the default. Is that how you had in mind? Or would you still suggest to put "optgp" as default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is exactly what I meant, thanks!