Skip to content

Commit

Permalink
feat: separated task preprocessing from simulation
Browse files Browse the repository at this point in the history
  • Loading branch information
jonrkarr committed Sep 15, 2021
1 parent 8d7ac00 commit 89e3c7c
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Dockerfile-brian2
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Base OS
FROM ghcr.io/biosimulators/biosimulators_pyneuroml/pyneuroml:latest

ARG VERSION=0.0.9
ARG VERSION=0.0.10
ARG SIMULATOR_VERSION="2.4.2"

# metadata
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile-netpyne
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Base OS
FROM ghcr.io/biosimulators/biosimulators_pyneuroml/neuron:latest

ARG VERSION=0.0.9
ARG VERSION=0.0.10
ARG SIMULATOR_VERSION="1.0.0.2"

# metadata
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile-neuron
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Base OS
FROM ghcr.io/biosimulators/biosimulators_pyneuroml/pyneuroml:latest

ARG VERSION=0.0.9
ARG VERSION=0.0.10
ARG SIMULATOR_VERSION="8.0.0"

# metadata
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile-pyneuroml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Base OS
FROM python:3.9-slim-buster

ARG VERSION=0.0.9
ARG VERSION=0.0.10
ARG SIMULATOR_VERSION="0.5.11"

# metadata
Expand Down
2 changes: 1 addition & 1 deletion biosimulators_pyneuroml/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.0.9'
__version__ = '0.0.10'
63 changes: 49 additions & 14 deletions biosimulators_pyneuroml/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@
"""

from .data_model import Simulator, KISAO_ALGORITHM_MAP, SEDML_TIME_OUTPUT_COLUMN_ID, SEDML_OUTPUT_FILE_ID
from .utils import validate_task, read_xml_file, set_sim_in_lems_xml, run_lems_xml, get_simulator_run_lems_method
from .utils import validate_task, read_xml_file, set_sim_in_lems_xml, run_lems_xml, get_simulator_run_lems_method, validate_lems_document
from biosimulators_utils.combine.exec import exec_sedml_docs_in_archive
from biosimulators_utils.config import get_config, Config # noqa: F401
from biosimulators_utils.log.data_model import CombineArchiveLog, TaskLog, StandardOutputErrorCapturerLevel # noqa: F401
from biosimulators_utils.viz.data_model import VizFormat # noqa: F401
from biosimulators_utils.report.data_model import ReportFormat, VariableResults, SedDocumentResults # noqa: F401
from biosimulators_utils.sedml.data_model import (Task, UniformTimeCourseSimulation, # noqa: F401
from biosimulators_utils.sedml import validation
from biosimulators_utils.sedml.data_model import (Task, ModelAttributeChange, UniformTimeCourseSimulation, # noqa: F401
Variable, Symbol)
from biosimulators_utils.sedml.exec import exec_sed_doc as base_exec_sed_doc
from biosimulators_utils.sedml.utils import apply_changes_to_xml_model
from biosimulators_utils.utils.core import raise_errors_warnings
import copy
import functools
import os
Expand Down Expand Up @@ -105,7 +108,7 @@ def exec_sed_task(task, variables, preprocessed_task=None, log=None, config=None
Args:
task (:obj:`Task`): task
variables (:obj:`list` of :obj:`Variable`): variables that should be recorded
preprocessed_task (:obj:`object`, optional): preprocessed information about the task, including possible
preprocessed_task (:obj:`dict`, optional): preprocessed information about the task, including possible
model changes and variables. This can be used to avoid repeatedly executing the same initialization
for repeated calls to this method.
log (:obj:`TaskLog`, optional): log for the task
Expand All @@ -124,22 +127,38 @@ def exec_sed_task(task, variables, preprocessed_task=None, log=None, config=None
:obj:`NotImplementedError`: if the task is not of a supported type or involves an unsuported feature
'''
config = config or get_config()

if config.LOG and not log:
log = TaskLog()

if preprocessed_task is None:
preprocessed_task = preprocess_sed_task(task, variables, config=config, simulator=simulator)

lems_root = preprocessed_task['model']

if task.model.changes:
raise_errors_warnings(validation.validate_model_change_types(task.model.changes, (ModelAttributeChange,)),
error_summary='Changes for model `{}` are not supported.'.format(task.model.id))

model = copy.deepcopy(task.model)
for change in model.changes:
change.new_value = str(change.new_value)

apply_changes_to_xml_model(model, lems_root, sed_doc=None, working_dir=None)

lems_simulation = preprocessed_task['simulation']
sim = task.simulation
sim.algorithm = copy.deepcopy(sim.algorithm)
sim.algorithm.kisao_id = validate_task(task, variables, simulator, config=config)
sim.algorithm.kisao_id = preprocessed_task['algorithm_kisao_id']

lems_root = read_xml_file(task.model.source)

set_sim_in_lems_xml(lems_root, task, variables)
lems_results = run_lems_xml(lems_root, working_dirname=os.path.dirname(
task.model.source), lems_filename=task.model.source,
verbose=config.VERBOSE, config=config)[SEDML_OUTPUT_FILE_ID]
set_sim_in_lems_xml(lems_simulation, task, variables)
lems_results = run_lems_xml(
lems_root,
working_dirname=os.path.dirname(task.model.source),
lems_filename=task.model.source,
verbose=config.VERBOSE,
config=config,
)[SEDML_OUTPUT_FILE_ID]

# transform the results to an instance of :obj:`VariableResults`
variable_results = VariableResults()
Expand All @@ -156,11 +175,11 @@ def exec_sed_task(task, variables, preprocessed_task=None, log=None, config=None
if config.LOG:
log.algorithm = sim.algorithm.kisao_id
log.simulator_details = {
'method': 'pyneuroml.pynml.' + get_simulator_run_lems_method(simulator).__name__,
'method': preprocessed_task['simulation_method'],
'lemsSimulation': {
'length': '{}s'.format(sim.output_end_time),
'step': '{}s'.format((sim.output_end_time - sim.output_start_time) / sim.number_of_steps),
'method': KISAO_ALGORITHM_MAP[sim.algorithm.kisao_id]['id'],
'method': preprocessed_task['algorithm_method'],
},
}

Expand All @@ -179,6 +198,22 @@ def preprocess_sed_task(task, variables, config=None, simulator=Simulator.pyneur
simulator (:obj:`Simulator`, optional): simulator
Returns:
:obj:`object`: preprocessed information about the task
:obj:`dict`: preprocessed information about the task
"""
pass
config = config or get_config()

