diff --git a/.gitignore b/.gitignore index 921d07f7..2db72f5a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ docs/.buildinfo docs/.doctrees/ docs/_raw_sources/ docs/_sources/ + +# test files (to be deleted) +test_spatial_exec.py diff --git a/biosimulators_utils/combine/data_model.py b/biosimulators_utils/combine/data_model.py index 1bbcfb88..b3420950 100644 --- a/biosimulators_utils/combine/data_model.py +++ b/biosimulators_utils/combine/data_model.py @@ -11,6 +11,8 @@ import abc import datetime # noqa: F401 import enum +from typing import List + __all__ = [ 'CombineArchiveBase', @@ -40,7 +42,7 @@ def __init__(self, contents=None): """ self.contents = contents or [] - def get_master_content(self): + def get_master_content(self) -> List: """ Get the master content of an archive Returns: diff --git a/biosimulators_utils/combine/exec.py b/biosimulators_utils/combine/exec.py index 7087f140..69c00083 100644 --- a/biosimulators_utils/combine/exec.py +++ b/biosimulators_utils/combine/exec.py @@ -29,19 +29,27 @@ import os import tempfile import shutil -import types # noqa: F401 +from typing import Optional, Tuple +from types import FunctionType # noqa: F401 + __all__ = [ 'exec_sedml_docs_in_archive', ] -def exec_sedml_docs_in_archive(sed_doc_executer, archive_filename, out_dir, apply_xml_model_changes=False, - sed_doc_executer_supported_features=(Task, Report, DataSet, Plot2D, Curve, Plot3D, Surface), - sed_doc_executer_logged_features=(Task, Report, DataSet, Plot2D, Curve, Plot3D, Surface), - log_level=StandardOutputErrorCapturerLevel.c, - config=None): - """ Execute the SED-ML files in a COMBINE/OMEX archive (execute tasks and save outputs) +def exec_sedml_docs_in_archive( + sed_doc_executer, + archive_filename: str, + out_dir: str, + apply_xml_model_changes=False, + sed_doc_executer_supported_features=(Task, Report, DataSet, Plot2D, Curve, Plot3D, Surface), + sed_doc_executer_logged_features=(Task, Report, DataSet, Plot2D, Curve, Plot3D, Surface), + log_level=StandardOutputErrorCapturerLevel.c, + config: Optional[Config] = None + ) -> Tuple[SedDocumentResults, CombineArchiveLog]: + """ Execute the SED-ML files in a COMBINE/OMEX archive (execute tasks and save outputs). If 'smoldyn' is detected + in the archive, a simularium file will automatically be generated. Args: sed_doc_executer (:obj:`types.FunctionType`): function to execute each SED document in the archive. @@ -53,8 +61,10 @@ def sed_doc_executer(doc, working_dir, base_out_path, rel_out_path=None, ''' Execute the tasks specified in a SED document and generate the specified outputs Args: - doc (:obj:`SedDocument` of :obj:`str`): SED document or a path to SED-ML file which defines a SED document - working_dir (:obj:`str`): working directory of the SED document (path relative to which models are located) + doc (:obj:`SedDocument` of :obj:`str`): SED document or a + path to SED-ML file which defines a SED document + working_dir (:obj:`str`): working directory of the + SED document (path relative to which models are located) out_path (:obj:`str`): path to store the outputs @@ -64,7 +74,8 @@ def sed_doc_executer(doc, working_dir, base_out_path, rel_out_path=None, with reports at keys ``{rel_out_path}/{report.id}`` within the HDF5 file rel_out_path (:obj:`str`, optional): path relative to :obj:`out_path` to store the outputs - apply_xml_model_changes (:obj:`bool`, optional): if :obj:`True`, apply any model changes specified in the SED-ML file + apply_xml_model_changes (:obj:`bool`, optional): if :obj:`True`, + apply any model changes specified in the SED-ML file log (:obj:`SedDocumentLog`, optional): execution status of document log_level (:obj:`StandardOutputErrorCapturerLevel`, optional): level at which to log output indent (:obj:`int`, optional): degree to indent status messages @@ -77,14 +88,17 @@ def sed_doc_executer(doc, working_dir, base_out_path, rel_out_path=None, * CSV: directory in which to save outputs to files ``{ out_dir }/{ relative-path-to-SED-ML-file-within-archive }/{ report.id }.csv`` * HDF5: directory in which to save a single HDF5 file (``{ out_dir }/reports.h5``), - with reports at keys ``{ relative-path-to-SED-ML-file-within-archive }/{ report.id }`` within the HDF5 file - - apply_xml_model_changes (:obj:`bool`): if :obj:`True`, apply any model changes specified in the SED-ML files before - calling :obj:`task_executer`. - sed_doc_executer_supported_features (:obj:`list` of :obj:`type`, optional): list of the types of elements that the - SED document executer supports. Default: tasks, reports, plots, data sets, curves, and surfaces. - sed_doc_executer_logged_features (:obj:`list` of :obj:`type`, optional): list of the types fo elements which that - the SED document executer logs. Default: tasks, reports, plots, data sets, curves, and surfaces. + with reports at keys ``{ relative-path-to-SED-ML-file-within-archive }/{ report.id }`` + within the HDF5 file + + apply_xml_model_changes (:obj:`bool`): if :obj:`True`, apply any model changes specified in the + SED-ML files before calling :obj:`task_executer`. + sed_doc_executer_supported_features (:obj:`list` of :obj:`type`, optional): list of the types + of elements that the SED document executer supports. Default: tasks, reports, plots, data sets, + curves, and surfaces. + sed_doc_executer_logged_features (:obj:`list` of :obj:`type`, optional): list of the types of elements + which that the SED document executer logs. Default: tasks, reports, + plots, data sets, curves, and surfaces. log_level (:obj:`StandardOutputErrorCapturerLevel`, optional): level at which to log output config (:obj:`Config`): configuration @@ -225,6 +239,34 @@ def sed_doc_executer(doc, working_dir, base_out_path, rel_out_path=None, results[content.location] = doc_results if config.LOG: doc_log.status = Status.SUCCEEDED + + # check the manifest for a smoldyn model + for file_contents in archive.contents: + if 'smoldyn' in file_contents.location: + config.SPATIAL = True + print('There is spatial!') + + # generate simularium file if spatial + if config.SPATIAL: + import biosimulators_simularium as biosimularium + simularium_filename = os.path.join(out_dir, 'output') + spatial_archive = biosimularium.SmoldynCombineArchive( + rootpath=out_dir, + simularium_filename=simularium_filename + ) + # check if modelout file exists + if not os.path.exists(spatial_archive.model_output_filename): + generate_model_output_file = True + else: + generate_model_output_file = False + # construct converter + converter = biosimularium.SmoldynDataConverter( + archive=spatial_archive, + generate_model_output=generate_model_output_file + ) + # generate simularium file + converter.generate_simularium_file(io_format='json') + except Exception as exception: if config.DEBUG: raise @@ -241,31 +283,35 @@ def sed_doc_executer(doc, working_dir, base_out_path, rel_out_path=None, print('') - # handle smoldyn output/simularium conversion - # arch = SmoldynCombineArchive(rootpath=archive_tmp_dir) - # if arch.verify_smoldyn_in_manifest(): - # converter = SmoldynDataConverter(arch) - # simularium_fp = os.path.join(arch.rootpath, 'simularium_output') - # arch.simularium_filename = simularium_fp - # converter.generate_simularium_file() - if config.BUNDLE_OUTPUTS: print('Bundling outputs ...') # bundle CSV files of reports into zip archive report_formats = config.REPORT_FORMATS - archive_paths = [os.path.join(out_dir, '**', '*.' + format.value) for format in report_formats if format != ReportFormat.h5] + archive_paths = [ + os.path.join(out_dir, '**', '*.' + f.value) + for f in report_formats if f != ReportFormat.h5 + ] archive = build_archive_from_paths(archive_paths, out_dir) if archive.files: ArchiveWriter().run(archive, os.path.join(out_dir, config.REPORTS_PATH)) # bundle PDF files of plots into zip archive viz_formats = config.VIZ_FORMATS - archive_paths = [os.path.join(out_dir, '**', '*.' + format.value) for format in viz_formats] + archive_paths = [os.path.join(out_dir, '**', '*.' + f.value) for f in viz_formats] archive = build_archive_from_paths(archive_paths, out_dir) if archive.files: ArchiveWriter().run(archive, os.path.join(out_dir, config.PLOTS_PATH)) + # bundle Simularium file into zip archive + if config.SPATIAL: + simularium_format = ['simularium'] + archive_paths = [os.path.join(out_dir, '**', '*.' + f) for f in simularium_format] + archive = build_archive_from_paths(archive_paths, out_dir) + if archive.files: + ArchiveWriter().run(archive, os.path.join(out_dir, 'simularium.zip')) + + # cleanup temporary files print('Cleaning up ...') if not config.KEEP_INDIVIDUAL_OUTPUTS: @@ -273,8 +319,11 @@ def sed_doc_executer(doc, working_dir, base_out_path, rel_out_path=None, report_formats = config.REPORT_FORMATS viz_formats = config.VIZ_FORMATS path_patterns = ( - [os.path.join(out_dir, '**', '*.' + format.value) for format in report_formats if format != ReportFormat.h5] - + [os.path.join(out_dir, '**', '*.' + format.value) for format in viz_formats] + [ + os.path.join(out_dir, '**', '*.' + f.value) + for f in report_formats if format != ReportFormat.h5 + ] + + [os.path.join(out_dir, '**', '*.' + f.value) for f in viz_formats] ) for path_pattern in path_patterns: for path in glob.glob(path_pattern, recursive=True): @@ -330,3 +379,9 @@ def sed_doc_executer(doc, working_dir, base_out_path, rel_out_path=None, # return results and log return (results, log) + + + + + + diff --git a/biosimulators_utils/config.py b/biosimulators_utils/config.py index 3e530a9a..5985990a 100644 --- a/biosimulators_utils/config.py +++ b/biosimulators_utils/config.py @@ -28,9 +28,13 @@ DEFAULT_BIOSIMULATIONS_API_ENDPOINT = 'https://api.biosimulations.org/' DEFAULT_BIOSIMULATIONS_API_AUTH_ENDPOINT = 'https://auth.biosimulations.org/oauth/token' DEFAULT_BIOSIMULATIONS_API_AUDIENCE = 'api.biosimulations.org' +DEFAULT_SUPPORTED_SPATIAL_SIMULATOR = 'smoldyn' +# noinspection PyPep8Naming,PyDefaultArgument class Config(object): + SUPPORTED_SPATIAL_SIMULATOR: str + """ Configuration Attributes: @@ -65,6 +69,8 @@ class Config(object): BIOSIMULATIONS_API_AUDIENCE (:obj:`str`): audience for the BioSimulations API VERBOSE (:obj:`bool`): whether to display the detailed output of the execution of each task DEBUG (:obj:`bool`): whether to raise exceptions rather than capturing them + SUPPORTED_SPATIAL_SIMULATOR (:obj:`strl`, optional): spatial simulator that this config supports. Currently + only `'smoldyn'` is supported. """ def __init__(self, @@ -96,22 +102,26 @@ def __init__(self, BIOSIMULATIONS_API_AUTH_ENDPOINT=DEFAULT_BIOSIMULATIONS_API_AUTH_ENDPOINT, BIOSIMULATIONS_API_AUDIENCE=DEFAULT_BIOSIMULATIONS_API_AUDIENCE, VERBOSE=False, - DEBUG=False): + DEBUG=False, + SPATIAL=False): """ Args: - OMEX_METADATA_INPUT_FORMAT (:obj:`OmexMetadataInputFormat`, optional): format to validate OMEX Metadata files against + OMEX_METADATA_INPUT_FORMAT (:obj:`OmexMetadataInputFormat`, optional): format to validate + OMEX Metadata files against OMEX_METADATA_OUTPUT_FORMAT (:obj:`OmexMetadataOutputFormat`, optional): format to export OMEX Metadata files OMEX_METADATA_SCHEMA (:obj:`OmexMetadataSchema`, optional): schema to validate OMEX Metadata files against - VALIDATE_OMEX_MANIFESTS (:obj:`bool`, optional): whether to validate OMEX manifests during the execution of COMBINE/OMEX archives - VALIDATE_SEDML (:obj:`bool`, optional): whether to validate SED-ML files during the execution of COMBINE/OMEX archives - VALIDATE_SEDML_MODELS (:obj:`bool`, optional): whether to validate models referenced by SED-ML files during the execution + VALIDATE_OMEX_MANIFESTS (:obj:`bool`, optional): whether to validate OMEX manifests during the execution of COMBINE/OMEX archives + VALIDATE_SEDML (:obj:`bool`, optional): whether to validate SED-ML files during the execution of + COMBINE/OMEX archives + VALIDATE_SEDML_MODELS (:obj:`bool`, optional): whether to validate models referenced by SED-ML + files during the execution of COMBINE/OMEX archives VALIDATE_IMPORTED_MODEL_FILES (:obj:`bool`, optional): whether to validate files imported from models VALIDATE_OMEX_METADATA (:obj:`bool`, optional): whether to validate OMEX metadata (RDF files) during the execution of COMBINE/OMEX archives VALIDATE_IMAGES (:obj:`bool`, optional): whether to validate the images in COMBINE/OMEX archives during their execution VALIDATE_RESULTS (:obj:`bool`, optional): whether to validate the results of simulations following their execution - ALGORITHM_SUBSTITUTION_POLICY (:obj:`str`, optional): algorithm substition policy + ALGORITHM_SUBSTITUTION_POLICY (:obj:`str`, optional): algorithm substitution policy COLLECT_COMBINE_ARCHIVE_RESULTS (:obj:`bool`, optional): whether to assemble an in memory data structure with all of the simulation results of COMBINE/OMEX archives COLLECT_SED_DOCUMENT_RESULTS (:obj:`bool`, optional): whether to assemble an in memory data structure with all of the @@ -132,6 +142,7 @@ def __init__(self, BIOSIMULATIONS_API_AUDIENCE (:obj:`str`, optional): audience for the BioSimulations API VERBOSE (:obj:`bool`, optional): whether to display the detailed output of the execution of each task DEBUG (:obj:`bool`, optional): whether to raise exceptions rather than capturing them + SPATIAL (:obj:`bool`, optional): whether the simulation is spatial in nature and able to be simularium-ed """ self.OMEX_METADATA_INPUT_FORMAT = OMEX_METADATA_INPUT_FORMAT self.OMEX_METADATA_OUTPUT_FORMAT = OMEX_METADATA_OUTPUT_FORMAT @@ -162,23 +173,34 @@ def __init__(self, self.BIOSIMULATIONS_API_AUDIENCE = BIOSIMULATIONS_API_AUDIENCE self.VERBOSE = VERBOSE self.DEBUG = DEBUG + self.SPATIAL = SPATIAL + self.SUPPORTED_SPATIAL_SIMULATOR = DEFAULT_SUPPORTED_SPATIAL_SIMULATOR + try: + assert self.SUPPORTED_SPATIAL_SIMULATOR == 'smoldyn' + except AssertionError: + raise ValueError( + """ + The only spatial simulator that is currently supported is 'smoldyn'. Please set the value of + SUPPORTED_SPATIAL_SIMULATOR to 'smoldyn' and try again. + """ + ) def get_config(): - """ Get the configuration + """ Factory for generating a new instance of `Config`. Returns: :obj:`Config`: configuration """ report_formats = os.environ.get('REPORT_FORMATS', 'h5').strip() if report_formats: - report_formats = [ReportFormat(format.strip().lower()) for format in report_formats.split(',')] + report_formats = [ReportFormat(f.strip().lower()) for f in report_formats.split(',')] else: report_formats = [] viz_formats = os.environ.get('VIZ_FORMATS', 'pdf').strip() if viz_formats: - viz_formats = [VizFormat(format.strip().lower()) for format in viz_formats.split(',')] + viz_formats = [VizFormat(f.strip().lower()) for f in viz_formats.split(',')] else: viz_formats = [] @@ -198,7 +220,8 @@ def get_config(): VALIDATE_RESULTS=os.environ.get('VALIDATE_RESULTS', '1').lower() in ['1', 'true'], ALGORITHM_SUBSTITUTION_POLICY=AlgorithmSubstitutionPolicy(os.environ.get( 'ALGORITHM_SUBSTITUTION_POLICY', DEFAULT_ALGORITHM_SUBSTITUTION_POLICY)), - COLLECT_COMBINE_ARCHIVE_RESULTS=os.environ.get('COLLECT_COMBINE_ARCHIVE_RESULTS', '0').lower() in ['1', 'true'], + COLLECT_COMBINE_ARCHIVE_RESULTS=os.environ.get('COLLECT_COMBINE_ARCHIVE_RESULTS', + '0').lower() in ['1', 'true'], COLLECT_SED_DOCUMENT_RESULTS=os.environ.get('COLLECT_SED_DOCUMENT_RESULTS', '0').lower() in ['1', 'true'], SAVE_PLOT_DATA=os.environ.get('SAVE_PLOT_DATA', '1').lower() in ['1', 'true'], REPORT_FORMATS=report_formats, @@ -212,10 +235,12 @@ def get_config(): LOG_PATH=os.environ.get('LOG_PATH', DEFAULT_LOG_PATH), BIOSIMULATORS_API_ENDPOINT=os.environ.get('BIOSIMULATORS_API_ENDPOINT', DEFAULT_BIOSIMULATORS_API_ENDPOINT), BIOSIMULATIONS_API_ENDPOINT=os.environ.get('BIOSIMULATIONS_API_ENDPOINT', DEFAULT_BIOSIMULATIONS_API_ENDPOINT), - BIOSIMULATIONS_API_AUTH_ENDPOINT=os.environ.get('BIOSIMULATIONS_API_AUTH_ENDPOINT', DEFAULT_BIOSIMULATIONS_API_AUTH_ENDPOINT), + BIOSIMULATIONS_API_AUTH_ENDPOINT=os.environ.get('BIOSIMULATIONS_API_AUTH_ENDPOINT', + DEFAULT_BIOSIMULATIONS_API_AUTH_ENDPOINT), BIOSIMULATIONS_API_AUDIENCE=os.environ.get('BIOSIMULATIONS_API_AUDIENCE', DEFAULT_BIOSIMULATIONS_API_AUDIENCE), VERBOSE=os.environ.get('VERBOSE', '1').lower() in ['1', 'true'], DEBUG=os.environ.get('DEBUG', '0').lower() in ['1', 'true'], + SPATIAL=os.environ.get('SPATIAL', '0').lower() in ['1', 'true'] ) diff --git a/requirements.optional.txt b/requirements.optional.txt index d28f5af7..1c3d3fef 100644 --- a/requirements.optional.txt +++ b/requirements.optional.txt @@ -23,10 +23,10 @@ python_libsbml [smoldyn] smoldyn >= 2.66 -# simulariumio -[simularium] -simulariumio +[spatial] +smoldyn >= 2.66 +biosimulators_simularium ################################# ## Visualization formats