Source code for sbmlutils.comp.flatten

"""Helpers for model flattening."""
import os
import time
from pathlib import Path
from typing import Optional

import libsbml

from sbmlutils.console import console
from sbmlutils.io import read_sbml, write_sbml
from sbmlutils.log import get_logger
from sbmlutils.validation import log_sbml_errors_for_doc, validate_doc


[docs]logger = get_logger(__name__)
[docs]def flatten_sbml( sbml_path: Path, sbml_flat_path: Path, leave_ports: bool = True ) -> libsbml.SBMLDocument: """Flatten given SBML file. :param sbml_path: input path to SBML file to flatten (should be a comp model) :param sbml_flat_path: output path for flat SBML :param leave_ports: boolean flag to leave ports in flattened model. :return: flattened SBMLDocument """ # FIXME: not working with relative paths, # necessary to change the working directory to the sbml file directory # to resolve relative links to external model definitions. if not isinstance(sbml_path, Path): sbml_path = Path(sbml_path) working_dir = os.getcwd() os.chdir(str(sbml_path.parent)) doc = read_sbml(source=sbml_path) flat_doc = flatten_sbml_doc( doc, leave_ports=leave_ports, sbml_flat_path=sbml_flat_path ) # change back the working dir os.chdir(working_dir) return flat_doc
[docs]def flatten_sbml_doc( doc: libsbml.SBMLDocument, sbml_flat_path: Optional[Path] = None, leave_ports: bool = True, ) -> libsbml.SBMLDocument: """Flatten SBMLDocument. Validation should be performed before the flattening and is not part of the flattening routine. If an output path is provided the file is written to the output path. :param doc: SBMLDocument to flatten. :param sbml_flat_path: Path to write flattended SBMLDocument to :param leave_ports: flag to leave ports :return: SBMLDocument """ error_count = doc.getNumErrors() if error_count > 0: if doc.getError(0).getErrorId() == libsbml.XMLFileUnreadable: # Handle case of unreadable file here. logger.error("SBML error in doc: libsbml.XMLFileUnreadable") elif doc.getError(0).getErrorId() == libsbml.XMLFileOperationError: # Handle case of other file error here. logger.error("SBML error in doc: libsbml.XMLFileOperationError") else: # Handle other error cases here. logger.error("SBML errors in doc, see SBMLDocument error log.") # converter options props = libsbml.ConversionProperties() props.addOption("flatten comp", True) # Invokes CompFlatteningConverter props.addOption("leave_ports", leave_ports) # Indicates whether to leave ports props.addOption("abortIfUnflattenable", "none") # flatten current = time.perf_counter() result = doc.convert(props) flattened_status = result == libsbml.LIBSBML_OPERATION_SUCCESS lines = [ str(doc), f"{'flattend':<25}: {str(flattened_status).upper()}", f"{'flatten time (ms)':<25}: {time.perf_counter() - current:.3f}", ] info = "\n".join(lines) if flattened_status: console.rule("Flatten SBML", style="success") console.print(info, style="success") console.rule(style="success") else: console.rule("Flatten SBML", style="error") console.print(info, style="error") log_sbml_errors_for_doc(doc) raise ValueError("SBML could not be flattend due to errors.") if sbml_flat_path is not None: write_sbml(doc, filepath=sbml_flat_path) logger.info(f"Flattened model created: '{sbml_flat_path}'") return doc
[docs]def flatten_external_model_definitions( doc: libsbml.SBMLDocument, validate: bool = False ) -> libsbml.SBMLDocument: """Convert all ExternalModelDefinitions to ModelDefinitions. I.e. the definition of models in external files are read and directly included in the top model. The resulting comp model consists than only of a single file. The model refs in the submodel do not change in the process, so no need to update the submodels. :param doc: SBMLDocument :param validate: validation flag :return: SBMLDocument with ExternalModelDefinitions replaced """ logger.debug("* flattenExternalModelDefinitions") # FIXME: handle multiple levels of hierarchies. Recursively to handle the ExternalModelDefinitions of submodels logger.warning( "flattenExternalModelDefinitions is experimental and does not work recursively!" ) comp_doc = doc.getPlugin("comp") if comp_doc is None: logger.warning("Model is not a comp model, no ExternalModelDefinitions") return doc emd_list = comp_doc.getListOfExternalModelDefinitions() if (emd_list is None) or (len(emd_list) == 0): # no ExternalModelDefinitions logger.warning("Model does not contain any ExternalModelDefinitions") return doc else: emd_ids = [] for emd in emd_list: logger.debug(emd) emd_ids.append(emd.getId()) # get the model definition from the model ref_model = emd.getReferencedModel() ref_doc = ref_model.getSBMLDocument() # print(ref_model) for k in range(ref_doc.getNumPlugins()): plugin = ref_doc.getPlugin(k) # print(k, plugin) # enable the package on the main SBMLDocument uri = plugin.getURI() prefix = plugin.getPrefix() doc.enablePackage(uri, prefix, True) # print("\n") # add model definition for model md = libsbml.ModelDefinition(ref_model) comp_doc.addModelDefinition(md) # remove the emds afterwards for emd_id in emd_ids: # remove the emd from the model comp_doc.removeExternalModelDefinition(emd_id) # validate if validate: validate_doc(doc) return doc