def _exec_pydesign(func_name, compiled_code): """ :param func_name: function name :param compiled_code: python code compiled with `compile` :return: function """ global_namespace = dict(__name__='__gsmodutils_design_loader__', ) # Get the function from the file try: exec_(compiled_code, global_namespace) except Exception as ex: print(compiled_code) raise DesignError("Code execution error in file for {} {}".format( func_name, ex)) if func_name not in global_namespace: raise DesignError( "function {} not found in python file".format(func_name)) func = global_namespace[func_name] if not hasattr(func, '__call__'): raise DesignError( "Design function must be callable, got type {}".format( type(func))) return func
def from_pydesign(cls, project, did, func_name, compiled_code): """ Load a pydesign function as a proper design """ func = cls._exec_pydesign(func_name, compiled_code) # Set default variables func.name = getattr(func, 'name', "") func.parent = getattr(func, 'parent', None) func.description = getattr(func, 'description', func.__doc__) func.base_model = getattr(func, 'base_model', None) func.conditions = getattr(func, 'conditions', None) # Will throw error if parent is not a valid design parent = None if func.parent is not None: try: parent = project.get_design(func.parent) except DesignError: raise DesignOrphanError( "Design parent appears to be invalid {} --> {}".format( func.parent, did)) except DesignNotFoundError: raise DesignOrphanError( "Design parent not found {} --> {}".format( func.parent, did)) try: base_model = project.load_model(func.base_model) except IOError: raise DesignError( "Base model {} does not appear to be valid".format( func.base_model)) try: tmodel = func(base_model.copy(), project) except Exception as ex: raise DesignError("Error executing design function {} {}".format( did, ex)) if not isinstance(tmodel, cobra.Model): raise DesignError("Design does not return a cobra Model instance") # We use the loaded model diff as the remaining patameters for the design # This is the only reason the model has to be loaded here diff = model_diff(base_model, tmodel) this = cls(did=did, name=func.name, description=func.description, project=project, parent=parent, base_model=func.base_model, conditions=func.conditions, is_pydesign=True, design_func=func, **diff) return this
def check_parents(self, p_stack=None): """ Tests to see if their is a loop in parental inheritance""" if p_stack is None: p_stack = [] if self.id in p_stack: raise DesignError( 'Error in design {id} - has a cyclical reference') p_stack.append(self.id) if self.parent is None: return True if not isinstance(self.parent, StrainDesign): raise TypeError('invalid parent design') return self.parent.check_parents(p_stack)
def get_design(self, design): """ Get the StrainDesign object (not resulting model) of a design :param design: design identifier :return: """ if design not in self.list_designs: raise DesignNotFoundError( "Design of name {} not found in project".format(design)) if design in self._json_designs: des_path = os.path.join(self._project_path, self.config.design_dir, '{}.json'.format(design)) self._designs_store[design] = StrainDesign.from_json( design, des_path, self) else: try: self._designs_store[design] = self._load_py_design(design) except Exception as exp: raise DesignError(str(exp)) return self._designs_store[design]
def load(self): """ Returns a cobra model containing the parent model with the design applied :return: """ if self.project is None: raise DesignError("No specified project or model.") model = self.project.load_model(self.base_model) # Add reactions/genes from design to existing model self.add_to_model(model) if self.conditions is not None: try: self.project.load_conditions(self.conditions, model=model) except KeyError: logger.warning( 'Cannot find conditions id {} in project specified by design' .format(self.conditions)) return model
def save_design(self, model, did, name, description='', conditions=None, base_model=None, parent=None, overwrite=False): """ Creates a design from a diff of model_a and model_b id should be a string with no spaces (conversion handled) Returns the saved design diff :param model: cobrapy model :param did: design identifier :param name: name of the design :param description: text description of what it does :param conditions: conditions that should be applied for the design :param base_model: Model that the design should be derived from - specified model included in project :param parent: string for parent design that this design is a diff from :param overwrite: overwrite and existing design (only applies if the id is already in use) """ # Test, infeasible designs should not be added status = model.solver.optimize() if status == 'infeasible': raise Infeasible('Could not find valid solution') if parent is not None: if isinstance(parent, string_types): parent = self.get_design(parent) elif not isinstance( parent, StrainDesign) or parent.id not in self.list_designs: raise DesignError( 'Parent relate a valid project strain design') did = str(did).replace(' ', '_') design_save_path = os.path.join(self.design_path, '{}.json'.format(did)) if os.path.exists(design_save_path) and not overwrite: raise IOError('File {} exists'.format(design_save_path)) if base_model is None and parent is None: base_model = self.config.default_model elif parent is not None: # If a parent design is specified this model is loaded first base_model = parent.base_model elif base_model is not None and base_model not in self.config.models: raise KeyError('Base model not found should be one of {}'.format( " ".join(self.config.models))) if parent is None: lmodel = self.load_model(base_model) else: lmodel = parent.load() if conditions is not None: self.load_conditions(conditions, lmodel) # Find all the differences between the models diff = model_diff(lmodel, model) if parent is not None: parent = parent.id diff['description'] = description diff['id'] = did diff['name'] = name diff['conditions'] = conditions diff['base_model'] = base_model diff['parent'] = parent des = StrainDesign.from_dict(did, diff, self) des.to_json(design_save_path, overwrite=overwrite) return des
def add_to_model(self, model, copy=False, add_missing=True): """ Add this design to a given cobra model :param model: :param copy: :param add_missing: add missing metabolites to the model :return: """ if not isinstance(model, cobra.Model): raise TypeError('Expected cobra model') mdl = model if copy: mdl = model.copy() # Load parent design first if self.parent is not None: self.parent.add_to_model(mdl) mdl.design = self if self.is_pydesign: try: mdl = self.design_func(mdl, self.project) except Exception as ex: raise DesignError("Function execution error {}".format(ex)) return mdl # Add new or changed metabolites to model for metabolite in self.metabolites: # create new metabolite object if its not in the model already if metabolite['id'] in mdl.metabolites: metab = mdl.metabolites.get_by_id(metabolite['id']) else: metab = cobra.Metabolite() # Doesn't check any of these properties for differences, just update them metab.id = metabolite['id'] metab.name = metabolite['name'] metab.charge = metabolite['charge'] metab.formula = metabolite['formula'] metab.notes = metabolite['notes'] metab.annotation = metabolite['annotation'] metab.compartment = metabolite['compartment'] if metab.id not in mdl.metabolites: mdl.add_metabolites([metab]) # Add new or changed reactions to model for rct in self.reactions: if rct['id'] in mdl.reactions: reaction = mdl.reactions.get_by_id(rct['id']) reaction.remove_from_model() reaction = cobra.Reaction() reaction.id = rct['id'] reaction.name = rct['name'] reaction.lower_bound = rct['lower_bound'] reaction.upper_bound = rct['upper_bound'] reaction.gene_reaction_rule = rct['gene_reaction_rule'] reaction.subsystem = rct['subsystem'] reaction.name = rct['name'] mdl.add_reactions([reaction]) reaction = mdl.reactions.get_by_id(reaction.id) metabolites = dict([(str(x), v) for x, v in rct['metabolites'].items()]) if add_missing: for mid in metabolites: try: mdl.metabolites.get_by_id(mid) except KeyError: metab = cobra.Metabolite(id=mid) mdl.add_metabolites(metab) reaction.add_metabolites(metabolites) reaction.objective_coefficient = rct['objective_coefficient'] # delete removed metabolites/reactions for rtid in self.removed_reactions: try: reaction = mdl.reactions.get_by_id(rtid) reaction.remove_from_model() except KeyError: pass for metid in self.removed_metabolites: try: met = mdl.metabolites.get_by_id(metid) met.remove_from_model() except KeyError: pass mdl.id += "::{}".format(self.id) # Add gene annotation for gene in self.genes: try: gobj = model.genes.get_by_id(gene['id']) except KeyError: # genes should already be contained in the model if they have a reaction relationship # However, tolerate bad designs continue gobj.name = gene['name'] gobj.functional = gene['functional'] gobj.annotation = gene['annotation'] gobj.notes = gene['notes'] return mdl
def __init__(self, did, name, description, project, parent=None, reactions=None, metabolites=None, genes=None, removed_metabolites=None, removed_reactions=None, removed_genes=None, base_model=None, conditions=None, is_pydesign=False, design_func=None): """ Class for handling strain designs created by the project Mainly the useful functionality of this over dicts is validation, relationship to projects, creating the models as well as displaying the contents as a pandas dataframe :param project: Where project is None, the model will not be able to """ self._as_model = None self.project = project self.base_model = base_model self.id = did self.name = name self.description = description self._reactions = reactions if self._reactions is None: self._reactions = [] self._metabolites = metabolites if self._metabolites is None: self._metabolites = [] self._genes = genes if self._genes is None: self._genes = [] self._removed_metabolites = removed_metabolites if self._removed_metabolites is None: self._removed_metabolites = [] self._removed_reactions = removed_reactions if self._removed_reactions is None: self._removed_reactions = [] self._removed_genes = removed_genes if self._removed_genes is None: self._removed_genes = [] self.conditions = conditions self.parent = parent self.check_parents() self._p_model = None self.is_pydesign = is_pydesign self.design_func = design_func if self.is_pydesign and not hasattr(self.design_func, '__call__'): raise DesignError( "Python designs require a design function to be passed, got type {}" .format(type(design_func)))