def test_split_stochastic(): tau = 5 * ms expr = Expression('(-v + I) / tau') # No stochastic part assert expr.split_stochastic() == (expr, None) # No non-stochastic part -- note that it should return 0 and not None expr = Expression('sigma*xi/tau**.5') non_stochastic, stochastic = expr.split_stochastic() assert sympy_equals(non_stochastic.code, 0) assert 'xi' in stochastic assert len(stochastic) == 1 assert sympy_equals(stochastic['xi'].code, 'sigma/tau**.5') expr = Expression('(-v + I) / tau + sigma*xi/tau**.5') non_stochastic, stochastic = expr.split_stochastic() assert 'xi' in stochastic assert len(stochastic) == 1 assert sympy_equals(non_stochastic.code, '(-v + I) / tau') assert sympy_equals(stochastic['xi'].code, 'sigma/tau**.5') expr = Expression( '(-v + I) / tau + sigma*xi_1/tau**.5 + xi_2*sigma2/sqrt(tau_2)') non_stochastic, stochastic = expr.split_stochastic() assert set(stochastic.keys()) == {'xi_1', 'xi_2'} assert sympy_equals(non_stochastic.code, '(-v + I) / tau') assert sympy_equals(stochastic['xi_1'].code, 'sigma/tau**.5') assert sympy_equals(stochastic['xi_2'].code, 'sigma2/tau_2**.5') expr = Expression('-v / tau + 1 / xi') with pytest.raises(ValueError): expr.split_stochastic()
def test_construction_errors(): ''' Test that the Equations constructor raises errors correctly ''' # parse error assert_raises(EquationError, lambda: Equations('dv/dt = -v / tau volt')) # Only a single string or a list of SingleEquation objects is allowed assert_raises(TypeError, lambda: Equations(None)) assert_raises(TypeError, lambda: Equations(42)) assert_raises(TypeError, lambda: Equations(['dv/dt = -v / tau : volt'])) # duplicate variable names assert_raises(EquationError, lambda: Equations('''dv/dt = -v / tau : volt v = 2 * t/second * volt : volt''')) eqs = [SingleEquation(DIFFERENTIAL_EQUATION, 'v', volt, expr=Expression('-v / tau')), SingleEquation(SUBEXPRESSION, 'v', volt, expr=Expression('2 * t/second * volt')) ] assert_raises(EquationError, lambda: Equations(eqs)) # illegal variable names assert_raises(ValueError, lambda: Equations('ddt/dt = -dt / tau : volt')) assert_raises(ValueError, lambda: Equations('dt/dt = -t / tau : volt')) assert_raises(ValueError, lambda: Equations('dxi/dt = -xi / tau : volt')) assert_raises(ValueError, lambda: Equations('for : volt')) assert_raises((EquationError, ValueError), lambda: Equations('d1a/dt = -1a / tau : volt')) assert_raises(ValueError, lambda: Equations('d_x/dt = -_x / tau : volt')) # xi in a subexpression assert_raises(EquationError, lambda: Equations('''dv/dt = -(v + I) / (5 * ms) : volt I = second**-1*xi**-2*volt : volt''')) # more than one xi assert_raises(EquationError, lambda: Equations('''dv/dt = -v / tau + xi/tau**.5 : volt dx/dt = -x / tau + 2*xi/tau : volt tau : second''')) # using not-allowed flags eqs = Equations('dv/dt = -v / (5 * ms) : volt (flag)') eqs.check_flags({DIFFERENTIAL_EQUATION: ['flag']}) # allow this flag assert_raises(ValueError, lambda: eqs.check_flags({DIFFERENTIAL_EQUATION: []})) assert_raises(ValueError, lambda: eqs.check_flags({})) assert_raises(ValueError, lambda: eqs.check_flags({SUBEXPRESSION: ['flag']})) assert_raises(ValueError, lambda: eqs.check_flags({DIFFERENTIAL_EQUATION: ['otherflag']})) # Circular subexpression assert_raises(ValueError, lambda: Equations('''dv/dt = -(v + w) / (10 * ms) : 1 w = 2 * x : 1 x = 3 * w : 1''')) # Boolean/integer differential equations assert_raises(TypeError, lambda: Equations('dv/dt = -v / (10*ms) : boolean')) assert_raises(TypeError, lambda: Equations('dv/dt = -v / (10*ms) : integer'))
def test_repeated_construction(): eqs1 = Equations('dx/dt = x : 1') eqs2 = Equations('dx/dt = x : 1', x='y') assert len(eqs1) == 1 assert 'x' in eqs1 assert eqs1['x'].expr == Expression('x') assert len(eqs2) == 1 assert 'y' in eqs2 assert eqs2['y'].expr == Expression('y')
def test_expr_creation(): ''' Test creating expressions. ''' expr = Expression('v > 5 * mV') assert expr.code == 'v > 5 * mV' assert ('v' in expr.identifiers and 'mV' in expr.identifiers and not 'V' in expr.identifiers) assert_raises(SyntaxError, lambda: Expression('v 5 * mV'))
def test_expr_creation(): """ Test creating expressions. """ expr = Expression('v > 5 * mV') assert expr.code == 'v > 5 * mV' assert ('v' in expr.identifiers and 'mV' in expr.identifiers and not 'V' in expr.identifiers) with pytest.raises(SyntaxError): Expression('v 5 * mV')
def test_substitute(): # Check that Equations.substitute returns an independent copy eqs = Equations('dx/dt = x : 1') eqs2 = eqs.substitute(x='y') # First equation should be unaffected assert len(eqs) == 1 and 'x' in eqs assert eqs['x'].expr == Expression('x') # Second equation should have x substituted by y assert len(eqs2) == 1 and 'y' in eqs2 assert eqs2['y'].expr == Expression('y')
def test_split_stochastic(): tau = 5 * ms expr = Expression('(-v + I) / tau') # No stochastic part assert expr.split_stochastic() == (expr, None) expr = Expression('(-v + I) / tau + sigma*xi/tau**.5') non_stochastic, stochastic = expr.split_stochastic() assert 'xi' in stochastic assert len(stochastic) == 1 assert sympy_equals(non_stochastic.code, '(-v + I) / tau') assert sympy_equals(stochastic['xi'].code, 'sigma/tau**.5') expr = Expression('(-v + I) / tau + sigma*xi_1/tau**.5 + xi_2*sigma2/sqrt(tau_2)') non_stochastic, stochastic = expr.split_stochastic() assert set(stochastic.keys()) == {'xi_1', 'xi_2'} assert sympy_equals(non_stochastic.code, '(-v + I) / tau') assert sympy_equals(stochastic['xi_1'].code, 'sigma/tau**.5') assert sympy_equals(stochastic['xi_2'].code, 'sigma2/tau_2**.5') expr = Expression('-v / tau + 1 / xi') assert_raises(ValueError, expr.split_stochastic)
def test_str_repr(): """ Test the string representation of expressions and statements. Assumes that __str__ returns the complete expression/statement string and __repr__ a string of the form "Expression(...)" or "Statements(...)" that can be evaluated. """ expr_string = '(v - I)/ tau' expr = Expression(expr_string) # use sympy to check for equivalence of expressions (terms may have be # re-arranged by sympy) assert sympy_equals(expr_string, str(expr)) assert sympy_equals(expr_string, eval(repr(expr)).code) # Use exact string equivalence for statements statement_string = 'v += w' statement = Statements(statement_string) assert str(statement) == 'v += w' assert repr(statement) == "Statements('v += w')"
def test_construction_errors(): """ Test that the Equations constructor raises errors correctly """ # parse error with pytest.raises(EquationError): Equations('dv/dt = -v / tau volt') with pytest.raises(EquationError): Equations('dv/dt = -v / tau : volt second') # incorrect unit definition with pytest.raises(EquationError): Equations('dv/dt = -v / tau : mvolt') with pytest.raises(EquationError): Equations('dv/dt = -v / tau : voltage') with pytest.raises(EquationError): Equations('dv/dt = -v / tau : 1.0*volt') # Only a single string or a list of SingleEquation objects is allowed with pytest.raises(TypeError): Equations(None) with pytest.raises(TypeError): Equations(42) with pytest.raises(TypeError): Equations(['dv/dt = -v / tau : volt']) # duplicate variable names with pytest.raises(EquationError): Equations("""dv/dt = -v / tau : volt v = 2 * t/second * volt : volt""") eqs = [ SingleEquation(DIFFERENTIAL_EQUATION, 'v', volt.dim, expr=Expression('-v / tau')), SingleEquation(SUBEXPRESSION, 'v', volt.dim, expr=Expression('2 * t/second * volt')) ] with pytest.raises(EquationError): Equations(eqs) # illegal variable names with pytest.raises(SyntaxError): Equations('ddt/dt = -dt / tau : volt') with pytest.raises(SyntaxError): Equations('dt/dt = -t / tau : volt') with pytest.raises(SyntaxError): Equations('dxi/dt = -xi / tau : volt') with pytest.raises(SyntaxError): Equations('for : volt') with pytest.raises((EquationError, SyntaxError)): Equations('d1a/dt = -1a / tau : volt') with pytest.raises(SyntaxError): Equations('d_x/dt = -_x / tau : volt') # xi in a subexpression with pytest.raises(EquationError): Equations("""dv/dt = -(v + I) / (5 * ms) : volt I = second**-1*xi**-2*volt : volt""") # more than one xi with pytest.raises(EquationError): Equations("""dv/dt = -v / tau + xi/tau**.5 : volt dx/dt = -x / tau + 2*xi/tau : volt tau : second""") # using not-allowed flags eqs = Equations('dv/dt = -v / (5 * ms) : volt (flag)') eqs.check_flags({DIFFERENTIAL_EQUATION: ['flag']}) # allow this flag with pytest.raises(ValueError): eqs.check_flags({DIFFERENTIAL_EQUATION: []}) with pytest.raises(ValueError): eqs.check_flags({}) with pytest.raises(ValueError): eqs.check_flags({SUBEXPRESSION: ['flag']}) with pytest.raises(ValueError): eqs.check_flags({DIFFERENTIAL_EQUATION: ['otherflag']}) eqs = Equations('dv/dt = -v / (5 * ms) : volt (flag1, flag2)') eqs.check_flags({DIFFERENTIAL_EQUATION: ['flag1', 'flag2']}) # allow both flags # Don't allow the two flags in combination with pytest.raises(ValueError): eqs.check_flags({DIFFERENTIAL_EQUATION: ['flag1', 'flag2']}, incompatible_flags=[('flag1', 'flag2')]) eqs = Equations("""dv/dt = -v / (5 * ms) : volt (flag1) dw/dt = -w / (5 * ms) : volt (flag2)""") # They should be allowed when used independently eqs.check_flags({DIFFERENTIAL_EQUATION: ['flag1', 'flag2']}, incompatible_flags=[('flag1', 'flag2')]) # Circular subexpression with pytest.raises(ValueError): Equations("""dv/dt = -(v + w) / (10 * ms) : 1 w = 2 * x : 1 x = 3 * w : 1""") # Boolean/integer differential equations with pytest.raises(TypeError): Equations('dv/dt = -v / (10*ms) : boolean') with pytest.raises(TypeError): Equations('dv/dt = -v / (10*ms) : integer')