algorithm_kisao_id = validate_task(task, variables, simulator, config=config)

lems_root = read_xml_file(task.model.source)
lems_simulation = lems_root.xpath('/Lems/Simulation')[0]

validate_lems_document(lems_root)

# return preprocessed information
return {
'model': lems_root,
'simulation': lems_simulation,
'algorithm_kisao_id': algorithm_kisao_id,
'simulation_method': 'pyneuroml.pynml.' + get_simulator_run_lems_method(simulator).__name__,
'algorithm_method': KISAO_ALGORITHM_MAP[algorithm_kisao_id]['id'],
}
24 changes: 16 additions & 8 deletions biosimulators_pyneuroml/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from .data_model import Simulator, KISAO_ALGORITHM_MAP, RunLemsOptions, SEDML_TIME_OUTPUT_COLUMN_ID, SEDML_OUTPUT_FILE_ID
from biosimulators_utils.config import get_config
from biosimulators_utils.log.utils import StandardOutputErrorCapturer
from biosimulators_utils.sedml.data_model import ModelLanguage, UniformTimeCourseSimulation, Task, Variable, Symbol # noqa: F401
from biosimulators_utils.sedml.data_model import (ModelLanguage, ModelAttributeChange, UniformTimeCourseSimulation, # noqa: F401
Task, Variable, Symbol)
from biosimulators_utils.sedml import validation
from biosimulators_utils.simulator.utils import get_algorithm_substitution_policy
from biosimulators_utils.utils.core import raise_errors_warnings
Expand All @@ -24,6 +25,7 @@

