def test_expr_check_linearity(): ''' Test checking for linearity. ''' expr = Expression('-v / tau + sin(2 * pi * t * f)') assert expr.check_linearity('v') assert expr.check_linearity('x') # does not appear in the expression assert not expr.check_linearity('tau')
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_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, 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') assert_raises(ValueError, expr.split_stochastic)
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_resolve(): ''' Test resolving external identifiers. ''' I = 3 * mV tau = 5 * ms expr = Expression('-(v + I) / tau') namespace = expr.resolve(['v']) assert not 'v' in namespace assert namespace['I'] == I and namespace['tau'] == tau another_I = 5 * mV expr = Expression('-(v + I) / tau', namespace={'I' : another_I}) # tau is not defined, the namespace should be exhaustive assert_raises(ValueError, lambda: expr.resolve(['v'])) expr = Expression('-(v + I) / tau', namespace={'I' : another_I, 'tau': tau}) # Now it should work namespace = expr.resolve(['v']) assert namespace['I'] == another_I and namespace['tau'] == tau # test resolution of units not present in any namespace expr = Expression('v * amp * ohm') namespace = expr.resolve(['v']) assert namespace['ohm'] is brian2.ohm and namespace['amp'] is brian2.amp
def test_resolution_warnings(): ''' Test that certain calls to resolve generate a warning. ''' I = 3 * mV tau = 5 * ms another_I = 5 * mV # Only specifying part of the namespace expr = Expression('-(v + I) / tau', namespace={'I' : another_I}, exhaustive=False) # make sure this triggers a warning (the 'I' in the namespace shadows the # I variable defined above with catch_logs() as logs: namespace = expr.resolve(['v']) assert len(logs) == 1 assert logs[0][0] == 'WARNING' assert logs[0][1].endswith('resolution_conflict') assert namespace['I'] == another_I and namespace['tau'] == tau freq = 300 * Hz t = 5 * second # This expression treats t as a special variable and is not actually using # the t above! expr = Expression('sin(2 * 3.141 * freq * t)') with catch_logs() as logs: namespace = expr.resolve([]) assert len(logs) == 1 assert logs[0][0] == 'WARNING' assert logs[0][1].endswith('resolution_conflict') assert namespace['freq'] == freq and not 't' in namespace I = 3 * mV tau = 5 * ms expr = Expression('-(v + I)/ tau') # If we claim that I is an internal variable, it shadows the variable # defined in the local namespace -- this should trigger a warning with catch_logs() as logs: namespace = expr.resolve(['v', 'I']) assert len(logs) == 1 assert logs[0][0] == 'WARNING' assert logs[0][1].endswith('resolution_conflict') assert namespace['tau'] == tau and not 'I' in namespace # A more extreme example: I is defined above, but also in the namespace and # is claimed to be an internal variable expr = Expression('-(v + I)/ tau', namespace={'I': 5 * mV}, exhaustive=False) with catch_logs() as logs: namespace = expr.resolve(['v', 'I']) assert len(logs) == 1 assert logs[0][0] == 'WARNING' assert logs[0][1].endswith('resolution_conflict') assert namespace['tau'] == tau and not 'I' in namespace
def test_split_stochastic(): tau = 5 * ms expr = Expression('(-v + I) / tau') expr.resolve(['v', 'I']) # No stochastic part assert expr.split_stochastic() == (expr, None) expr = Expression('(-v + I) / tau + sigma*xi/tau**.5') expr.resolve(['v', 'I', 'sigma']) non_stochastic, stochastic = expr.split_stochastic() assert 'xi' in stochastic.identifiers assert sympy_equals(non_stochastic.code, '(-v + I) / tau') assert sympy_equals(stochastic.code, 'sigma*xi/tau**.5') expr = Expression('-v / tau + 1 / xi') assert_raises(ValueError, expr.split_stochastic)
def test_expr_units(): ''' Test getting/checking the units of an expression. ''' tau = 5 * ms expr = Expression('-v / tau', namespace={'tau': tau}) expr.resolve(['v']) expr.check_units(volt / second, {'v': volt}) assert_raises(DimensionMismatchError, lambda: expr.check_units(volt / second, {'v': second})) assert_raises(DimensionMismatchError, lambda: expr.check_units(volt, {'v': volt})) assert expr.get_dimensions({'v': volt}) == get_dimensions(volt / second)
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')
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_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)