def test_validate(self): # Test some edge cases for validation. # Test scope rule: # Variables are allowed access all children of their ancestors m = myokit.Model() c = m.add_component('c') p = c.add_variable('p') p11 = p.add_variable('p11') p12 = p.add_variable('p12') p121 = p12.add_variable('p121') p121.set_rhs('p + p11 + p12') p121.validate() # But not children of children of ancestors p112 = p11.add_variable('p112') p121.set_rhs(p112.lhs()) self.assertRaises(myokit.IllegalReferenceError, p121.validate) # RHS cannot be a partial, init, or condition m = myokit.Model() c = m.add_component('c') p = c.add_variable('p') p.set_rhs(3) q = c.add_variable('q') q.set_rhs(2) p.validate() p.set_rhs(myokit.PartialDerivative(p.lhs(), q.lhs())) self.assertRaisesRegex( myokit.IntegrityError, 'Partial derivatives', p.validate) p.promote(1) p.set_rhs(myokit.InitialValue(myokit.Name(p))) self.assertRaisesRegex( myokit.IntegrityError, 'Initial value', p.validate) p.set_rhs('q == 3') self.assertRaisesRegex( myokit.IntegrityError, 'can not be a condition', p.validate)
def test_sensitivities(self): # Test instantiation of cmodel with sensitivities # Bad type sens = 'Bad type' with self.assertRaisesRegex(ValueError, 'The argument `sensitivities'): myokit.CModel(self.model, sens) # Empty deps or indeps sens = ([], ['some parameter']) m = myokit.CModel(self.model, sens) self.assertFalse(m.has_sensitivities) sens = (['some state'], []) m = myokit.CModel(self.model, sens) self.assertFalse(m.has_sensitivities) # Provide sensitivies as Variables s1 = self.model.get('ik1.gK1') s2 = self.model.get('ikp.IKp') p1 = self.model.get('cell.K_o') p2 = self.model.get('ikp.gKp') sens = ([s1, s2], [p1, p2]) m = myokit.CModel(self.model, sens) self.assertTrue(m.has_sensitivities) # Provide sensitivities as Names sens = ([myokit.Name(s1), myokit.Name(s2)], [myokit.Name(p1), myokit.Name(p2)]) m = myokit.CModel(self.model, sens) self.assertTrue(m.has_sensitivities) # Sensitivity of derivative s3 = self.model.get('ik.x') sens = ([myokit.Derivative(myokit.Name(s3)), myokit.Name(s2)], [myokit.Name(p1), myokit.Name(p2)]) m = myokit.CModel(self.model, sens) self.assertTrue(m.has_sensitivities) s3 = 'dot(ik.x)' sens = ([s3, myokit.Name(s2)], [myokit.Name(p1), myokit.Name(p2)]) m = myokit.CModel(self.model, sens) self.assertTrue(m.has_sensitivities) # Sensitivity of derivative of non-state sens = ([myokit.Derivative(myokit.Name(s1)), myokit.Name(s2)], [myokit.Name(p1), myokit.Name(p2)]) with self.assertRaisesRegex(ValueError, 'Sensitivity of '): myokit.CModel(self.model, sens) # Sensitivity of bound variable sens = (['engine.time', myokit.Name(s2)], [myokit.Name(p1), myokit.Name(p2)]) with self.assertRaisesRegex(ValueError, 'Sensitivities cannot'): myokit.CModel(self.model, sens) # Sensitivity w.r.t. Initial value s3 = self.model.get('ik.x') sens = ([s3, myokit.Name(s2)], [myokit.Name(p1), myokit.InitialValue(myokit.Name(s3))]) m = myokit.CModel(self.model, sens) self.assertTrue(m.has_sensitivities) # Sensitivity w.r.t. initial value of non-state sens = ([myokit.Name(s1), myokit.Name(s2)], [myokit.Name(p1), myokit.InitialValue(myokit.Name(p2))]) with self.assertRaisesRegex(ValueError, 'Sensitivity with respect to'): myokit.CModel(self.model, sens) # Sensitivity w.r.t. non-literal sens = ([myokit.Name(s1), myokit.Name(s2)], [myokit.Name(p1), 'ik.E']) with self.assertRaisesRegex(ValueError, 'Sensitivity with respect to'): myokit.CModel(self.model, sens)
def _parse_sensitivities(self, model, sensitivities): """ Parses the ``sensitivities`` constructor argument and returns a tuple ``(has_sensitivities, dependents, independents)``, where ``has_sensitivities`` is a boolean and the other two entries are lists containing :class:`myokit.Expression` objects. Acceptable input for dependents (y in dy/dx): - Variable (state or intermediary) - Name or Derivative - "ina.INa" or "dot(membrane.V)" Acceptable input for independents (x in dy/dx): - Variable (literal) - Name or InitialValue - "ikr.gKr" or "init(membrane.V)" The resulting lists contain Expression objects. """ if sensitivities is None: return False, [], [] # Get lists try: deps, indeps = sensitivities deps = list(deps) indeps = list(indeps) except Exception: raise ValueError( 'The argument `sensitivities` must be None, or a tuple' ' containing two lists.') if len(deps) == 0 or len(indeps) == 0: return False, [], [] # Create output lists dependents = [] independents = [] # Check dependents, make sure all are Name or Derivative objects from # the cloned model. for x in deps: deriv = False if isinstance(x, myokit.Variable): var = model.get(x.qname()) elif isinstance(x, myokit.Name): var = model.get(x.var().qname()) elif isinstance(x, myokit.Derivative): deriv = True var = model.get(x.var().qname()) else: x = str(x) if x[:4] == 'dot(' and x[-1:] == ')': deriv = True var = x = x[4:-1] var = model.get(x) lhs = myokit.Name(var) if deriv: lhs = myokit.Derivative(lhs) if not var.is_state(): raise ValueError('Sensitivity of ' + lhs.code() + ' requested, but ' + var.qname() + ' is not a state variable.') elif var.is_bound(): raise ValueError('Sensitivities cannot be calculated for bound' ' variables (got ' + str(var.qname()) + ').') # Note: constants are fine, just not very useful! But may be # easy, e.g. when working with multiple models. dependents.append(lhs) # Check independents, make sure all are Name or InitialValue # objects from the cloned model. for x in indeps: init = False if isinstance(x, myokit.Variable): var = model.get(x.qname()) elif isinstance(x, myokit.Name): var = model.get(x.var().qname()) elif isinstance(x, myokit.InitialValue): init = True var = model.get(x.var().qname()) else: x = str(x) if x[:5] == 'init(' and x[-1:] == ')': init = True x = x[5:-1] var = model.get(x) lhs = myokit.Name(var) if init: lhs = myokit.InitialValue(myokit.Name(var)) if not var.is_state(): raise ValueError('Sensitivity with respect to ' + lhs.code() + ' requested, but ' + var.qname() + ' is not a' ' state variable.') elif not var.is_literal(): raise ValueError( 'Sensitivity with respect to ' + var.qname() + ' requested, but this is not a literal variable (it' ' depends on other variables).') independents.append(lhs) return True, dependents, independents
def test_sensitivities_with_derivatives(self): # Test various inputs to the `sensitivities` argument are handled # correctly, including sensitivites of derivatives and intermediary # variables depending on derivatives. # Note: passing the wrong values into the sensitivities constructor arg # is tested in the CModel tests. m = myokit.parse_model(''' [[model]] e.x = 1.2 e.y = 2.3 [e] t = 0 bind time p = 1 / 100 q = 2 r = 3 dot(x) = p * q dot(y) = 2 * p - y fx = x^2 fy = r + p^2 + dot(y) ''') m.validate() x0 = m.get('e.x').state_value() y0 = m.get('e.y').state_value() p = m.get('e.p').eval() q = m.get('e.q').eval() r = m.get('e.r').eval() # Dependents is a list of y in dy/dx. # Must refer to state, derivative, or intermediary, given as: # - Variable # - Name # - Derivative # - "qname" # - "dot(qname)" dependents = [ 'e.x', # qname 'e.y', # qname 'dot(e.x)', # dot(qname) m.get('e.y').lhs(), # Derivative m.get('e.fx'), # Variable m.get('e.fy').lhs(), # Name ] # Independents is a list of x in dy/dx # Must refer to literals or initial values, given as: # - Variable # - Name # - InitialValue # - "qname" # - "init(qname)" independents = [ 'e.p', # qname m.get('e.q'), # qname m.get('e.r').lhs(), # Name 'init(e.x)', # init(qname) myokit.InitialValue(myokit.Name(m.get('e.y'))), # InitialValue ] # Run, get output s = myokit.Simulation(m, sensitivities=(dependents, independents)) s.set_tolerance(1e-9, 1e-9) d, e = s.run(8) d, e = d.npview(), np.array(e) t = d.time() # Check solution self.assertTrue(np.allclose(d['e.x'], x0 + p * q * t)) self.assertTrue( np.allclose(d['e.y'], 2 * p + (y0 - 2 * p) * np.exp(-t))) self.assertTrue( np.allclose(d['e.fx'], (p * q * t)**2 + 2 * p * q * t * x0 + x0**2)) self.assertTrue( np.allclose(d['e.fy'], r + p**2 + (2 * p - y0) * np.exp(-t))) self.assertTrue(np.allclose(d['dot(e.x)'], p * q)) self.assertTrue(np.allclose(d['dot(e.y)'], (2 * p - y0) * np.exp(-t))) # Sensitivities of x to (p, q, r, x0, y0) self.assertTrue(np.allclose(e[:, 0, 0], q * t)) self.assertTrue(np.allclose(e[:, 0, 1], p * t)) self.assertTrue(np.allclose(e[:, 0, 2], 0)) self.assertTrue(np.allclose(e[:, 0, 3], 1)) self.assertTrue(np.allclose(e[:, 0, 4], 0)) # Sensitivities of y to (p, q, r, x0, y0) self.assertTrue(np.allclose(e[:, 1, 0], 2 - 2 * np.exp(-t))) self.assertTrue(np.allclose(e[:, 1, 1], 0)) self.assertTrue(np.allclose(e[:, 1, 2], 0)) self.assertTrue(np.allclose(e[:, 1, 3], 0)) self.assertTrue(np.allclose(e[:, 1, 4], np.exp(-t))) # Sensitivities of dot(x) to (p, q, r, x0, y0) self.assertTrue(np.allclose(e[:, 2, 0], q)) self.assertTrue(np.allclose(e[:, 2, 1], p)) self.assertTrue(np.allclose(e[:, 2, 2], 0)) self.assertTrue(np.allclose(e[:, 2, 3], 0)) self.assertTrue(np.allclose(e[:, 2, 4], 0)) # Sensitivities of dot(y) to (p, q, r, x0, y0) self.assertTrue(np.allclose(e[:, 3, 0], 2 * np.exp(-t))) self.assertTrue(np.allclose(e[:, 3, 1], 0)) self.assertTrue(np.allclose(e[:, 3, 2], 0)) self.assertTrue(np.allclose(e[:, 3, 3], 0)) self.assertTrue(np.allclose(e[:, 3, 4], -np.exp(-t))) # Sensitivities of fx to (p, q, r, x0, y0) self.assertTrue( np.allclose(e[:, 4, 0], 2 * p * (q * t)**2 + 2 * q * t * x0)) self.assertTrue( np.allclose(e[:, 4, 1], 2 * q * (p * t)**2 + 2 * p * t * x0)) self.assertTrue(np.allclose(e[:, 4, 2], 0)) self.assertTrue(np.allclose(e[:, 4, 3], 2 * p * q * t + 2 * x0)) self.assertTrue(np.allclose(e[:, 4, 4], 0)) # Sensitivities of fy to (p, q, r, x0, y0) self.assertTrue(np.allclose(e[:, 5, 0], 2 * p + 2 * np.exp(-t))) self.assertTrue(np.allclose(e[:, 5, 1], 0)) self.assertTrue(np.allclose(e[:, 5, 2], 1)) self.assertTrue(np.allclose(e[:, 5, 3], 0)) self.assertTrue(np.allclose(e[:, 5, 4], -np.exp(-t)))
def test_all(self): w = myokit.formats.python.PythonExpressionWriter() model = myokit.Model() component = model.add_component('c') avar = component.add_variable('a') # Name a = myokit.Name(avar) self.assertEqual(w.ex(a), 'c.a') # Derivative x = myokit.Derivative(a) self.assertEqual(w.ex(x), 'dot(c.a)') # Partial derivative x = myokit.PartialDerivative(a, a) self.assertEqual(w.ex(x), 'diff(c.a, c.a)') # Initial value x = myokit.InitialValue(a) self.assertEqual(w.ex(x), 'init(c.a)') # Number b = myokit.Number(3) self.assertEqual(w.ex(b), '3.0') # Number with unit b = myokit.Number(12, 'pF') self.assertEqual(w.ex(b), '12.0') # Prefix plus x = myokit.PrefixPlus(b) self.assertEqual(w.ex(x), '12.0') # Prefix minus x = myokit.PrefixMinus(b) self.assertEqual(w.ex(x), '(-12.0)') # Plus x = myokit.Plus(a, b) self.assertEqual(w.ex(x), 'c.a + 12.0') # Minus x = myokit.Minus(a, b) self.assertEqual(w.ex(x), 'c.a - 12.0') # Multiply x = myokit.Multiply(a, b) self.assertEqual(w.ex(x), 'c.a * 12.0') # Divide x = myokit.Divide(a, b) self.assertEqual(w.ex(x), 'c.a / 12.0') # Quotient x = myokit.Quotient(a, b) self.assertEqual(w.ex(x), 'c.a // 12.0') # Remainder x = myokit.Remainder(a, b) self.assertEqual(w.ex(x), 'c.a % 12.0') # Power x = myokit.Power(a, b) self.assertEqual(w.ex(x), 'c.a ** 12.0') # Sqrt x = myokit.Sqrt(b) self.assertEqual(w.ex(x), 'math.sqrt(12.0)') # Exp x = myokit.Exp(a) self.assertEqual(w.ex(x), 'math.exp(c.a)') # Log(a) x = myokit.Log(b) self.assertEqual(w.ex(x), 'math.log(12.0)') # Log(a, b) x = myokit.Log(a, b) self.assertEqual(w.ex(x), 'math.log(c.a, 12.0)') # Log10 x = myokit.Log10(b) self.assertEqual(w.ex(x), 'math.log10(12.0)') # Sin x = myokit.Sin(b) self.assertEqual(w.ex(x), 'math.sin(12.0)') # Cos x = myokit.Cos(b) self.assertEqual(w.ex(x), 'math.cos(12.0)') # Tan x = myokit.Tan(b) self.assertEqual(w.ex(x), 'math.tan(12.0)') # ASin x = myokit.ASin(b) self.assertEqual(w.ex(x), 'math.asin(12.0)') # ACos x = myokit.ACos(b) self.assertEqual(w.ex(x), 'math.acos(12.0)') # ATan x = myokit.ATan(b) self.assertEqual(w.ex(x), 'math.atan(12.0)') # Floor x = myokit.Floor(b) self.assertEqual(w.ex(x), 'math.floor(12.0)') # Ceil x = myokit.Ceil(b) self.assertEqual(w.ex(x), 'math.ceil(12.0)') # Abs x = myokit.Abs(b) self.assertEqual(w.ex(x), 'abs(12.0)') # Equal x = myokit.Equal(a, b) self.assertEqual(w.ex(x), '(c.a == 12.0)') # NotEqual x = myokit.NotEqual(a, b) self.assertEqual(w.ex(x), '(c.a != 12.0)') # More x = myokit.More(a, b) self.assertEqual(w.ex(x), '(c.a > 12.0)') # Less x = myokit.Less(a, b) self.assertEqual(w.ex(x), '(c.a < 12.0)') # MoreEqual x = myokit.MoreEqual(a, b) self.assertEqual(w.ex(x), '(c.a >= 12.0)') # LessEqual x = myokit.LessEqual(a, b) self.assertEqual(w.ex(x), '(c.a <= 12.0)') # Not cond1 = myokit.parse_expression('5 > 3') cond2 = myokit.parse_expression('2 < 1') x = myokit.Not(cond1) self.assertEqual(w.ex(x), 'not ((5.0 > 3.0))') # And x = myokit.And(cond1, cond2) self.assertEqual(w.ex(x), '((5.0 > 3.0) and (2.0 < 1.0))') # Or x = myokit.Or(cond1, cond2) self.assertEqual(w.ex(x), '((5.0 > 3.0) or (2.0 < 1.0))') # If x = myokit.If(cond1, a, b) self.assertEqual(w.ex(x), '(c.a if (5.0 > 3.0) else 12.0)') # Piecewise c = myokit.Number(1) x = myokit.Piecewise(cond1, a, cond2, b, c) self.assertEqual( w.ex(x), '(c.a if (5.0 > 3.0) else (12.0 if (2.0 < 1.0) else 1.0))') # Test fetching using ewriter method w = myokit.formats.ewriter('python') self.assertIsInstance(w, myokit.formats.python.PythonExpressionWriter) # Test lhs method w.set_lhs_function(lambda x: 'sheep') self.assertEqual(w.ex(a), 'sheep') # Test without a Myokit expression self.assertRaisesRegex( ValueError, 'Unknown expression type', w.ex, 7)