__all__ = [
'validate_task',
'validate_lems_document',
'set_sim_in_lems_xml',
'run_lems_xml',
'get_simulator_run_lems_method',
Expand Down Expand Up @@ -59,7 +61,7 @@ def validate_task(task, variables, simulator, config=None):
error_summary='Task `{}` is invalid.'.format(task.id))
raise_errors_warnings(validation.validate_model_language(task.model.language, ModelLanguage.LEMS),
error_summary='Language for model `{}` is not supported.'.format(model.id))
raise_errors_warnings(validation.validate_model_change_types(task.model.changes, ()),
raise_errors_warnings(validation.validate_model_change_types(task.model.changes, (ModelAttributeChange,)),
error_summary='Changes for model `{}` are not supported.'.format(model.id))
raise_errors_warnings(*validation.validate_model_changes(task.model),
error_summary='Changes for model `{}` are invalid.'.format(model.id))
Expand Down Expand Up @@ -108,13 +110,11 @@ def validate_task(task, variables, simulator, config=None):
return exec_kisao_id


def set_sim_in_lems_xml(lems_xml_root, task, variables):
""" Set the simulation in a LEMS document
def validate_lems_document(lems_xml_root):
""" Validate LEMS document
Args:
lems_xml_root (:obj:`lxml.etree._Element`): LEMS document
task (:obj:`Task`): task
variables (:obj:`list` of :obj:`Variable`): variables to record
"""
lems_xml = lems_xml_root.xpath('/Lems')
if len(lems_xml) != 1:
Expand All @@ -127,12 +127,20 @@ def set_sim_in_lems_xml(lems_xml_root, task, variables):
raise ValueError('LEMS document must have a `Simulation` element.')
elif len(simulation_xml) > 1:
raise ValueError('LEMS document must have a single `Simulation` element, not {}.'.format(len(simulation_xml)))
simulation_xml = simulation_xml[0]

# add simulation

def set_sim_in_lems_xml(simulation_xml, task, variables):
""" Set the simulation in a LEMS document
Args:
simulation_xml (:obj:`lxml.etree._Element`): LEMS simulation
task (:obj:`Task`): task
variables (:obj:`list` of :obj:`Variable`): variables to record
"""
model = task.model
simulation = task.simulation

# modify simulation
simulation_xml.attrib['target'] = model.id
simulation_xml.attrib['length'] = '{}s'.format(simulation.output_end_time)
simulation_xml.attrib['step'] = '{}s'.format((simulation.output_end_time - simulation.output_start_time) / simulation.number_of_steps)
Expand Down
26 changes: 22 additions & 4 deletions tests/test_core_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,45 @@ def tearDown(self):
def test_exec_sed_task(self):
task, variables = self._get_simulation()
log = TaskLog()
results, log = core.exec_sed_task(task, variables, log)
results, log = core.exec_sed_task(task, variables, log=log)
self._assert_variable_results(task, variables, results)

def test_exec_sed_task_with_changes(self):
# TODO: add test for continuation of time course
# - Simulation 1: 0-10 ms
# - Simulation 2: 0-5 ms
# - Simulation 3: 5-10 ms starting from simulation #2

task, variables = self._get_simulation()
preprocessed_task = core.preprocess_sed_task(task, variables)

core.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)

task.model.changes.append(sedml_data_model.ModelAttributeChange(
target="/Lems/Include[@file='Cells.xml']/@file",
new_value='Undefined.xml',
))
with self.assertRaises(RuntimeError):
core.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)

def test_exec_sed_task_neuron(self):
task, variables = self._get_simulation()
log = TaskLog()
results, log = core.exec_sed_task(task, variables, log, simulator=Simulator.neuron)
results, log = core.exec_sed_task(task, variables, log=log, simulator=Simulator.neuron)
self._assert_variable_results(task, variables, results)

def test_exec_sed_task_netpyne(self):
task, variables = self._get_simulation()
log = TaskLog()
results, log = core.exec_sed_task(task, variables, log, simulator=Simulator.netpyne)
results, log = core.exec_sed_task(task, variables, log=log, simulator=Simulator.netpyne)
self._assert_variable_results(task, variables, results)

def test_exec_sed_task_non_zero_output_start_time(self):
task, variables = self._get_simulation()
task.simulation.output_start_time = 100e-3
task.simulation.number_of_steps = int(200 / 0.01)
log = TaskLog()
results, log = core.exec_sed_task(task, variables, log)
results, log = core.exec_sed_task(task, variables, log=log)
self._assert_variable_results(task, variables, results)

def test_exec_sedml_docs_in_combine_archive(self):
Expand Down
8 changes: 4 additions & 4 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,25 +101,25 @@ def test_set_sim_in_lems_xml(self):
Variable(id='v', target='hhpop[0]/v'),
]
lems_xml_root = utils.read_xml_file(filename)
utils.set_sim_in_lems_xml(lems_xml_root, task, variables)
sim_xml = lems_xml_root.xpath('/Lems/Simulation')[0]
utils.set_sim_in_lems_xml(sim_xml, task, variables)
self.assertEqual(sim_xml.attrib['length'], '10.0s')
self.assertEqual(sim_xml.attrib['step'], '1.0s')
self.assertEqual(sim_xml.attrib['method'], 'eulerTree')

lems_xml_root = lxml.etree.Element('Undefined')
with self.assertRaisesRegex(ValueError, 'must contain a single `Lems`'):
utils.set_sim_in_lems_xml(lems_xml_root, task, variables)
utils.validate_lems_document(lems_xml_root)

lems_xml_root = lxml.etree.Element('Lems')
with self.assertRaisesRegex(ValueError, 'must have a `Simulation`'):
utils.set_sim_in_lems_xml(lems_xml_root, task, variables)
utils.validate_lems_document(lems_xml_root)

lems_xml_root = lxml.etree.Element('Lems')
lems_xml_root.append(lxml.etree.Element('Simulation'))
lems_xml_root.append(lxml.etree.Element('Simulation'))
with self.assertRaisesRegex(ValueError, 'must have a single `Simulation`'):
utils.set_sim_in_lems_xml(lems_xml_root, task, variables)
utils.validate_lems_document(lems_xml_root)

def test_get_available_processors(self):
processors = utils.get_available_processors()
Expand Down

0 comments on commit 89e3c7c

Please sign in to comment.