diff --git a/gillespy2/core/__init__.py b/gillespy2/core/__init__.py
index 2959a97ad..3ba8556d2 100644
--- a/gillespy2/core/__init__.py
+++ b/gillespy2/core/__init__.py
@@ -30,6 +30,7 @@
from .results import *
from .sortableobject import *
from .species import *
+from .timespan import TimeSpan
from gillespy2.__version__ import __version__
_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
diff --git a/gillespy2/core/gillespyError.py b/gillespy2/core/gillespyError.py
index 977970827..4da00039b 100644
--- a/gillespy2/core/gillespyError.py
+++ b/gillespy2/core/gillespyError.py
@@ -33,6 +33,10 @@ class ParameterError(ModelError):
pass
+class TimespanError(ModelError):
+ pass
+
+
# Solver specific errors
class SolverError(Exception):
pass
diff --git a/gillespy2/core/gillespySolver.py b/gillespy2/core/gillespySolver.py
index cd1882db3..b4c8c50bf 100644
--- a/gillespy2/core/gillespySolver.py
+++ b/gillespy2/core/gillespySolver.py
@@ -18,6 +18,7 @@
import copy
import numpy
+from .timespan import TimeSpan
from .gillespyError import SimulationError, ModelError
from typing import Set, Type
@@ -97,18 +98,16 @@ def validate_tspan(self, increment, t):
)
if self.model.tspan is None:
- end = 20 + increment if t is None else t + increment
- self.model.timespan(numpy.arange(0, end, increment))
+ if t is None:
+ tspan = TimeSpan.arange(increment)
+ else:
+ tspan = TimeSpan.arange(increment, t=t)
+ self.model.timespan(tspan)
+ elif not isinstance(self.model.tspan, TimeSpan) or type(self.model.tspan).__name__ != "TimeSpan":
+ tspan = TimeSpan(self.model.tspan)
+ self.model.timespan(tspan)
else:
- if self.model.tspan[0] < 0:
- raise SimulationError("Simulation must run from t=0 to end time (t must always be positive).")
-
- first_diff = self.model.tspan[1] - self.model.tspan[0]
- other_diff = self.model.tspan[2:] - self.model.tspan[1:-1]
- isuniform = numpy.isclose(other_diff, first_diff).all()
-
- if not isuniform:
- raise SimulationError("StochKit only supports uniform timespans")
+ self.model.tspan.validate()
@classmethod
def get_supported_features(cls) -> "Set[Type]":
diff --git a/gillespy2/core/model.py b/gillespy2/core/model.py
index 015a7fa61..ea3613620 100644
--- a/gillespy2/core/model.py
+++ b/gillespy2/core/model.py
@@ -15,19 +15,29 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import numpy as np
+from typing import Set, Type
+from collections import OrderedDict
+
import gillespy2
-from gillespy2.core.jsonify import TranslationTable
-from gillespy2.core.reaction import *
-from gillespy2.core.raterule import RateRule
+from gillespy2.core.assignmentrule import AssignmentRule
+from gillespy2.core.events import Event
+from gillespy2.core.functiondefinition import FunctionDefinition
from gillespy2.core.parameter import Parameter
-from gillespy2.core.species import Species
+from gillespy2.core.raterule import RateRule
from gillespy2.core.reaction import Reaction
-import numpy as np
-from gillespy2.core.results import Trajectory,Results
-from collections import OrderedDict
-from gillespy2.core.gillespyError import *
-from .gillespyError import SimulationError
-from typing import Set, Type
+from gillespy2.core.species import Species
+from gillespy2.core.timespan import TimeSpan
+from gillespy2.core.sortableobject import SortableObject
+from gillespy2.core.jsonify import Jsonify, TranslationTable
+from gillespy2.core.results import Trajectory, Results
+from gillespy2.core.gillespyError import (
+ ParameterError,
+ ModelError,
+ SimulationError,
+ StochMLImportError,
+ InvalidStochMLError
+)
try:
import lxml.etree as eTree
@@ -229,6 +239,55 @@ def decorate(header):
return print_string
+ def add(self, components):
+ """
+ Adds a component, or list of components to the model. If a list is provided, Species
+ and Parameters are added before other components. Lists may contain any combination
+ of accepted types other than lists and do not need to be in any particular order.
+
+ :param components: The component or list of components to be added the the model.
+ :type components: Species, Parameters, Reactions, Events, Rate Rules, Assignment Rules, \
+ FunctionDefinitions, and TimeSpan or list
+
+ :returns: The components that were added to the model.
+ :rtype: Species, Parameters, Reactions, Events, Rate Rules, Assignment Rules, \
+ FunctionDefinitions, and TimeSpan or list
+
+ :raises ModelError: Component is invalid.
+ """
+ if isinstance(components, list):
+ p_types = (Species, Parameter, FunctionDefinition, TimeSpan)
+ p_names = (p_type.__name__ for p_type in p_types)
+
+ others = []
+ for component in components:
+ if isinstance(component, p_types) or type(component).__name__ in p_names:
+ self.add(component)
+ else:
+ others.append(component)
+
+ for component in others:
+ self.add(component)
+ elif isinstance(components, AssignmentRule) or type(components).__name__ == AssignmentRule.__name__:
+ self.add_assignment_rule(components)
+ elif isinstance(components, Event) or type(components).__name__ == Event.__name__:
+ self.add_event(components)
+ elif isinstance(components, FunctionDefinition) or type(components).__name__ == FunctionDefinition.__name__:
+ self.add_function_definition(components)
+ elif isinstance(components, Parameter) or type(components).__name__ == Parameter.__name__:
+ self.add_parameter(components)
+ elif isinstance(components, RateRule) or type(components).__name__ == RateRule.__name__:
+ self.add_rate_rule(components)
+ elif isinstance(components, Reaction) or type(components).__name__ == Reaction.__name__:
+ self.add_reaction(components)
+ elif isinstance(components, Species) or type(components).__name__ == Species.__name__:
+ self.add_species(components)
+ elif isinstance(components, TimeSpan) or type(components).__name__ == TimeSpan.__name__:
+ self.timespan(components)
+ else:
+ raise ModelError(f"Unsupported component: {type(components)} is not a valid component.")
+ return components
+
def make_translation_table(self):
from collections import ChainMap
@@ -653,10 +712,13 @@ def timespan(self, time_span):
timespans.
:param time_span: Evenly-spaced list of times at which to sample the species populations during the simulation.
- Best to use the form np.linspace(, , )
- :type time_span: numpy ndarray
+ Best to use the form gillespy2.TimeSpan(np.linspace(, , ))
+ :type time_span: gillespy2.TimeSpan | iterator
"""
- self.tspan = time_span
+ if isinstance(time_span, TimeSpan) or type(time_span).__name__ == "TimeSpan":
+ self.tspan = time_span
+ else:
+ self.tspan = TimeSpan(time_span)
def get_reaction(self, rname):
"""
diff --git a/gillespy2/core/timespan.py b/gillespy2/core/timespan.py
new file mode 100644
index 000000000..b1be6aaa5
--- /dev/null
+++ b/gillespy2/core/timespan.py
@@ -0,0 +1,134 @@
+"""
+GillesPy2 is a modeling toolkit for biochemical simulation.
+Copyright (C) 2019-2021 GillesPy2 developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+import numpy as np
+from collections.abc import Iterator
+
+from gillespy2.core.jsonify import Jsonify
+from .gillespyError import TimespanError
+
+class TimeSpan(Iterator, Jsonify):
+ """
+ Model timespan that describes the duration to run the simulation and at which timepoint to sample
+ the species populations during the simulation.
+
+ :param items: Evenly-spaced list of times at which to sample the species populations during the simulation.
+ Best to use the form np.linspace(, , )
+ :type items: list, tuple, range, or numpy.ndarray
+
+ :raises TimespanError: items is an invalid type.
+ """
+ def __init__(self, items):
+ if isinstance(items, np.ndarray):
+ self.items = items
+ elif isinstance(items, (list, tuple, range)):
+ self.items = np.array(items)
+ else:
+ raise TimespanError("Timespan must be of type: list, tuple, range, or numpy.ndarray.")
+
+ self.validate()
+
+ def __eq__(self, o):
+ return self.items.__eq__(o).all()
+
+ def __getitem__(self, key):
+ return self.items.__getitem__(key)
+
+ def __iter__(self):
+ return self.items.__iter__()
+
+ def __len__(self):
+ return self.items.__len__()
+
+ def __next__(self):
+ return self.items.__next__()
+
+ @classmethod
+ def linspace(cls, t=20, num_points=None):
+ """
+ Creates a timespan using the form np.linspace(0, , ).
+
+ :param t: End time for the simulation.
+ :type t: int
+
+ :param num_points: Number of sample points for the species populations during the simulation.
+ :type num_points: int
+
+ :returns: Timespan for the model.
+ :rtype: gillespy2.TimeSpan
+
+ :raises TimespanError: t or num_points are None, <= 0, or invalid type.
+ """
+ if t is None or not isinstance(t, int) or t <= 0:
+ raise TimespanError("t must be a positive int.")
+ if num_points is not None and (not isinstance(num_points, int) or num_points <= 0):
+ raise TimespanError("num_points must be a positive int.")
+
+ if num_points is None:
+ num_points = int(t / 0.05) + 1
+ items = np.linspace(0, t, num_points)
+ return cls(items)
+
+ @classmethod
+ def arange(cls, increment, t=20):
+ """
+ Creates a timespan using the form np.arange(0, , ).
+
+ :param increment: Distance between sample points for the species populations during the simulation.
+ :type increment: float | int
+
+ :param t: End time for the simulation.
+ :type t: int
+
+ :returns: Timespan for the model.
+ :rtype: gillespy2.TimeSpan
+
+ :raises TimespanError: t or increment are None, <= 0, or invalid type.
+ """
+ if t is None or not isinstance(t, int) or t <= 0:
+ raise TimespanError("t must be a positive int.")
+ if not isinstance(increment, (float, int)) or increment <= 0:
+ raise TimespanError("increment must be a positive float or int.")
+
+ items = np.arange(0, t + increment, increment)
+ return cls(items)
+
+ def validate(self):
+ """
+ Validate the models time span
+
+ :raises TimespanError: Timespan is an invalid type, empty, not uniform, contains a single \
+ repeated value, or contains a negative initial time.
+ """
+ if not isinstance(self.items, np.ndarray):
+ if not isinstance(self.items, (list, tuple, range)):
+ raise TimespanError("Timespan must be of type: list, tuple, range, or numpy.ndarray.")
+ self.items = np.array(self.items)
+
+ if len(self.items) == 0:
+ raise TimespanError("Timespans must contain values.")
+ if self.items[0] < 0:
+ raise TimespanError("Simulation must run from t=0 to end time (t must always be positive).")
+
+ first_diff = self.items[1] - self.items[0]
+ other_diff = self.items[2:] - self.items[1:-1]
+ isuniform = np.isclose(other_diff, first_diff).all()
+
+ if not isuniform:
+ raise TimespanError("StochKit only supports uniform timespans.")
+ if first_diff == 0 or np.count_nonzero(other_diff) != len(other_diff):
+ raise TimespanError("Timespan can't contain a single repeating value.")
diff --git a/test/run_tests.py b/test/run_tests.py
index 2b0771be7..d5691290c 100644
--- a/test/run_tests.py
+++ b/test/run_tests.py
@@ -58,8 +58,10 @@
import test_jsonify
import test_notebooks
from unit_tests import test_model as unittest_model
+ from unit_tests import test_timespan as unittest_timespan
modules = [
+ unittest_timespan,
unittest_model,
test_empty_model,
test_build_engine,
diff --git a/test/test_model.py b/test/test_model.py
index aca54849d..83d35a7dd 100644
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -69,7 +69,7 @@ def test_model_reordered_equality(self):
def test_uniform_timespan(self):
model = Model()
model.timespan(np.linspace(0, 1, 100))
- with self.assertRaises(SimulationError):
+ with self.assertRaises(TimespanError):
model.timespan(np.array([0, 0.1, 0.5]))
model.run()
diff --git a/test/unit_tests/test_model.py b/test/unit_tests/test_model.py
index 6eaba58cc..6c18f1e18 100644
--- a/test/unit_tests/test_model.py
+++ b/test/unit_tests/test_model.py
@@ -27,6 +27,112 @@ class TestModel(unittest.TestCase):
def setUp(self):
self.model = RobustModel()
+ def test_model_add__assignment_rule(self):
+ from gillespy2 import AssignmentRule
+ ar1 = AssignmentRule(name="ar1", variable="k1", formula="29")
+ self.model.add(ar1)
+ self.assertIn("ar1", self.model.listOfAssignmentRules)
+
+ def test_model_add__event(self):
+ from gillespy2 import Event, EventTrigger, EventAssignment
+ ea = EventAssignment(name="ea", variable="k1", expression="29")
+ et = EventTrigger(expression="t > 29")
+ e2 = Event(name="e2", trigger=et, assignments=[ea])
+ self.model.add(e2)
+ self.assertIn("e2", self.model.listOfEvents)
+
+ def test_model_add__function_definition(self):
+ from gillespy2 import FunctionDefinition
+ divide = FunctionDefinition(name="divide", function="x / y", args=["x", "y"])
+ self.model.add(divide)
+ self.assertIn("divide", self.model.listOfFunctionDefinitions)
+
+ def test_model_add__invalid_component(self):
+ with self.assertRaises(ModelError):
+ self.model.add("29")
+
+ def test_model_add__parameter(self):
+ from gillespy2 import Parameter
+ k3 = Parameter(name="k3", expression=29)
+ self.model.add(k3)
+ self.assertIn("k3", self.model.listOfParameters)
+
+ def test_model_add__rate_rule(self):
+ from gillespy2 import RateRule
+ rr3 = RateRule(name="rr3", variable="k1", formula="29")
+ self.model.add(rr3)
+ self.assertIn("rr3", self.model.listOfRateRules)
+
+ def test_model_add__reaction(self):
+ from gillespy2 import Reaction
+ r4 = Reaction(name="r4", reactants={"s1": 1}, products={"s2": 1}, rate="k1")
+ self.model.add(r4)
+ self.assertIn("r4", self.model.listOfReactions)
+
+ def test_model_add__species(self):
+ from gillespy2 import Species
+ s3 = Species(name="s3", initial_value=29)
+ self.model.add(s3)
+ self.assertIn("s3", self.model.listOfSpecies)
+
+ def test_model_add__timespan(self):
+ from gillespy2 import TimeSpan
+ tspan = TimeSpan(range(100))
+ self.model.add(tspan)
+ self.assertEqual(tspan, self.model.tspan)
+
+ def test_model_add__multiple_components__in_order(self):
+ import gillespy2
+
+ s1 = gillespy2.Species(name="s1", initial_value=29)
+ k1 = gillespy2.Parameter(name="k1", expression=29)
+ r1 = gillespy2.Reaction(name="r1", reactants={"s1": 1}, rate="k1")
+ rr1 = gillespy2.RateRule(name="rr1", variable="k1", formula="29")
+ ar1 = gillespy2.AssignmentRule(name="ar1", variable="s1", formula="29")
+ ea = gillespy2.EventAssignment(name="ea", variable="k1", expression="29")
+ et = gillespy2.EventTrigger(expression="t > 29")
+ e1 = gillespy2.Event(name="e1", trigger=et, assignments=[ea])
+ divide = gillespy2.FunctionDefinition(name="divide", function="x / y", args=["x", "y"])
+ tspan = gillespy2.TimeSpan(range(100))
+
+ model = gillespy2.Model(name="Test Model")
+ model.add([s1, k1, r1, rr1, ar1, e1, divide, tspan])
+
+ self.assertIn("ar1", model.listOfAssignmentRules)
+ self.assertIn("e1", model.listOfEvents)
+ self.assertIn("divide", model.listOfFunctionDefinitions)
+ self.assertIn("k1", model.listOfParameters)
+ self.assertIn("rr1", model.listOfRateRules)
+ self.assertIn("r1", model.listOfReactions)
+ self.assertIn("s1", model.listOfSpecies)
+ self.assertEqual(tspan, model.tspan)
+
+ def test_model_add__multiple_components__in_order(self):
+ import gillespy2
+
+ s1 = gillespy2.Species(name="s1", initial_value=29)
+ k1 = gillespy2.Parameter(name="k1", expression=29)
+ r1 = gillespy2.Reaction(name="r1", reactants={"s1": 1}, rate="k1")
+ rr1 = gillespy2.RateRule(name="rr1", variable="k1", formula="29")
+ ar1 = gillespy2.AssignmentRule(name="ar1", variable="s1", formula="29")
+ ea = gillespy2.EventAssignment(name="ea", variable="k1", expression="29")
+ et = gillespy2.EventTrigger(expression="t > 29")
+ e1 = gillespy2.Event(name="e1", trigger=et, assignments=[ea])
+ divide = gillespy2.FunctionDefinition(name="divide", function="x / y", args=["x", "y"])
+ tspan = gillespy2.TimeSpan(range(100))
+
+ model = gillespy2.Model(name="Test Model")
+ model.add([ar1, divide, e1, k1, s1, r1, rr1, tspan])
+
+ self.assertIn("ar1", model.listOfAssignmentRules)
+ self.assertIn("e1", model.listOfEvents)
+ self.assertIn("divide", model.listOfFunctionDefinitions)
+ self.assertIn("k1", model.listOfParameters)
+ self.assertIn("rr1", model.listOfRateRules)
+ self.assertIn("r1", model.listOfReactions)
+ self.assertIn("s1", model.listOfSpecies)
+ self.assertEqual(tspan, model.tspan)
+
def test_delete_assignment_rule(self):
self.model.delete_assignment_rule('rr2')
self.assertNotIn('rr2', self.model.listOfAssignmentRules)
diff --git a/test/unit_tests/test_timespan.py b/test/unit_tests/test_timespan.py
new file mode 100644
index 000000000..2cb61e4e0
--- /dev/null
+++ b/test/unit_tests/test_timespan.py
@@ -0,0 +1,214 @@
+"""
+GillesPy2 is a modeling toolkit for biochemical simulation.
+Copyright (C) 2019-2021 GillesPy2 developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+import numpy
+import unittest
+
+from gillespy2.core.timespan import TimeSpan
+from gillespy2.core.gillespyError import TimespanError
+
+class TestTimeSpan(unittest.TestCase):
+ '''
+ ################################################################################################
+ Unit tests for gillespy2.TimeSpan.
+ ################################################################################################
+ '''
+ def test_constructor(self):
+ """ Test the TimeSpan constructor. """
+ test_tspan = numpy.linspace(0, 20, 401)
+ tspan = TimeSpan(test_tspan)
+ self.assertEqual(tspan, test_tspan)
+
+ def test_constructor__list(self):
+ """ Test the TimeSpan constructor with list data structure. """
+ test_tspan = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tspan = TimeSpan(test_tspan)
+ self.assertEqual(tspan, numpy.array(test_tspan))
+
+ def test_constructor__tuple(self):
+ """ Test the TimeSpan constructor with tuple data structure. """
+ test_tspan = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ tspan = TimeSpan(test_tspan)
+ self.assertEqual(tspan, numpy.array(test_tspan))
+
+ def test_constructor__range(self):
+ """ Test the TimeSpan constructor with range data structure. """
+ test_tspan = range(11)
+ tspan = TimeSpan(test_tspan)
+ self.assertEqual(tspan, numpy.array(test_tspan))
+
+ def test_constructor__invalid_type(self):
+ """ Test the TimeSpan constructor with an invalid data structure type. """
+ test_tspan = set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ with self.assertRaises(TimespanError):
+ TimeSpan(test_tspan)
+
+ def test_linspace(self):
+ """ Test TimeSpan.linspace. """
+ tspan = TimeSpan.linspace(t=30, num_points=301)
+ self.assertEqual(tspan, numpy.linspace(0, 30, 301))
+
+ def test_linspace__no_t(self):
+ """ Test TimeSpan.linspace without passing t. """
+ tspan = TimeSpan.linspace(num_points=201)
+ self.assertEqual(tspan, numpy.linspace(0, 20, 201))
+
+ def test_linspace__no_num_points(self):
+ """ Test TimeSpan.linspace without passing num_points. """
+ tspan = TimeSpan.linspace(t=30)
+ self.assertEqual(tspan, numpy.linspace(0, 30, int(30 / 0.05) + 1))
+
+ def test_linspace__no_args(self):
+ """ Test TimeSpan.linspace without passing any args. """
+ tspan = TimeSpan.linspace()
+ self.assertEqual(tspan, numpy.linspace(0, 20, 401))
+
+ def test_linspace__t_less_than_1(self):
+ """ Test TimeSpan.linspace with t<1. """
+ test_values = [0, -1, -2, -5, -10]
+ for test_val in test_values:
+ with self.subTest(t=test_val):
+ with self.assertRaises(TimespanError):
+ tspan = TimeSpan.linspace(t=test_val, num_points=301)
+
+ def test_linspace__num_points_less_than_1(self):
+ """ Test TimeSpan.linspace with num_points<1. """
+ test_values = [0, -1, -2, -5, -10]
+ for test_val in test_values:
+ with self.subTest(num_points=test_val):
+ with self.assertRaises(TimespanError):
+ tspan = TimeSpan.linspace(t=30, num_points=test_val)
+
+ def test_linspace__t_is_none(self):
+ """ Test TimeSpan.linspace with t=None. """
+ with self.assertRaises(TimespanError):
+ tspan = TimeSpan.linspace(t=None, num_points=401)
+
+ def test_linspace__invalid_t_type(self):
+ """ Test TimeSpan.linspace with invalid t type. """
+ with self.assertRaises(TimespanError):
+ tspan = TimeSpan.linspace(t=20.5, num_points=401)
+
+ def test_linspace__invalid_num_points_type(self):
+ """ Test TimeSpan.linspace with invalid num_points type. """
+ with self.assertRaises(TimespanError):
+ tspan = TimeSpan.linspace(t=20, num_points=40.1)
+
+ def test_arange(self):
+ """ Test TimeSpan.arange. """
+ tspan = TimeSpan.arange(0.1, t=30)
+ self.assertEqual(tspan, numpy.arange(0, 30.1, 0.1))
+
+ def test_arange__no_t(self):
+ """ Test TimeSpan.arange. """
+ tspan = TimeSpan.arange(0.1)
+ self.assertEqual(tspan, numpy.arange(0, 20.1, 0.1))
+
+ def test_arange__t_less_than_1(self):
+ """ Test TimeSpan.arange with t<1. """
+ test_values = [0, -1, -2, -5, -10]
+ for test_val in test_values:
+ with self.subTest(t=test_val):
+ with self.assertRaises(TimespanError):
+ tspan = TimeSpan.arange(0.1, t=test_val)
+
+ def test_arange__num_points_less_than_1(self):
+ """ Test TimeSpan.arange with increment<1. """
+ test_values = [0, -1, -2, -5, -10]
+ for test_val in test_values:
+ with self.subTest(imcrement=test_val):
+ with self.assertRaises(TimespanError):
+ tspan = TimeSpan.arange(test_val, t=30)
+
+ def test_arange__t_is_none(self):
+ """ Test TimeSpan.arange with t=None. """
+ with self.assertRaises(TimespanError):
+ tspan = TimeSpan.arange(0.1, t=None)
+
+ def test_arange__invalid_t_type(self):
+ """ Test TimeSpan.arange with invalid t type. """
+ with self.assertRaises(TimespanError):
+ tspan = TimeSpan.arange(0.05, t=20.5)
+
+ def test_arange__invalid_increment(self):
+ """ Test TimeSpan.arange with invalid increment type. """
+ with self.assertRaises(TimespanError):
+ tspan = TimeSpan.arange("0.05", t=20)
+
+ def test_validate__list(self):
+ """ Test TimeSpan.validate with list data structure. """
+ test_tspan = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tspan = TimeSpan(test_tspan)
+ tspan.items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tspan.validate()
+ self.assertEqual(tspan, numpy.array(test_tspan))
+
+ def test_validate__tuple(self):
+ """ Test TimeSpan.validate with tuple data structure. """
+ test_tspan = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ tspan = TimeSpan(test_tspan)
+ tspan.items = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ tspan.validate()
+ self.assertEqual(tspan, numpy.array(test_tspan))
+
+ def test_validate__range(self):
+ """ Test TimeSpan.validate with range data structure. """
+ test_tspan = range(11)
+ tspan = TimeSpan(test_tspan)
+ tspan.items = range(11)
+ tspan.validate()
+ self.assertEqual(tspan, numpy.array(test_tspan))
+
+ def test_validate__invalid_type(self):
+ """ Test TimeSpan.validate with an invalid data structure type. """
+ test_tspan = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tspan = TimeSpan(test_tspan)
+ with self.assertRaises(TimespanError):
+ tspan.items = set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ tspan.validate()
+
+ def test_validate__empty_timespan(self):
+ """ Test TimeSpan.validate with an empty data structure. """
+ test_tspan = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tspan = TimeSpan(test_tspan)
+ with self.assertRaises(TimespanError):
+ tspan.items = []
+ tspan.validate()
+
+ def test_validate__all_same_values(self):
+ """ Test TimeSpan.validate with an empty data structure. """
+ test_tspan = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tspan = TimeSpan(test_tspan)
+ with self.assertRaises(TimespanError):
+ tspan.items = [2, 2, 2, 2, 2, 2, 2, 2, 2]
+ tspan.validate()
+
+ def test_validate__negative_start(self):
+ """ Test TimeSpan.validate with an initial time < 0. """
+ test_tspan = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tspan = TimeSpan(test_tspan)
+ with self.assertRaises(TimespanError):
+ tspan.items = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tspan.validate()
+
+ def test_validate__non_uniform_timespan(self):
+ """ Test TimeSpan.validate with a non-uniform timespan. """
+ test_tspan = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tspan = TimeSpan(test_tspan)
+ with self.assertRaises(TimespanError):
+ tspan.items = [2, 1, 3, 4, 5, 6, 7, 8, 9, 10]
+ tspan.validate()