def test_apply_loop_invariant_optimisation_integer(): variables = { 'v': Variable('v', scalar=False), 'N': Constant('N', 10), 'b': Variable('b', scalar=True, dtype=int), 'c': Variable('c', scalar=True, dtype=int), 'd': Variable('d', scalar=True, dtype=int), 'y': Variable('y', scalar=True, dtype=float), 'z': Variable('z', scalar=True, dtype=float), 'w': Variable('w', scalar=True, dtype=float), } statements = [ Statement('v', '=', 'v % (2*3*N)', '', np.float32), # integer version doesn't get rewritten but float version does Statement('a', ':=', 'b//(c//d)', '', int), Statement('x', ':=', 'y/(z/w)', '', float), ] scalar, vector = optimise_statements([], statements, variables) assert len(scalar) == 3 assert np.issubdtype(scalar[0].dtype, np.signedinteger) assert scalar[0].var == '_lio_1' expr = scalar[0].expr.replace(' ', '') assert expr == '6*N' or expr == 'N*6' assert np.issubdtype(scalar[1].dtype, np.signedinteger) assert scalar[1].var == '_lio_2' expr = scalar[1].expr.replace(' ', '') assert expr == 'b//(c//d)' assert np.issubdtype(scalar[2].dtype, np.floating) assert scalar[2].var == '_lio_3' expr = scalar[2].expr.replace(' ', '') assert expr == '(y*w)/z' or expr == '(w*y)/z'
def test_apply_loop_invariant_optimisation_constant_evaluation(): variables = { 'v1': Variable('v1', scalar=False), 'v2': Variable('v2', scalar=False), 'i1': Variable('i1', scalar=False, dtype=int), 'N': Constant('N', 10), 's1': Variable('s1', scalar=True, dtype=float), 's2': Variable('s2', scalar=True, dtype=float), 'exp': DEFAULT_FUNCTIONS['exp'] } statements = [ Statement('v1', '=', 'v1 * (1 + 2 + 3)', '', np.float), Statement('v1', '=', 'exp(N)*v1', '', np.float), Statement('v1', '=', 'exp(0)*v1', '', np.float), ] scalar, vector = optimise_statements([], statements, variables) # exp(N) should be pulled out of the vector statements, the rest should be # evaluated in place assert len(scalar) == 1 assert scalar[0].expr == 'exp(N)' assert len(vector) == 3 expr = vector[0].expr.replace(' ', '') assert expr == '_lio_1*v1' or 'v1*_lio_1' expr = vector[1].expr.replace(' ', '') assert expr == '6.0*v1' or 'v1*6.0' assert vector[2].expr == 'v1'
def test_apply_loop_invariant_optimisation_boolean(): variables = {'v1': Variable('v1', scalar=False), 'v2': Variable('v2', scalar=False), 'N': Constant('N', 10), 'b': Variable('b', scalar=True, dtype=bool), 'c': Variable('c', scalar=True, dtype=bool), 'int': DEFAULT_FUNCTIONS['int'], 'foo': Function(lambda x: None, arg_units=[Unit(1)], return_unit=Unit(1), arg_types=['boolean'], return_type='float', stateless=False) } # The calls for "foo" cannot be pulled out, since foo is marked as stateful statements = [Statement('v1', '=', '1.0*int(b and c)', '', np.float32), Statement('v1', '=', '1.0*foo(b and c)', '', np.float32), Statement('v2', '=', 'int(not b and True)', '', np.float32), Statement('v2', '=', 'foo(not b and True)', '', np.float32) ] scalar, vector = optimise_statements([], statements, variables) assert len(scalar) == 4 assert scalar[0].expr == '1.0 * int(b and c)' assert scalar[1].expr == 'b and c' assert scalar[2].expr == 'int((not b) and True)' assert scalar[3].expr == '(not b) and True' assert len(vector) == 4 assert vector[0].expr == '_lio_1' assert vector[1].expr == 'foo(_lio_2)' assert vector[2].expr == '_lio_3' assert vector[3].expr == 'foo(_lio_4)'
def test_apply_loop_invariant_optimisation(): variables = {'v': Variable('v', scalar=False), 'w': Variable('w', scalar=False), 'dt': Constant('dt', dimensions=second.dim, value=0.1*ms), 'tau': Constant('tau', dimensions=second.dim, value=10*ms), 'exp': DEFAULT_FUNCTIONS['exp']} statements = [Statement('v', '=', 'dt*w*exp(-dt/tau)/tau + v*exp(-dt/tau)', '', np.float32), Statement('w', '=', 'w*exp(-dt/tau)', '', np.float32)] scalar, vector = optimise_statements([], statements, variables) # The optimisation should pull out at least exp(-dt / tau) assert len(scalar) >= 1 assert np.issubdtype(scalar[0].dtype, np.floating) assert scalar[0].var == '_lio_1' assert len(vector) == 2 assert all('_lio_' in stmt.expr for stmt in vector)
def test_apply_loop_invariant_optimisation_no_optimisation(): variables = { 'v1': Variable('v1', scalar=False), 'v2': Variable('v2', scalar=False), 'N': Constant('N', 10), 's1': Variable('s1', scalar=True, dtype=float), 's2': Variable('s2', scalar=True, dtype=float), 'rand': DEFAULT_FUNCTIONS['rand'] } statements = [ # This hould not be simplified to 0! Statement('v1', '=', 'rand() - rand()', '', np.float), Statement('v1', '=', '3*rand() - 3*rand()', '', np.float), Statement('v1', '=', '3*rand() - ((1+2)*rand())', '', np.float), # This should not pull out rand()*N Statement('v1', '=', 's1*rand()*N', '', np.float), Statement('v1', '=', 's2*rand()*N', '', np.float), # This is not important mathematically, but it would change the numbers # that are generated Statement('v1', '=', '0*rand()*N', '', np.float), Statement('v1', '=', '0/rand()*N', '', np.float) ] scalar, vector = optimise_statements([], statements, variables) for vs in vector[:3]: assert vs.expr.count( 'rand()' ) == 2, 'Expression should still contain two rand() calls, but got ' + str( vs) for vs in vector[3:]: assert vs.expr.count( 'rand()' ) == 1, 'Expression should still contain a rand() call, but got ' + str( vs)
def test_apply_loop_invariant_optimisation_integer(): variables = { 'v': Variable('v', Unit(1), scalar=False), 'N': Constant('N', Unit(1), 10) } statements = [Statement('v', '=', 'v % (2*3*N)', '', np.float32)] scalar, vector = apply_loop_invariant_optimisations( statements, variables, np.float64) # The optimisation should not pull out 2*N assert len(scalar) == 0
def check_pre_code(codegen, stmts, vars_pre, vars_syn, vars_post): ''' Given a set of statements stmts where the variables names in vars_pre are presynaptic, in vars_syn are synaptic and in vars_post are postsynaptic, check that the conditions for compatibility with GeNN are met, and return a new statement sequence translated for compatibility with GeNN, along with the name of the targeted variable. ''' read, write, indices = codegen.array_read_write(stmts) post_write = set(write).intersection(set(vars_post)) if len(post_write) == 0: raise NotImplementedError( "GeNN does not support Synapses with no postsynaptic effect.") if len(post_write) > 1: raise NotImplementedError( "GeNN only supports writing to a single postsynaptic variable.") post_write_var = list(post_write)[0] found_write_statement = False new_stmts = [] for stmt in stmts: ids = get_identifiers(stmt.expr) if stmt.var == post_write_var: if stmt.inplace: if stmt.op != '+=': raise NotImplementedError( "GeNN only supports the += in place operation on postsynaptic variables." ) accumulation_expr = stmt.expr else: # TODO: we could support expressions like v = v + expr, but this requires some additional work # namely, for an expression like v = expr we need to check if (expr-v) when simplified reduces to # an expression that only has postsynaptic variables using sympy raise NotImplementedError( "GeNN only supports in-place modification of postsynaptic variables" ) new_stmt = Statement('addtoinSyn', '=', '_hidden_weightmatrix*(' + accumulation_expr + ')', comment=stmt.comment, dtype=stmt.dtype) new_stmts.append(new_stmt) if found_write_statement: raise NotImplementedError( "GeNN does not support multiple writes to postsynaptic variables." ) found_write_statement = True else: new_stmts.append(stmt) return post_write_var, new_stmts
def test_apply_loop_invariant_optimisation(): variables = { 'v': Variable('v', Unit(1), scalar=False), 'w': Variable('w', Unit(1), scalar=False), 'dt': Constant('dt', second, 0.1 * ms), 'tau': Constant('tau', second, 10 * ms), 'exp': DEFAULT_FUNCTIONS['exp'] } statements = [ Statement('v', '=', 'dt*w*exp(-dt/tau)/tau + v*exp(-dt/tau)', '', np.float32), Statement('w', '=', 'w*exp(-dt/tau)', '', np.float32) ] scalar, vector = apply_loop_invariant_optimisations( statements, variables, np.float64) # The optimisation should pull out exp(-dt / tau) assert len(scalar) == 1 assert scalar[0].dtype == np.float64 # We asked for this dtype above assert scalar[0].var == '_lio_const_1' assert len(vector) == 2 assert all('_lio_const_1' in stmt.expr for stmt in vector)
def test_apply_loop_invariant_optimisation_simplification(): variables = { 'v1': Variable('v1', scalar=False), 'v2': Variable('v2', scalar=False), 'i1': Variable('i1', scalar=False, dtype=int), 'N': Constant('N', 10) } statements = [ # Should be simplified to 0.0 Statement('v1', '=', 'v1 - v1', '', np.float), Statement('v1', '=', 'N*v1 - N*v1', '', np.float), Statement('v1', '=', 'v1*N * 0', '', np.float), Statement('v1', '=', 'v1 * 0', '', np.float), Statement('v1', '=', 'v1 * 0.0', '', np.float), Statement('v1', '=', '0.0 / (v1*N)', '', np.float), # Should be simplified to 0 Statement('i1', '=', 'i1*N * 0', '', np.int), Statement('i1', '=', '0 * i1', '', np.int), Statement('i1', '=', '0 * i1*N', '', np.int), Statement('i1', '=', 'i1 * 0', '', np.int), # Should be simplified to v1*N Statement('v2', '=', '0 + v1*N', '', np.float), Statement('v2', '=', 'v1*N + 0.0', '', np.float), Statement('v2', '=', 'v1*N - 0', '', np.float), Statement('v2', '=', 'v1*N - 0.0', '', np.float), Statement('v2', '=', '1 * v1*N', '', np.float), Statement('v2', '=', '1.0 * v1*N', '', np.float), Statement('v2', '=', 'v1*N / 1.0', '', np.float), Statement('v2', '=', 'v1*N / 1', '', np.float), # Should be simplified to i1 Statement('i1', '=', 'i1*1', '', int), Statement('i1', '=', 'i1//1', '', int), Statement('i1', '=', 'i1+0', '', int), Statement('i1', '=', '0+i1', '', int), Statement('i1', '=', 'i1-0', '', int), # Should *not* be simplified (because it would change the type, # important for integer division, for example) Statement('v1', '=', 'i1*1.0', '', float), Statement('v1', '=', '1.0*i1', '', float), Statement('v1', '=', 'i1/1.0', '', float), Statement('v1', '=', 'i1/1', '', float), Statement('v1', '=', 'i1+0.0', '', float), Statement('v1', '=', '0.0+i1', '', float), Statement('v1', '=', 'i1-0.0', '', float), ## Should *not* be simplified, flooring division by 1 changes the value Statement('v1', '=', 'v2//1.0', '', float), Statement('i1', '=', 'i1//1.0', '', float) # changes type ] scalar, vector = optimise_statements([], statements, variables) assert len(scalar) == 0 for s in vector[:6]: assert s.expr == '0.0' for s in vector[6:10]: assert s.expr == '0', s.expr # integer for s in vector[10:18]: expr = s.expr.replace(' ', '') assert expr == 'v1*N' or expr == 'N*v1' for s in vector[18:23]: expr = s.expr.replace(' ', '') assert expr == 'i1' for s in vector[23:27]: expr = s.expr.replace(' ', '') assert expr == '1.0*i1' or expr == 'i1*1.0' or expr == 'i1/1.0' for s in vector[27:30]: expr = s.expr.replace(' ', '') assert expr == '0.0+i1' or expr == 'i1+0.0' for s in vector[30:31]: expr = s.expr.replace(' ', '') assert expr == 'v2//1.0' or expr == 'v2//1' for s in vector[31:]: expr = s.expr.replace(' ', '') assert expr == 'i1//1.0'
def check_pre_code(codegen, stmts, vars_pre, vars_syn, vars_post, conditional_write_vars): ''' Given a set of statements stmts where the variables names in vars_pre are presynaptic, in vars_syn are synaptic and in vars_post are postsynaptic, check that the conditions for compatibility with GeNN are met, and return a new statement sequence translated for compatibility with GeNN, along with the name of the targeted variable. Also adapts the synaptic statement to be multiplied by 0 for a refractory post-synaptic cell. ''' read, write, indices = codegen.array_read_write(stmts) post_write = set(write).intersection(set(vars_post)) if len(post_write) > 1: raise NotImplementedError( "GeNN only supports writing to a single postsynaptic variable.") if len(post_write) == 0: logger.warn( "Warning: You have defined (a) synaptic group(s) that has no direct postsynaptic effect." ) return None, stmts post_write_var = list(post_write)[0] found_write_statement = False new_stmts = [] for stmt in stmts: ids = get_identifiers(stmt.expr) if stmt.var == post_write_var: if stmt.inplace: if stmt.op != '+=': raise NotImplementedError( "GeNN only supports the += in place operation on postsynaptic variables." ) accumulation_expr = stmt.expr # "write-protect" a variable during refractoriness to match Brian's semantics if stmt.var in conditional_write_vars: assert conditional_write_vars[stmt.var] == 'not_refractory' accumulation_expr = 'int(not_refractory_post) * ({})'.format( accumulation_expr) else: # TODO: we could support expressions like v = v + expr, but this requires some additional work # namely, for an expression like v = expr we need to check if (expr-v) when simplified reduces to # an expression that only has postsynaptic variables using sympy raise NotImplementedError( "GeNN only supports in-place modification of postsynaptic variables" ) new_stmt = Statement('addtoinSyn', '=', '_hidden_weightmatrix*(' + accumulation_expr + ')', comment=stmt.comment, dtype=stmt.dtype) new_stmts.append(new_stmt) if found_write_statement: raise NotImplementedError( "GeNN does not support multiple writes to postsynaptic variables." ) found_write_statement = True else: new_stmts.append(stmt) return post_write_var, new_stmts