Skip to content

Commit

Permalink
Improvements for 0.9.0-beta.3
Browse files Browse the repository at this point in the history
Update docs and add extra functionality to the API
  • Loading branch information
pablormier committed Aug 18, 2021
1 parent f53de15 commit bb5be60
Show file tree
Hide file tree
Showing 14 changed files with 356 additions and 190 deletions.
190 changes: 130 additions & 60 deletions README.md

Large diffs are not rendered by default.

262 changes: 174 additions & 88 deletions docs/index.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion examples/exact_fastcore_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import numpy as np

# Load a model
m = miom.mio.load_gem('@homo_sapiens_human1_fcm.miom')
m = miom.load_gem('@homo_sapiens_human1_fcm.miom')
print("Reactions in the consistent iHuman GEM:", m.num_reactions)
core_rxn = m.find_reactions_from_pathway("Cholesterol metabolism")
print("Num. of core reactions:", sum(core_rxn))
Expand Down
2 changes: 1 addition & 1 deletion examples/fba_example.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import miom

# Load a model
m = miom.mio.load_gem('https://github.com/pablormier/miom-gems/raw/main/gems/mus_musculus_iMM1865.miom')
m = miom.load_gem('@mus_musculus_iMM1865.miom')

target = 'BIOMASS_reaction'
opt_flux = (miom.load(network=m, solver=miom.Solvers.GLPK)
Expand Down
12 changes: 5 additions & 7 deletions examples/imat_example.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from miom import miom, Solvers
from miom.mio import load_gem
from miom.miom import Comparator, ExtractionMode
import miom

# Example implementation of iMAT using MIOM.
# Note that this implementation supports also custom weights for the reactions
Expand All @@ -9,7 +7,7 @@
# function is exactly the same as the original iMAT.

# Use the iHuman-GEM model
m = load_gem('https://github.com/pablormier/miom-gems/raw/main/gems/homo_sapiens_human1.miom')
m = miom.load_gem('@homo_sapiens_human1.miom')

# Add all the reactions from the Cholesterol pathway to the highly expressed set
RH = m.find_reactions_from_pathway("Cholesterol metabolism")
Expand All @@ -18,14 +16,14 @@
w = RH + RL
print("RH:", sum(RH), "RL:", sum(abs(RL)))

m = (miom(m, solver=Solvers.COIN_OR_CBC)
m = (miom(m, solver=miom.Solvers.COIN_OR_CBC)
.setup(int_tol=1e-8, opt_tol=0.01, verbosity=1)
.steady_state()
.subset_selection(w)
.solve(max_seconds=30)
.select_subnetwork(
mode=ExtractionMode.ABSOLUTE_FLUX_VALUE,
comparator=Comparator.GREATER_OR_EQUAL,
mode=miom.ExtractionMode.ABSOLUTE_FLUX_VALUE,
comparator=miom.Comparator.GREATER_OR_EQUAL,
value=1e-8
)
.network)
Expand Down
8 changes: 4 additions & 4 deletions examples/introduction_example.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from miom import miom, Solvers
from miom.mio import load_gem
import miom

# Load a genome-scale metabolic network. You can load SMBL or Matlab metabolic networks
# as well using the same method, but it requires to have the cobratoolbox python library
# installed.
network = load_gem("https://github.com/pablormier/miom-gems/raw/main/gems/mus_musculus_iMM1865.miom")
network = miom.load_gem("@mus_musculus_iMM1865.miom")
target_rxn = "BIOMASS_reaction"
# Create the optimization problem with miom and solve
model = (miom(network)
model = (miom
.load(network)
.steady_state()
.set_rxn_objective(target_rxn)
.solve(verbosity=1))
Expand Down
2 changes: 1 addition & 1 deletion examples/sparse_fba_example.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import miom

# Load a model
m = miom.mio.load_gem('@mus_musculus_iMM1865.miom')
m = miom.load_gem('@mus_musculus_iMM1865.miom')
print("Num. of reactions in the network:", m.num_reactions)

# Solve with Gurobi, CPLEX or CBC (other MIP solvers struggle with mediudm/large networks)
Expand Down
6 changes: 4 additions & 2 deletions miom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
ExtractionMode
)

__all__ = ["load", "Solvers", "Comparator", "ExtractionMode"]
__version__ = "0.9.0-beta.2"
from miom.mio import load_gem

__all__ = ["load", "load_gem", "Solvers", "Comparator", "ExtractionMode"]
__version__ = "0.9.0-beta.3"
3 changes: 2 additions & 1 deletion miom/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def convert_list_gems(input_files, output=None):
m = miom.mio.load_gem(input_file)
print(f"Loaded network with {m.num_reactions} reactions (in-memory size: {m.object_size:.2f} MB)")
# Concatenate folder and output file
print(os.path.abspath(output))
if output is not None:
if isinstance(output, list) and len(output) == len(input_files):
output_file = output[i]
Expand All @@ -29,7 +30,7 @@ def convert_list_gems(input_files, output=None):
os.makedirs(abspath)
except OSError as e:
print(f"Cannot create {abspath} folder: {e}")
elif os.path.isdir(output):
elif os.path.isdir(output) or os.path.isdir(os.path.abspath(output)):
output_file = os.path.join(output, output_file)
else:
raise ValueError("The provided output is not a folder or a list of destinations for each file")
Expand Down
9 changes: 5 additions & 4 deletions miom/mio.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,9 @@ def load_gem(model_or_path):
if ext == '.miom' or ext == '.xz' or ext == '.npz':
return _load_compressed_model(file)
else:
return _cobra_to_miom(_read_cobra_model(file))
return cobra_to_miom(_read_cobra_model(file))
else:
return _cobra_to_miom(model_or_path)
return cobra_to_miom(model_or_path)


def _read_cobra_model(filepath):
Expand Down Expand Up @@ -191,7 +191,7 @@ def export_gem(miom_network, path_to_exported_file):
f_out.write(compressed)


def _cobra_to_miom(model):
def cobra_to_miom(model):
try:
from cobra.util.array import create_stoichiometric_matrix
except ImportError as e:
Expand Down Expand Up @@ -219,11 +219,12 @@ def _cobra_to_miom(model):
subsystems.append(rxn.subsystem.tolist())
else:
subsystems.append(rxn.subsystem)
rxn_data = [(rxn.id, rxn.lower_bound, rxn.upper_bound, subsystem, rxn.gene_reaction_rule)
rxn_data = [(rxn.id, rxn.name, rxn.lower_bound, rxn.upper_bound, subsystem, rxn.gene_reaction_rule)
for rxn, subsystem in zip(model.reactions, subsystems)]
met_data = [(met.id, met.name, met.formula) for met in model.metabolites]
R = np.array(rxn_data, dtype=[
('id', 'object'),
('name', 'object'),
('lb', 'float'),
('ub', 'float'),
('subsystem', 'object'),
Expand Down
43 changes: 26 additions & 17 deletions miom/miom.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,10 @@ def indicator_values(self):
return None


def _partial(fn):
def _composable(fn):
"""Annotation for methods that return the instance itself to enable chaining.
If a method `my_method` is annotated with @_partial, a method called `_my_method`
If a method `my_method` is annotated with @_composable, a method called `_my_method`
is expected to be provided by a subclass. Parent method `my_method` is called first
and the result is passed to the child method `_my_method`.
Expand All @@ -326,7 +326,7 @@ def wrapper(self, *args, **kwargs):
# Find subclass implementation
fname = '_' + fn.__name__
if not hasattr(self, fname):
raise ValueError(f'Method "{fn.__name__}()" is marked as @_partial '
raise ValueError(f'Method "{fn.__name__}()" is marked as @_composable '
f'but the expected implementation "{fname}()" was not provided '
f'by {self.__class__.__name__}')
func = getattr(self, fname)
Expand Down Expand Up @@ -403,7 +403,7 @@ def initialize_problem(self):
def get_solver_status(self):
pass

@_partial
@_composable
def setup(self, **kwargs):
"""Provide the options for the solver.
Expand Down Expand Up @@ -444,7 +444,7 @@ def setup(self, **kwargs):
self._options["_min_eps"] = np.sqrt(2*self._options["int_tol"])
return self._options

@_partial
@_composable
def steady_state(self):
"""Add the required constraints for finding steady-state fluxes
Expand All @@ -456,7 +456,7 @@ def steady_state(self):
"""
pass

@_partial
@_composable
def keep(self, reactions):
"""Force the inclusion of a list of reactions in the solution.
Expand Down Expand Up @@ -501,7 +501,7 @@ def keep(self, reactions):
if r.index in valid_rxn_idx]
return dict(idxs=idxs, valid_rxn_idx=valid_rxn_idx)

@_partial
@_composable
def subset_selection(self, rxn_weights, direction='max', eps=1e-2):
"""Transform the current model into a subset selection problem.
Expand Down Expand Up @@ -561,7 +561,7 @@ def subset_selection(self, rxn_weights, direction='max', eps=1e-2):
warnings.warn("Indicator variables were already assigned")
return False

@_partial
@_composable
def set_flux_bounds(self, rxn_id, min_flux=None, max_flux=None):
"""Change the flux bounds of a reaction.
Expand All @@ -576,7 +576,7 @@ def set_flux_bounds(self, rxn_id, min_flux=None, max_flux=None):
i, _ = self.network.find_reaction(rxn_id)
return i

@_partial
@_composable
def add_constraints(self, constraints):
"""Add a list of constraint to the model
Expand All @@ -591,7 +591,7 @@ def add_constraints(self, constraints):
self.add_constraint(c)
return len(constraints) > 0

@_partial
@_composable
def add_constraint(self, constraint):
"""Add a specific constraint to the model.
The constraint should use existing variables already included in the model.
Expand All @@ -601,7 +601,7 @@ def add_constraint(self, constraint):
"""
pass

@_partial
@_composable
def set_objective(self, cost_vector, variables, direction='max'):
"""Set the optmization objective of the model.
Expand Down Expand Up @@ -660,7 +660,7 @@ def set_fluxes_for(self, reactions, tolerance=1e-6):
self.add_constraint(self.variables.fluxvars[i] <= ub)
return self

@_partial
@_composable
def reset(self):
"""Resets the original problem (removes all modifications)
Expand Down Expand Up @@ -730,7 +730,7 @@ def obtain_subnetwork(
S_sub = S_sub[act_met, :]
return MiomNetwork(S_sub, R_sub, M_sub)

@_partial
@_composable
def select_subnetwork(
self,
mode=ExtractionMode.ABSOLUTE_FLUX_VALUE,
Expand Down Expand Up @@ -792,7 +792,7 @@ def get_fluxes(self, reactions=None):
else:
raise ValueError("reactions should be an iterable of strings or a single string")

@_partial
@_composable
def solve(self, verbosity=None, max_seconds=None):
"""Solve the current model and assign the values to the variables of the model.
Expand All @@ -803,7 +803,7 @@ def solve(self, verbosity=None, max_seconds=None):
"""
self._last_start_time = perf_counter()

@_partial
@_composable
def copy(self):
pass

Expand Down Expand Up @@ -912,7 +912,13 @@ def _select_subnetwork(self, **kwargs):
return PicosModel(previous_step_model=self, miom_network=m)

def _copy(self, **kwargs):
return PicosModel(previous_step_model=self)
m = PicosModel(previous_step_model=self)
# Create a copy of the internal problem
m.problem = self.problem.copy()
# TODO: Assign the new variables
m.variables._indicator_vars = None
m.variables._flux_vars = None
raise NotImplementedError("Will be added in future versions")

def get_solver_status(self):
return {
Expand Down Expand Up @@ -1051,7 +1057,10 @@ def _select_subnetwork(self, **kwargs):
return PythonMipModel(previous_step_model=self, miom_network=kwargs["_parent_result"])

def _copy(self, **kwargs):
return PythonMipModel(previous_step_model=self)
m = PythonMipModel(previous_step_model=self)
m.problem = self.problem.copy()
raise NotImplementedError("Will be added in future versions")


def get_solver_status(self):
#solver_status['elapsed_seconds'] = self.problem.search_progress_log.log[-1:][0][0]
Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ markdown_extensions:
- pymdownx.highlight
- pymdownx.superfences
- pymdownx.details
- pymdownx.tabbed
- pymdownx.inlinehilite
- pymdownx.arithmatex:
generic: true
extra:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "miom"
version = "0.9.0-beta.2"
version = "0.9.0-beta.3"
description = "Mixed Integer Optimization for Metabolism"
authors = ["Pablo R. Mier <[email protected]>"]
keywords = ["optimization", "LP", "MIP", "metabolism", "metabolic-networks"]
Expand Down
3 changes: 0 additions & 3 deletions tests/test_miom.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,5 @@ def test_keep_rxn(model):
)
assert abs(V[i]) > 1e-8

def test_copy(model):
c = model.copy()
assert model != c and isinstance(c, BaseModel)


0 comments on commit bb5be60

Please sign in to comment.