1__author__ = "Jonathan Karr"
2__email__ = "karr@mssm.edu"
3
4try:
5    import biosimulators_utils
6    from biosimulators_utils.combine.data_model import CombineArchive, CombineArchiveContent, CombineArchiveContentFormat
7    from biosimulators_utils.combine.io import CombineArchiveWriter
8    from biosimulators_utils.config import get_config
9    from biosimulators_utils.report.data_model import ReportFormat
10    from biosimulators_utils.report.io import ReportReader
11    from biosimulators_utils.sedml.data_model import (
12        SedDocument,
13        Model, ModelLanguage, ModelAttributeChange,
14        UniformTimeCourseSimulation, Algorithm, AlgorithmParameterChange,
15        Task, DataGenerator, Report, DataSet, Variable, Symbol)
16    from biosimulators_utils.sedml.io import SedmlSimulationWriter
17    from smoldyn.biosimulators.data_model import Simulation, SimulationInstruction
18    from smoldyn.biosimulators.utils import read_simulation, _read_simulation_line
19except ModuleNotFoundError:
20    biosimulators_utils = None
21from unittest import mock
22import datetime
23import dateutil.tz
24import flaky
25import numpy
26import numpy.testing
27import os
28import shutil
29import smoldyn
30try:
31    import smoldyn.biosimulators.__main__
32    import smoldyn.biosimulators.combine
33except ModuleNotFoundError:
34    biosimulators_utils = None
35import tempfile
36import unittest
37
38
39@unittest.skipIf(biosimulators_utils is None, "BioSimulators-utils must be installed")
40class BioSimulatorsUtilsTestCase(unittest.TestCase):
41    EXAMPLES_DIRNAME = os.path.join(os.path.dirname(__file__), '..', 'examples')
42
43    def test__read_simulation_line(self):
44        sim = Simulation()
45
46        _read_simulation_line('dim 3', {}, sim)
47        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
48            id='number_dimensions',
49            description='Number of dimensions',
50            macro='dim',
51            arguments='3',
52        )))
53
54        _read_simulation_line('low_wall x -100 p', {}, sim)
55        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
56            id='low_x_wall_1',
57            description='Low x wall 1',
58            macro='low_wall x',
59            arguments='-100 p',
60        )))
61
62        _read_simulation_line('high_wall x -100 p', {}, sim)
63        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
64            id='high_x_wall_1',
65            description='High x wall 1',
66            macro='high_wall x',
67            arguments='-100 p',
68        )))
69
70        _read_simulation_line('boundaries x 0 1', {}, sim)
71        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
72            id='x_boundary',
73            description='X boundary',
74            macro='boundaries x',
75            arguments='0 1',
76        )))
77
78        _read_simulation_line('define SYSLENGTH 50', {}, sim)
79        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
80            id='value_parameter_SYSLENGTH',
81            description='Value of parameter "SYSLENGTH"',
82            macro='define SYSLENGTH',
83            arguments='50',
84        )))
85
86        _read_simulation_line('difc all 1', {}, sim)
87        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
88            id='diffusion_coefficient_species_all',
89            description='Diffusion coefficient of species "all"',
90            macro='difc all',
91            arguments='1',
92        )))
93
94        _read_simulation_line('difc all(x) 1', {}, sim)
95        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
96            id='diffusion_coefficient_species_all_state_x',
97            description='Diffusion coefficient of species "all" in state "x"',
98            macro='difc all(x)',
99            arguments='1',
100        )))
101
102        _read_simulation_line('difc_rule Prot* 22', {}, sim)
103        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
104            id='diffusion_coefficient_rule_species_Prot_',
105            description='Diffusion coefficient rule for species "Prot*"',
106            macro='difc_rule Prot*',
107            arguments='22',
108        )))
109
110        _read_simulation_line('difc_rule Prot*(x) 22', {}, sim)
111        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
112            id='diffusion_coefficient_rule_species_Prot__state_x',
113            description='Diffusion coefficient rule for species "Prot*" in state "x"',
114            macro='difc_rule Prot*(x)',
115            arguments='22',
116        )))
117
118        _read_simulation_line('difm red 1 0 0 0 0 0 0 0 2', {}, sim)
119        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
120            id='membrane_diffusion_coefficient_species_red',
121            description='Membrane diffusion coefficient of species "red"',
122            macro='difm red',
123            arguments='1 0 0 0 0 0 0 0 2',
124        )))
125
126        _read_simulation_line('difm red(x) 1 0 0 0 0 0 0 0 2', {}, sim)
127        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
128            id='membrane_diffusion_coefficient_species_red_state_x',
129            description='Membrane diffusion coefficient of species "red" in state "x"',
130            macro='difm red(x)',
131            arguments='1 0 0 0 0 0 0 0 2',
132        )))
133
134        _read_simulation_line('difm_rule red* 1 0 0 0 0 0 0 0 2', {}, sim)
135        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
136            id='membrane_diffusion_coefficient_rule_species_red_',
137            description='Membrane diffusion coefficient rule for species "red*"',
138            macro='difm_rule red*',
139            arguments='1 0 0 0 0 0 0 0 2',
140        )))
141
142        _read_simulation_line('difm_rule red*(x) 1 0 0 0 0 0 0 0 2', {}, sim)
143        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
144            id='membrane_diffusion_coefficient_rule_species_red__state_x',
145            description='Membrane diffusion coefficient rule for species "red*" in state "x"',
146            macro='difm_rule red*(x)',
147            arguments='1 0 0 0 0 0 0 0 2',
148        )))
149
150        _read_simulation_line('drift red 0 0 0', {}, sim)
151        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
152            id='drift_species_red',
153            description='Drift of species "red"',
154            macro='drift red',
155            arguments='0 0 0',
156        )))
157
158        _read_simulation_line('drift red(x) 0 0 0', {}, sim)
159        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
160            id='drift_species_red_state_x',
161            description='Drift of species "red" in state "x"',
162            macro='drift red(x)',
163            arguments='0 0 0',
164        )))
165
166        _read_simulation_line('drift_rule red* 0 0 0', {}, sim)
167        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
168            id='drift_rule_species_red_',
169            description='Drift rule for species "red*"',
170            macro='drift_rule red*',
171            arguments='0 0 0',
172        )))
173
174        _read_simulation_line('drift_rule red*(x) 0 0 0', {}, sim)
175        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
176            id='drift_rule_species_red__state_x',
177            description='Drift rule for species "red*" in state "x"',
178            macro='drift_rule red*(x)',
179            arguments='0 0 0',
180        )))
181
182        _read_simulation_line('surface_drift red surf1 all 0.1', {}, sim)
183        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
184            id='surface_drift_species_red_surface_surf1_shape_all',
185            description='Surface drift of species "red" on surface "surf1" with panel shape "all"',
186            macro='surface_drift red surf1 all',
187            arguments='0.1',
188        )))
189
190        _read_simulation_line('surface_drift red(x) surf1 all 0.1', {}, sim)
191        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
192            id='surface_drift_species_red_state_x_surface_surf1_shape_all',
193            description='Surface drift of species "red" in state "x" on surface "surf1" with panel shape "all"',
194            macro='surface_drift red(x) surf1 all',
195            arguments='0.1',
196        )))
197
198        _read_simulation_line('surface_drift_rule red* surf1 all 0.1', {}, sim)
199        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
200            id='surface_drift_rule_species_red__surface_surf1_panel_all',
201            description='Surface drift rule for species "red*" on surface "surf1" of panel shape "all"',
202            macro='surface_drift_rule red* surf1 all',
203            arguments='0.1',
204        )))
205
206        _read_simulation_line('surface_drift_rule red*(x) surf1 all 0.1', {}, sim)
207        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
208            id='surface_drift_rule_species_red__state_x_surface_surf1_panel_all',
209            description='Surface drift rule for species "red*" in state "x" on surface "surf1" of panel shape "all"',
210            macro='surface_drift_rule red*(x) surf1 all',
211            arguments='0.1',
212        )))
213
214        _read_simulation_line('mol 167 A 5 u u', {}, sim)
215        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
216            id='initial_count_species_A_5_u_u',
217            description='Initial count of species "A 5 u u"',
218            macro='mol A 5 u u',
219            arguments='167',
220        )))
221
222        _read_simulation_line('compartment_mol 500 red intersection', {}, sim)
223        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
224            id='initial_count_species_red_intersection',
225            description='Initial count of species "red intersection"',
226            macro='compartment_mol red intersection',
227            arguments='500',
228        )))
229
230        _read_simulation_line('surface_mol 100 red(up) all rect r1', {}, sim)
231        self.assertTrue(sim.instructions[-1].is_equal(SimulationInstruction(
232            id='initial_count_species_red_up__all_rect_r1',
233            description='Initial count of species "red(up) all rect r1"',
234            macro='surface_mol red(up) all rect r1',
235            arguments='100',
236        )))
237
238    def test_read_simulation(self):
239        filename = os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt')
240        sim = read_simulation(filename)
241        self.assertEqual(sim.species, ['red', 'green'])
242        self.assertEqual(sim.compartments, [])
243        self.assertEqual(sim.surfaces, [])
244
245        filename = os.path.join(self.EXAMPLES_DIRNAME, 'S9_compartments', 'compart.txt')
246        sim = read_simulation(filename)
247        self.assertEqual(sim.species, ['red', 'green'])
248        self.assertEqual(sim.compartments, ['middle'])
249        self.assertEqual(sim.surfaces, ['walls', 'surf'])
250
251
252@unittest.skipIf(biosimulators_utils is None, "BioSimulators-utils must be installed")
253class BioSimulatorsCombineTestCase(unittest.TestCase):
254    EXAMPLES_DIRNAME = os.path.join(os.path.dirname(__file__), '..', 'examples')
255
256    def setUp(self):
257        self.dirname = tempfile.mkdtemp()
258
259    def tearDown(self):
260        shutil.rmtree(self.dirname)
261
262    def test_SmoldynOutputFile(self):
263        output_file = smoldyn.biosimulators.data_model.SmoldynOutputFile('out.txt', '/tmp/out.txt')
264        self.assertEqual(output_file.name, 'out.txt')
265        self.assertEqual(output_file.filename, '/tmp/out.txt')
266
267    def test_SmoldynCommand(self):
268        output_file = smoldyn.biosimulators.data_model.SmoldynCommand('molcount', 'E')
269        self.assertEqual(output_file.command, 'molcount')
270        self.assertEqual(output_file.type, 'E')
271
272    def test_init_smoldyn_simulation_from_configuration_file(self):
273        sim = smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file(
274            os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt'))
275        self.assertIsInstance(sim, smoldyn.Simulation)
276
277        with self.assertRaises(FileNotFoundError):
278            smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file('not a file')
279
280        with self.assertRaisesRegex(ValueError, 'Error: '):
281            smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file(
282                os.path.join(self.EXAMPLES_DIRNAME, 'CMakeLists.txt'))
283
284    def test_read_write_smoldyn_simulation_configuration(self):
285        filename = os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt')
286        config = smoldyn.biosimulators.combine.read_smoldyn_simulation_configuration(filename)
287        self.assertEqual(config[2], 'graphics opengl')
288        self.assertEqual(config[-6], 'mol 2 green 50')
289
290        filename2 = os.path.join(self.dirname, 'config.txt')
291        smoldyn.biosimulators.combine.write_smoldyn_simulation_configuration(config, filename2)
292
293        config2 = smoldyn.biosimulators.combine.read_smoldyn_simulation_configuration(filename2)
294        self.assertEqual(config2, config)
295
296    def test_normalize_smoldyn_simulation_configuration(self):
297        filename = os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt')
298        config = smoldyn.biosimulators.combine.read_smoldyn_simulation_configuration(filename)
299        self.assertEqual(len(config), 30)
300
301        smoldyn.biosimulators.combine.normalize_smoldyn_simulation_configuration(config)
302
303        self.assertEqual(len(config), 25)
304
305        self.assertEqual(config[2], 'graphics opengl')
306        self.assertEqual(config[-1], 'mol 2 green 50')
307
308        config[0] = '#'
309        smoldyn.biosimulators.combine.normalize_smoldyn_simulation_configuration(config)
310        self.assertEqual(len(config), 23)
311
312        config[0] = 'graphics  opengl###comment   #comment2'
313        smoldyn.biosimulators.combine.normalize_smoldyn_simulation_configuration(config)
314        self.assertEqual(config[0], 'graphics opengl # comment   #comment2')
315
316        config[0] = 'graphics  opengl##  '
317        smoldyn.biosimulators.combine.normalize_smoldyn_simulation_configuration(config)
318        self.assertEqual(config[0], 'graphics opengl')
319
320        config[0] = '#comment   comment2'
321        smoldyn.biosimulators.combine.normalize_smoldyn_simulation_configuration(config)
322        self.assertEqual(config[0], '# comment   comment2')
323
324        config[0] = 'graphics  opengl  '
325        smoldyn.biosimulators.combine.normalize_smoldyn_simulation_configuration(config)
326        self.assertEqual(config[0], 'graphics opengl')
327
328    def test_disable_smoldyn_graphics_in_simulation_configuration(self):
329        filename = os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt')
330        config = smoldyn.biosimulators.combine.read_smoldyn_simulation_configuration(filename)
331        self.assertEqual(config[2], 'graphics opengl')
332
333        smoldyn.biosimulators.combine.disable_smoldyn_graphics_in_simulation_configuration(config)
334        self.assertEqual(config[2], 'graphics none')
335
336    def test_apply_change_to_smoldyn_simulation(self):
337        filename = os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt')
338        config = smoldyn.biosimulators.combine.read_smoldyn_simulation_configuration(filename)
339        smoldyn.biosimulators.combine.disable_smoldyn_graphics_in_simulation_configuration(config)
340        filename2 = os.path.join(self.dirname, 'config.txt')
341        smoldyn.biosimulators.combine.write_smoldyn_simulation_configuration(config, filename2)
342
343        smoldyn_simulation = smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file(filename2)
344        change = ModelAttributeChange(target='define K_1', new_value='0')
345        preprocessed_change = smoldyn.biosimulators.combine.validate_model_change(change)
346        smoldyn.biosimulators.combine.apply_change_to_smoldyn_simulation_configuration(config, change, preprocessed_change)
347
348        smoldyn_simulation = smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file(filename2)
349        change = ModelAttributeChange(target='killmol red', new_value='0')
350        preprocessed_change = smoldyn.biosimulators.combine.validate_model_change(change)
351        smoldyn.biosimulators.combine.apply_change_to_smoldyn_simulation(smoldyn_simulation, change, preprocessed_change)
352
353        smoldyn_simulation = smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file(filename2)
354        change = ModelAttributeChange(target='fixmolcount green', new_value='10')
355        preprocessed_change = smoldyn.biosimulators.combine.validate_model_change(change)
356        smoldyn.biosimulators.combine.apply_change_to_smoldyn_simulation(smoldyn_simulation, change, preprocessed_change)
357
358        smoldyn_simulation = smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file(filename2)
359        change = ModelAttributeChange(target='fixmolcountincmpt green  cytosol', new_value='10')
360        preprocessed_change = smoldyn.biosimulators.combine.validate_model_change(change)
361        smoldyn.biosimulators.combine.apply_change_to_smoldyn_simulation(smoldyn_simulation, change, preprocessed_change)
362
363        change = ModelAttributeChange(target=' dim  ', new_value=' 5 ')
364        with self.assertRaises(NotImplementedError):
365            smoldyn.biosimulators.combine.validate_model_change(change)
366
367    def test_get_smoldyn_run_timecourse_args(self):
368        sim = UniformTimeCourseSimulation(
369            initial_time=10.,
370            output_start_time=20.,
371            output_end_time=30.,
372            number_of_points=10,
373        )
374
375        self.assertEqual(smoldyn.biosimulators.combine.get_smoldyn_run_timecourse_args(sim), {
376            'start': 10.,
377            'stop': 30.,
378            'dt': 1.,
379        })
380
381        sim.output_end_time += 0.01
382        with self.assertRaises(NotImplementedError):
383            smoldyn.biosimulators.combine.get_smoldyn_run_timecourse_args(sim)
384
385    def test_get_smoldyn_instance_attr_or_run_algorithm_parameter_arg(self):
386        change = AlgorithmParameterChange(kisao_id='KISAO_0000254', new_value='5')
387        param = smoldyn.biosimulators.combine.get_smoldyn_instance_attr_or_run_algorithm_parameter_arg(change)
388        self.assertEqual(param, {
389            'name': 'accuracy',
390            'type': smoldyn.biosimulators.combine.AlgorithmParameterType.run_argument,
391            'value': 5.,
392        })
393
394        change = AlgorithmParameterChange(kisao_id='KISAO_0000488', new_value='10')
395        param = smoldyn.biosimulators.combine.get_smoldyn_instance_attr_or_run_algorithm_parameter_arg(change)
396        self.assertEqual(param, {
397            'name': 'setRandomSeed',
398            'type': smoldyn.biosimulators.combine.AlgorithmParameterType.instance_attribute,
399            'value': 10,
400        })
401
402        change = AlgorithmParameterChange(kisao_id='KISAO_0000488', new_value='not a float')
403        with self.assertRaises(ValueError):
404            smoldyn.biosimulators.combine.get_smoldyn_instance_attr_or_run_algorithm_parameter_arg(change)
405
406        change = AlgorithmParameterChange(kisao_id='KISAO_0000001', new_value='not a float')
407        with self.assertRaises(NotImplementedError):
408            smoldyn.biosimulators.combine.get_smoldyn_instance_attr_or_run_algorithm_parameter_arg(change)
409
410    def test_add_smoldyn_output_file(self):
411        filename = os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt')
412        config = smoldyn.biosimulators.combine.read_smoldyn_simulation_configuration(filename)
413        smoldyn.biosimulators.combine.disable_smoldyn_graphics_in_simulation_configuration(config)
414
415        filename2 = os.path.join(self.dirname, 'config.txt')
416        smoldyn.biosimulators.combine.write_smoldyn_simulation_configuration(config, filename2)
417
418        sim = smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file(filename2)
419        sim.setGraphics('none')
420        output_file = smoldyn.biosimulators.combine.add_smoldyn_output_file(self.dirname, sim)
421        self.assertIsInstance(output_file, smoldyn.biosimulators.data_model.SmoldynOutputFile)
422
423        smoldyn.biosimulators.combine.add_commands_to_smoldyn_output_file(sim, output_file, [
424            smoldyn.biosimulators.data_model.SmoldynCommand(command='molcount', type='E'),
425        ])
426
427    def test_add_smoldyn_output_files_for_sed_variables(self):
428        filename = os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt')
429        config = smoldyn.biosimulators.combine.read_smoldyn_simulation_configuration(filename)
430        smoldyn.biosimulators.combine.disable_smoldyn_graphics_in_simulation_configuration(config)
431
432        filename2 = os.path.join(self.dirname, 'config.txt')
433        smoldyn.biosimulators.combine.write_smoldyn_simulation_configuration(config, filename2)
434
435        sim = smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file(filename2)
436        sim.setGraphics('none')
437
438        var = Variable(id='time', symbol=Symbol.time.value)
439        variable_output_cmd_map = smoldyn.biosimulators.combine.validate_variables([var])
440        smoldyn_output_files = smoldyn.biosimulators.combine.add_smoldyn_output_files_for_sed_variables(
441            self.dirname, [var], variable_output_cmd_map, sim)
442        self.assertEqual(set(smoldyn_output_files.keys()), set(['molcount']))
443
444        var.symbol = 'undefined'
445        with self.assertRaises(ValueError):
446            smoldyn.biosimulators.combine.validate_variables([var])
447
448        var.symbol = None
449        var.target = 'undefined '
450        with self.assertRaises(NotImplementedError):
451            smoldyn.biosimulators.combine.validate_variables([var])
452
453    def test_get_variable_results(self):
454        filename = os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt')
455        config = smoldyn.biosimulators.combine.read_smoldyn_simulation_configuration(filename)
456        smoldyn.biosimulators.combine.disable_smoldyn_graphics_in_simulation_configuration(config)
457
458        filename2 = os.path.join(self.dirname, 'config.txt')
459        smoldyn.biosimulators.combine.write_smoldyn_simulation_configuration(config, filename2)
460
461        sim = smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file(filename2)
462        sim.setGraphics('none')
463
464        vars = [
465            Variable(id='time', symbol=Symbol.time.value),
466            Variable(id='red', target='molcount red'),
467            Variable(id='green', target='molcountspecies green'),
468            Variable(id='greenBox', target='molcountinbox green 0 10'),
469            Variable(id='greenHist', target='molcountspace green x 0 100 20')
470        ]
471        variable_output_cmd_map = smoldyn.biosimulators.combine.validate_variables(vars)
472        smoldyn_output_files = smoldyn.biosimulators.combine.add_smoldyn_output_files_for_sed_variables(
473            self.dirname, vars, variable_output_cmd_map, sim)
474
475        sim.run(start=0., stop=0.2, dt=0.01, overwrite=True, display=False, quit_at_end=False)
476
477        results = smoldyn.biosimulators.combine.get_variable_results(10, vars, variable_output_cmd_map, smoldyn_output_files)
478        self.assertEqual(set(results.keys()), set(['time', 'red', 'green', 'greenBox', 'greenHist']))
479        numpy.testing.assert_allclose(results['time'], numpy.linspace(0.1, 0.2, 11))
480
481        self.assertEqual(results['red'].shape, (11, ))
482        self.assertFalse(numpy.any(numpy.isnan(results['red'])))
483
484        self.assertEqual(results['green'].shape, (11, ))
485        self.assertFalse(numpy.any(numpy.isnan(results['green'])))
486
487        self.assertEqual(results['greenBox'].shape, (11, ))
488        self.assertFalse(numpy.any(numpy.isnan(results['greenBox'])))
489
490        self.assertEqual(results['greenHist'].shape, (11, 20))
491        self.assertFalse(numpy.any(numpy.isnan(results['greenHist'])))
492
493        vars = [
494            Variable(id='time', symbol='undefined'),
495        ]
496        with self.assertRaises(ValueError):
497            variable_output_cmd_map = smoldyn.biosimulators.combine.validate_variables(vars)
498            smoldyn.biosimulators.combine.get_variable_results(10, vars, variable_output_cmd_map, smoldyn_output_files)
499
500        vars = [
501            Variable(id='red', target='molcount blue'),
502        ]
503        variable_output_cmd_map = smoldyn.biosimulators.combine.validate_variables(vars)
504        with self.assertRaises(ValueError):
505            smoldyn.biosimulators.combine.get_variable_results(10, vars, variable_output_cmd_map, smoldyn_output_files)
506
507        vars = [
508            Variable(id='red', target='undefined blue'),
509        ]
510        with self.assertRaises(NotImplementedError):
511            variable_output_cmd_map = smoldyn.biosimulators.combine.validate_variables(vars)
512            smoldyn.biosimulators.combine.get_variable_results(10, vars, variable_output_cmd_map, smoldyn_output_files)
513
514    def test_get_variable_results_2d(self):
515        filename = os.path.join(self.EXAMPLES_DIRNAME, 'S4_molecules', 'isotropic', 'diffi.txt')
516        config = smoldyn.biosimulators.combine.read_smoldyn_simulation_configuration(filename)
517        smoldyn.biosimulators.combine.disable_smoldyn_graphics_in_simulation_configuration(config)
518
519        filename2 = os.path.join(self.dirname, 'config.txt')
520        smoldyn.biosimulators.combine.write_smoldyn_simulation_configuration(config, filename2)
521
522        sim = smoldyn.biosimulators.combine.init_smoldyn_simulation_from_configuration_file(filename2)
523        sim.setGraphics('none')
524
525        vars = [
526            Variable(id='time', symbol=Symbol.time.value),
527            Variable(id='greenHist', target='molcountspace2d green(all) z -10 10 30 -10 10 40 -10 10')
528        ]
529        variable_output_cmd_map = smoldyn.biosimulators.combine.validate_variables(vars)
530        smoldyn_output_files = smoldyn.biosimulators.combine.add_smoldyn_output_files_for_sed_variables(
531            self.dirname, vars, variable_output_cmd_map, sim)
532
533        sim.run(start=0., stop=0.2, dt=0.01, overwrite=True, display=False, quit_at_end=False)
534
535        results = smoldyn.biosimulators.combine.get_variable_results(10, vars, variable_output_cmd_map, smoldyn_output_files)
536        self.assertEqual(set(results.keys()), set(['time', 'greenHist']))
537        numpy.testing.assert_allclose(results['time'], numpy.linspace(0.1, 0.2, 11))
538
539        self.assertEqual(results['greenHist'].shape, (11, 30, 40))
540        self.assertFalse(numpy.any(numpy.isnan(results['greenHist'])))
541
542    def test_exec_sed_task(self):
543        task = Task(
544            id='task',
545            model=Model(
546                id='model',
547                source=os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt'),
548                language=ModelLanguage.Smoldyn.value,
549            ),
550            simulation=UniformTimeCourseSimulation(
551                initial_time=0.,
552                output_start_time=0.1,
553                output_end_time=0.2,
554                number_of_points=10,
555                algorithm=Algorithm(
556                    kisao_id='KISAO_0000057',
557                    changes=[
558                        AlgorithmParameterChange(kisao_id='KISAO_0000488', new_value='10'),
559                    ]
560                )
561            ),
562        )
563
564        variables = [
565            Variable(id='time', symbol=Symbol.time.value, task=task),
566            Variable(id='red', target='molcount red', task=task),
567            Variable(id='green', target='molcount green', task=task),
568        ]
569
570        results, log = smoldyn.biosimulators.combine.exec_sed_task(task, variables)
571
572        self.assertEqual(set(results.keys()), set(['time', 'red', 'green']))
573        numpy.testing.assert_allclose(results['time'], numpy.linspace(0.1, 0.2, 11))
574        for result in results.values():
575            self.assertEqual(result.shape, (11, ))
576            self.assertFalse(numpy.any(numpy.isnan(result)))
577
578        self.assertEqual(log.algorithm, 'KISAO_0000057')
579        self.assertEqual(log.simulator_details, {
580            'class': 'smoldyn.Simulation',
581            'instanceAttributes': {'setRandomSeed': 10},
582            'method': 'run',
583            'methodArguments': {
584                'start': 0.,
585                'stop': 0.2,
586                'dt': 0.01,
587            },
588        })
589
590        task.simulation.algorithm.changes.append(AlgorithmParameterChange('KISAO_0000254', '5'))
591        results, log = smoldyn.biosimulators.combine.exec_sed_task(task, variables)
592        self.assertEqual(log.simulator_details, {
593            'class': 'smoldyn.Simulation',
594            'instanceAttributes': {'setRandomSeed': 10},
595            'method': 'run',
596            'methodArguments': {
597                'start': 0.,
598                'stop': 0.2,
599                'dt': 0.01,
600                'accuracy': 5.,
601            },
602        })
603
604        task.simulation.algorithm.kisao_id = 'KISAO_0000437'
605        with self.assertRaises(NotImplementedError):
606            smoldyn.biosimulators.combine.exec_sed_task(task, variables)
607
608    def test_exec_sed_task_positive_initial_time(self):
609        task = Task(
610            id='task',
611            model=Model(
612                id='model',
613                source=os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt'),
614                language=ModelLanguage.Smoldyn.value,
615            ),
616            simulation=UniformTimeCourseSimulation(
617                initial_time=0.01,
618                output_start_time=0.01,
619                output_end_time=0.11,
620                number_of_points=10,
621                algorithm=Algorithm(
622                    kisao_id='KISAO_0000057',
623                )
624            ),
625        )
626
627        variables = [
628            Variable(id='time', symbol=Symbol.time.value, task=task),
629            Variable(id='red', target='molcount red', task=task),
630            Variable(id='green', target='molcount green', task=task),
631        ]
632
633        results, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables)
634
635        self.assertEqual(set(results.keys()), set(['time', 'red', 'green']))
636        numpy.testing.assert_allclose(results['time'], numpy.linspace(0.01, 0.11, 11))
637
638    def test_exec_sed_task_negative_initial_time(self):
639        task = Task(
640            id='task',
641            model=Model(
642                id='model',
643                source=os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt'),
644                language=ModelLanguage.Smoldyn.value,
645            ),
646            simulation=UniformTimeCourseSimulation(
647                initial_time=-0.01,
648                output_start_time=-0.01,
649                output_end_time=0.09,
650                number_of_points=10,
651                algorithm=Algorithm(
652                    kisao_id='KISAO_0000057',
653                )
654            ),
655        )
656
657        variables = [
658            Variable(id='time', symbol=Symbol.time.value, task=task),
659            Variable(id='red', target='molcount red', task=task),
660            Variable(id='green', target='molcount green', task=task),
661        ]
662
663        results, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables)
664
665        self.assertEqual(set(results.keys()), set(['time', 'red', 'green']))
666        numpy.testing.assert_allclose(results['time'], numpy.linspace(-0.01, 0.09, 11), rtol=5e-6)
667
668    @flaky.flaky(max_runs=10, min_passes=1)
669    def test_exec_sed_task_with_changes(self):
670        task = Task(
671            id='task',
672            model=Model(
673                id='model',
674                source=os.path.join(os.path.dirname(__file__), 'fixtures', 'lotvolt.txt'),
675                language=ModelLanguage.Smoldyn.value,
676            ),
677            simulation=UniformTimeCourseSimulation(
678                initial_time=0.,
679                output_start_time=0.,
680                output_end_time=0.1,
681                number_of_points=10,
682                algorithm=Algorithm(
683                    kisao_id='KISAO_0000057',
684                    changes=[
685                        AlgorithmParameterChange(kisao_id='KISAO_0000488', new_value='10'),
686                    ]
687                )
688            ),
689        )
690        model = task.model
691        sim = task.simulation
692
693        variable_ids = ['rabbit', 'fox']
694
695        variables = []
696        for variable_id in variable_ids:
697            variables.append(Variable(id=variable_id, target='molcount ' + variable_id, task=task))
698
699        preprocessed_task = smoldyn.biosimulators.combine.preprocess_sed_task(task, variables)
700        results, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
701        with self.assertRaises(AssertionError):
702            for variable_id in variable_ids:
703                numpy.testing.assert_allclose(
704                    results[variable_id][0:int(sim.number_of_points / 2 + 1)],
705                    results[variable_id][-int(sim.number_of_points / 2 + 1):])
706
707        # check simulation is repeatable
708        preprocessed_task = smoldyn.biosimulators.combine.preprocess_sed_task(task, variables)
709        results2, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
710        for variable_id in variable_ids:
711            numpy.testing.assert_allclose(
712                results2[variable_id],
713                results[variable_id])
714
715        # check simulation is repeatable in two steps
716        sim.output_end_time = sim.output_end_time / 2
717        sim.number_of_points = int(sim.number_of_points / 2)
718
719        model.changes = []
720        for variable_id in variable_ids:
721            model.changes.append(ModelAttributeChange(target='fixmolcount ' + variable_id, new_value=None))
722        preprocessed_task = smoldyn.biosimulators.combine.preprocess_sed_task(task, variables)
723        model.changes = []
724        results2, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
725        for variable_id in variable_ids:
726            numpy.testing.assert_allclose(
727                results2[variable_id],
728                results[variable_id][0:sim.number_of_points + 1])
729
730        for variable_id in variable_ids:
731            model.changes.append(ModelAttributeChange(target='fixmolcount ' + variable_id, new_value=results2[variable_id][-1]))
732        results3, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
733        for variable_id in variable_ids:
734            numpy.testing.assert_allclose(
735                results3[variable_id],
736                results[variable_id][-(sim.number_of_points + 1):])
737
738        # check model change modifies simulation
739        model.changes = []
740        for variable_id in variable_ids:
741            model.changes.append(ModelAttributeChange(target='fixmolcount ' + variable_id, new_value=None))
742        preprocessed_task = smoldyn.biosimulators.combine.preprocess_sed_task(task, variables)
743        model.changes = []
744        results2, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
745        for variable_id in variable_ids:
746            numpy.testing.assert_allclose(
747                results2[variable_id],
748                results[variable_id][0:sim.number_of_points + 1])
749
750        for variable_id in variable_ids:
751            model.changes.append(ModelAttributeChange(target='fixmolcount ' + variable_id, new_value=results2[variable_id][-1] + 1))
752        results3, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
753        with self.assertRaises(AssertionError):
754            for variable_id in variable_ids:
755                numpy.testing.assert_allclose(
756                    results3[variable_id],
757                    results[variable_id][-(sim.number_of_points + 1):])
758
759        # check model change modifies simulation
760        model.changes = []
761        for variable_id in variable_ids:
762            model.changes.append(ModelAttributeChange(target='killmol ' + variable_id, new_value=None))
763        preprocessed_task = smoldyn.biosimulators.combine.preprocess_sed_task(task, variables)
764        model.changes = []
765        results2, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
766        for variable_id in variable_ids:
767            numpy.testing.assert_allclose(
768                results2[variable_id],
769                results[variable_id][0:sim.number_of_points + 1])
770
771        for variable_id in variable_ids:
772            model.changes.append(ModelAttributeChange(target='killmol ' + variable_id, new_value=0))
773        results3, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
774        with self.assertRaises(AssertionError):
775            for variable_id in variable_ids:
776                numpy.testing.assert_allclose(
777                    results3[variable_id],
778                    results[variable_id][-(sim.number_of_points + 1):])
779
780        # preprocessing-time change
781        model.changes = []
782        for variable_id in variable_ids:
783            model.changes.append(ModelAttributeChange(target='define K_1', new_value=10))
784        results2, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables)
785        for variable_id in variable_ids:
786            numpy.testing.assert_allclose(
787                results2[variable_id],
788                results[variable_id][0:sim.number_of_points + 1])
789
790        model.changes = []
791        for variable_id in variable_ids:
792            model.changes.append(ModelAttributeChange(target='define K_1', new_value=10))
793        preprocessed_task = smoldyn.biosimulators.combine.preprocess_sed_task(task, variables)
794        with self.assertRaisesRegex(NotImplementedError, 'can only be changed during simulation preprocessing'):
795            smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
796        model.changes = []
797        results2, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
798        for variable_id in variable_ids:
799            numpy.testing.assert_allclose(
800                results2[variable_id],
801                results[variable_id][0:sim.number_of_points + 1])
802
803        model.changes = []
804        for variable_id in variable_ids:
805            model.changes.append(ModelAttributeChange(target='define K_1', new_value=0))
806        preprocessed_task = smoldyn.biosimulators.combine.preprocess_sed_task(task, variables)
807        model.changes = []
808        results2, _ = smoldyn.biosimulators.combine.exec_sed_task(task, variables, preprocessed_task=preprocessed_task)
809        with self.assertRaises(AssertionError):
810            for variable_id in variable_ids:
811                numpy.testing.assert_allclose(
812                    results2[variable_id],
813                    results[variable_id][0:sim.number_of_points + 1])
814
815    def test_exec_sedml_docs_in_combine_archive(self):
816        doc, archive_filename = self._build_combine_archive()
817
818        out_dir = os.path.join(self.dirname, 'out')
819
820        config = get_config()
821        config.REPORT_FORMATS = [ReportFormat.h5]
822
823        _, log = smoldyn.biosimulators.combine.exec_sedml_docs_in_combine_archive(
824            archive_filename, out_dir, config=config)
825        if log.exception:
826            raise log.exception
827
828        self._assert_combine_archive_outputs(doc, out_dir)
829
830    def test_command_line_interface(self):
831        doc, archive_filename = self._build_combine_archive()
832
833        out_dir = os.path.join(self.dirname, 'out')
834        with mock.patch.dict(os.environ, self._get_combine_archive_exec_env()):
835            with smoldyn.biosimulators.__main__.App(argv=['-i', archive_filename, '-o', out_dir]) as app:
836                app.run()
837
838        self._assert_combine_archive_outputs(doc, out_dir)
839
840    def test_raw_command_line_interface(self):
841        with mock.patch('sys.argv', ['', '--help']):
842            with self.assertRaises(SystemExit) as context:
843                smoldyn.biosimulators.__main__.main()
844                self.assertRegex(context.Exception, 'usage: ')
845
846    def _build_combine_archive(self):
847        task = Task(
848            id='task',
849            model=Model(
850                id='model',
851                source='bounce1.txt',
852                language=ModelLanguage.Smoldyn.value,
853            ),
854            simulation=UniformTimeCourseSimulation(
855                id='sim',
856                initial_time=0.,
857                output_start_time=0.1,
858                output_end_time=0.2,
859                number_of_points=10,
860                algorithm=Algorithm(
861                    kisao_id='KISAO_0000057',
862                    changes=[
863                        AlgorithmParameterChange(kisao_id='KISAO_0000488', new_value='10'),
864                    ]
865                )
866            ),
867        )
868
869        variables = [
870            Variable(id='time', symbol=Symbol.time.value, task=task),
871            Variable(id='red', target='molcount red', task=task),
872            Variable(id='green', target='molcount green', task=task),
873        ]
874
875        doc = SedDocument(
876            models=[task.model],
877            simulations=[task.simulation],
878            tasks=[task],
879            data_generators=[
880                DataGenerator(
881                    id='data_gen_time',
882                    variables=[Variable(id='var_time', symbol=Symbol.time.value, task=task)],
883                    math='var_time',
884                ),
885                DataGenerator(
886                    id='data_gen_red',
887                    variables=[Variable(id='var_red', target='molcount red', task=task)],
888                    math='var_red',
889                ),
890                DataGenerator(
891                    id='data_gen_green',
892                    variables=[Variable(id='var_green', target='molcount green', task=task)],
893                    math='var_green',
894                ),
895            ],
896        )
897        doc.outputs.append(Report(
898            id='report',
899            data_sets=[
900                DataSet(id='data_set_time', label='time', data_generator=doc.data_generators[0]),
901                DataSet(id='data_set_red', label='red', data_generator=doc.data_generators[1]),
902                DataSet(id='data_set_green', label='green', data_generator=doc.data_generators[2]),
903            ]
904        ))
905
906        archive_dirname = os.path.join(self.dirname, 'archive')
907        os.makedirs(archive_dirname)
908        shutil.copyfile(os.path.join(self.EXAMPLES_DIRNAME, 'S1_intro', 'bounce1.txt'), os.path.join(archive_dirname, 'bounce1.txt'))
909        sim_filename = os.path.join(archive_dirname, 'sim_1.sedml')
910        SedmlSimulationWriter().run(doc, sim_filename)
911
912        archive = CombineArchive(
913            contents=[
914                CombineArchiveContent(
915                    'bounce1.txt', CombineArchiveContentFormat.Smoldyn.value),
916                CombineArchiveContent(
917                    'sim_1.sedml', CombineArchiveContentFormat.SED_ML.value),
918            ],
919        )
920        archive_filename = os.path.join(self.dirname, 'archive.omex')
921        CombineArchiveWriter().run(archive, archive_dirname, archive_filename)
922
923        return doc, archive_filename
924
925    def _get_combine_archive_exec_env(self):
926        return {
927            'REPORT_FORMATS': 'h5'
928        }
929
930    def _assert_combine_archive_outputs(self, doc, out_dir):
931        results = ReportReader().run(doc.outputs[0], out_dir, os.path.join('sim_1.sedml', doc.outputs[0].id), format=ReportFormat.h5)
932
933        self.assertEqual(set(results.keys()), set(['data_set_time', 'data_set_red', 'data_set_green']))
934        numpy.testing.assert_allclose(results['data_set_time'], numpy.linspace(0.1, 0.2, 11))
935        for result in results.values():
936            self.assertEqual(result.shape, (11, ))
937            self.assertFalse(numpy.any(numpy.isnan(result)))
938
939
940if __name__ == "__main__":
941    unittest.main()
942