def separate_equations(eqs, additional_dependencies=[]): eqs.prepare() all_vars = eqs._string.keys() # Construct a list of dependency sets, where each variable in each # dependency set induces a dependency on each other variable in each # dependency set depsets = [] for var in all_vars: ids = set(get_identifiers(eqs._string[var])) ids = ids.intersection(all_vars) ids.add(var) depsets.append(ids) for expr in additional_dependencies: ids = set(get_identifiers(expr)) ids = ids.intersection(all_vars) depsets.append(ids) # Construct a graph deps which indicates what variable depends on which # other variables (or is depended on by other variables). deps = defaultdict(set) for ids in depsets: for id1 in ids: for id2 in ids: deps[id1].add(id2) # Extract all the independent subgraphs ind_graphs = [] while len(deps): ind_graphs.append(set(next_independent_subgraph(deps).keys())) if len(ind_graphs) == 1: return [eqs] # Finally, we construct an Equations object for each of the subgraphs ind_eqs = [] for G in ind_graphs: neweqs = Equations() for var in G: if var in eqs._eq_names: neweqs.add_eq(var, eqs._string[var], eqs._units[var], local_namespace=eqs._namespace[var]) elif var in eqs._diffeq_names: nonzero = var in eqs._diffeq_names_nonzero neweqs.add_diffeq(var, eqs._string[var], eqs._units[var], local_namespace=eqs._namespace[var], nonzero=nonzero) elif var in eqs._alias.keys(): neweqs.add_alias(var, eqs._string[var].strip()) else: assert False ind_eqs.append(neweqs) return ind_eqs
def to_sympy_expression(eq_string): """ Simple helper function for converting an Equation string `eq_string` (only the right hand side of an equation) into a `sympy` expression by calling `x = Symbol('x')` for every variable `x` in the equation. """ l_namespace = {'Symbol': Symbol} # add all variables as sympy symbols to namespace for identifier in get_identifiers(eq_string): exec '%s = Symbol("%s")' % (identifier, identifier) in {}, l_namespace return eval(eq_string, {}, l_namespace)
def select_threshold(expr, eqs, level=0): ''' Automatically selects the appropriate Threshold object from a string. Matches the following patterns: var_name > or >= const : Threshold var_name > or >= var_name : VariableThreshold others : StringThreshold ''' global CThreshold, PythonThreshold use_codegen = (get_global_preference('usecodegen') and get_global_preference('usecodegenthreshold')) use_weave = (get_global_preference('useweave') and get_global_preference('usecodegenweave')) if use_codegen: if CThreshold is None: from brian.experimental.codegen.threshold import (CThreshold, PythonThreshold) if use_weave: log_warn('brian.threshold', 'Using codegen CThreshold') return CThreshold(expr, level=level + 1) else: log_warn('brian.threshold', 'Using codegen PythonThreshold') return PythonThreshold(expr, level=level + 1) # plan: # - see if it matches A > B or A >= B, if not select StringThreshold # - check if A, B both match diffeq variable names, and if so # select VariableThreshold # - check that A is a variable name, if not select StringThreshold # - extract all the identifiers from B, and if none of them are # callable, assume it is a constant, try to eval it and then use # Threshold. If not, or if eval fails, use StringThreshold. # This misses the case of e.g. V>10*mV*exp(1) because exp will be # callable, but in general a callable means that it could be # non-constant. expr = expr.strip() eqs.prepare() ns = namespace(expr, level=level + 1) s = re.search(r'^\s*(\w+)\s*>=?(.+)', expr) if not s: return StringThreshold(expr, level=level + 1) A = s.group(1) B = s.group(2).strip() if A not in eqs._diffeq_names: return StringThreshold(expr, level=level + 1) if B in eqs._diffeq_names: return VariableThreshold(B, A) try: vars = get_identifiers(B) except SyntaxError: return StringThreshold(expr, level=level + 1) all_vars = eqs._eq_names + eqs._diffeq_names + eqs._alias.keys() + ['t'] for v in vars: if v not in ns or v in all_vars or callable(ns[v]): return StringThreshold(expr, level=level + 1) try: val = eval(B, ns) except: return StringThreshold(expr, level=level + 1) return Threshold(val, A)
def __init__(self, source, target = None, model = None, pre = None, post = None, max_delay = 0*ms, level = 0, clock = None,code_namespace=None, unit_checking = True, method = None, freeze = False, implicit = False, order = 1): # model (state updater) related target=target or source # default is target=source # Check clocks. For the moment we enforce the same clocks for all objects clock = clock or source.clock if source.clock!=target.clock: raise ValueError,"Source and target groups must have the same clock" if pre is None: pre_list=[] elif isSequenceType(pre) and not isinstance(pre,str): # a list of pre codes pre_list=pre else: pre_list=[pre] pre_list=[flattened_docstring(pre) for pre in pre_list] if post is not None: post=flattened_docstring(post) # Pre and postsynaptic indexes (synapse -> pre/post) self.presynaptic=DynamicArray1D(0,dtype=smallest_inttype(len(source))) # this should depend on number of neurons self.postsynaptic=DynamicArray1D(0,dtype=smallest_inttype(len(target))) # this should depend on number of neurons if not isinstance(model,SynapticEquations): model=SynapticEquations(model,level=level+1) # Insert the lastupdate variable if necessary (if it is mentioned in pre/post, or if there is event-driven code) expr=re.compile(r'\blastupdate\b') if (len(model._eventdriven)>0) or \ any([expr.search(pre) for pre in pre_list]) or \ (post is not None and expr.search(post) is not None): model+='\nlastupdate : second\n' pre_list=[pre+'\nlastupdate=t\n' for pre in pre_list] if post is not None: post=post+'\nlastupdate=t\n' # Identify pre and post variables in the model string # They are identified by _pre and _post suffixes # or no suffix for postsynaptic variables ids=set() for RHS in model._string.itervalues(): ids.update(get_identifiers(RHS)) pre_ids = [id[:-4] for id in ids if id[-4:]=='_pre'] post_ids = [id[:-5] for id in ids if id[-5:]=='_post'] post_vars = [var for var in source.var_index if isinstance(var,str)] # postsynaptic variables post_ids2 = list(ids.intersection(set(post_vars))) # post variables without the _post suffix # remember whether our equations refer to any variables in the pre- or # postsynaptic group. This is important for the state-updater, e.g. the # equations can no longer be solved as linear equations. model.refers_others = (len(pre_ids) + len(post_ids) + len(post_ids2) > 0) # Insert static equations for pre and post variables S=self for name in pre_ids: model.add_eq(name+'_pre', 'S.source.'+name+'[S.presynaptic[:]]', source.unit(name), global_namespace={'S':S}) for name in post_ids: model.add_eq(name+'_post', 'S.target.'+name+'[S.postsynaptic[:]]', target.unit(name), global_namespace={'S':S}) for name in post_ids2: # we have to change the name of the variable to avoid problems with equation processing if name not in model._string: # check that it is not already defined model.add_eq(name, 'S.target.state_(__'+name+')[S.postsynaptic[:]]', target.unit(name), global_namespace={'S':S,'__'+name:name}) self.source=source self.target=target NeuronGroup.__init__(self, 0,model=model,clock=clock,level=level+1,unit_checking=unit_checking,method=method,freeze=freeze,implicit=implicit,order=order) ''' At this point we have: * a state matrix _S with all variables * units, state dictionary with each value being a row of _S + the static equations * subgroups of synapses * link_var (i.e. we can link two synapses objects) * __len__ * __setattr__: we can write S.w=array of values * var_index is a dictionary from names to row index in _S * num_states() Things we have that we don't want: * LS structure (but it will not be filled since the object does not spike) * (from Group) __getattr_ needs to be rewritten * a complete state updater, but we need to extract parameters and event-driven parts * The state matrix is not dynamic Things we may need to add: * _pre and _post suffixes ''' self._iscompressed=False # True if compress() has already been called # Look for event-driven code in the differential equations if use_sympy: eqs=self._eqs # an Equations object #vars=eqs._diffeq_names_nonzero # Dynamic variables vars=eqs._eventdriven.keys() var_set=set(vars) for var,RHS in eqs._eventdriven.iteritems(): ids=get_identifiers(RHS) if len(set(list(ids)+[var]).intersection(var_set))==1: # no external dynamic variable # Now we test if it is a linear equation _namespace=dict.fromkeys(ids,1.) # there is a possibility of problems here (division by zero) # plus units problems? (maybe not since these are identifiers too) # another option is to use random numbers, but that doesn't solve all problems _namespace[var]=AffineFunction() try: eval(RHS,eqs._namespace[var],_namespace) except: # not linear raise TypeError,"Cannot turn equation for "+var+" into event-driven code" z=symbolic_eval(RHS) symbol_var=sympy.Symbol(var) symbol_t=sympy.Symbol('t')-sympy.Symbol('lastupdate') b=z.subs(symbol_var,0) a=sympy.simplify(z.subs(symbol_var,1)-b) if a==0: expr=symbol_var+b*symbol_t else: expr=-b/a+sympy.exp(a*symbol_t)*(symbol_var+b/a) expr=var+'='+str(expr) # Replace pre and post code # N.B.: the differential equations are kept, we will probably want to remove them! pre_list=[expr+'\n'+pre for pre in pre_list] if post is not None: post=expr+'\n'+post else: raise TypeError,"Cannot turn equation for "+var+" into event-driven code" elif len(self._eqs._eventdriven)>0: raise TypeError,"The Sympy package must be installed to produce event-driven code" if len(self._eqs._diffeq_names_nonzero)==0: self._state_updater=None # Set last spike to -infinity if 'lastupdate' in self.var_index: self.lastupdate=-1e6 # _S is turned to a dynamic array - OK this is probably not good! we may lose references at this point S=self._S self._S=DynamicArray(S.shape) self._S[:]=S # Pre and postsynaptic delays (synapse -> delay_pre/delay_post) self._delay_pre=[DynamicArray1D(len(self),dtype=np.int16) for _ in pre_list] # max 32767 delays self._delay_post=DynamicArray1D(len(self),dtype=np.int16) # Actually only useful if there is a post code! # Pre and postsynaptic synapses (i->synapse indexes) max_synapses=2147483647 # it could be explicitly reduced by a keyword # We use a loop instead of *, otherwise only 1 dynamic array is created self.synapses_pre=[DynamicArray1D(0,dtype=smallest_inttype(max_synapses)) for _ in range(len(self.source))] self.synapses_post=[DynamicArray1D(0,dtype=smallest_inttype(max_synapses)) for _ in range(len(self.target))] # Code generation self._binomial = lambda n,p:np.random.binomial(np.array(n,dtype=int),p) self.contained_objects = [] self.codes=[] self.namespaces=[] self.queues=[] for i,pre in enumerate(pre_list): code,_namespace=self.generate_code(pre,level+1,code_namespace=code_namespace) self.codes.append(code) self.namespaces.append(_namespace) self.queues.append(SpikeQueue(self.source, self.synapses_pre, self._delay_pre[i], max_delay = max_delay)) if post is not None: code,_namespace=self.generate_code(post,level+1,direct=True,code_namespace=code_namespace) self.codes.append(code) self.namespaces.append(_namespace) self.queues.append(SpikeQueue(self.target, self.synapses_post, self._delay_post, max_delay = max_delay)) self.queues_namespaces_codes = zip(self.queues, self.namespaces, self.codes) self.contained_objects+=self.queues
def integral2differential(expr, T=20 * ms, level=0, N=20, suffix=None, matrix_output=False): ''' Example: eqs,w=integral2differential('g(t)=t*exp(-t/tau)') M,nvar,w=integral2differential('g(t)=t*exp(-t/tau)',matrix_output=True) Returns an Equations object corresponding to the time-invariant linear system specified by the impulse response g(t), and the value w to generate the impulse response: g_in->g_in+w. If matrix_output is True, returns the matrix of the corresponding differential system, the index nvar of the variable and the initial condition w=x_nvar(0). T is the interval over which the function is calculated. N is the number of points chosen in that interval. level is the frame level where the expression is defined. suffix is a string added to internal variable names (default: unique string). ''' # Expression matching varname, time, RHS = re.search('\s*(\w+)\s*\(\s*(\w+)\s*\)\s*=\s*(.+)\s*', expr).groups() # Build the namespace frame = inspect.stack()[level + 1][0] global_namespace, local_namespace = frame.f_globals, frame.f_locals # Find external objects vars = list(get_identifiers(RHS)) namespace = {} for var in vars: if var == time: # time variable pass elif var in local_namespace: #local namespace[var] = local_namespace[var] elif var in global_namespace: #global namespace[var] = global_namespace[var] elif var in globals(): # typically units namespace[var] = globals()[var] # Convert to a function f = eval('lambda ' + time + ':' + RHS, namespace) # Unit unit = get_unit(f(rand()*second)).name # Pick N points t = rand(N) * T # Calculate derivatives and find rank n = 0 rank = 0 M = f(t).reshape(N, 1) while rank == n: n += 1 dfn = differentiate(f, t, order=n).reshape(N, 1) x, _, rank, _ = linalg.lstsq(M, dfn) if rank == n: M = hstack([M, dfn]) oldx = x # oldx expresses dfn as a function of df0,..,dfn-1 (n=rank) # Find initial condition X0 = array([differentiate(f, 0 * ms, order=n) for n in range(rank)]) # Rescaling DOES NOT WORK #R=ones(rank) #for i in range(rank): # if X0[i]!=0.: # R[i]=1./X0[i] # else: # R[i]=1. #R=diag(R) #X0=dot(R,X0) #oldx=dot(R,oldx) # Build A A = diag(ones(rank - 1), 1) A[-1, :] = oldx.reshape(1, rank) # Find Q=P^{-1} Q = eye(rank) if X0[0] == 0.: # continuous g, spikes act on last variable: x->x+1 Q[:, -1] = X0 nvar = rank - 1 w = 1. # Exact inversion P = eye(rank) P[:-1, -1] = -X0[:-1] / X0[-1] # Has to be !=0 !! P[-1, -1] = 1. / X0[-1] else: # discontinuous g, spikes act on first variable: x->x+g(0) Q[:, 0] = X0 nvar = 0 w = X0[0] P = linalg.inv(Q) M = dot(dot(P, A), Q) #M=dot(linalg.inv(R),dot(M,R)) # Turn into string # Set variable names if rank < 5: names = [varname] + ['x', 'y', 'z'][:rank - 1] else: names = [varname] + ['x' + str(i) for i in range(rank - 1)] # Add suffix if suffix is None: suffix = unique_id() names[1:] = [name + suffix for name in names[1:]] # Build string eqs = [] for i in range(rank): eqs.append('d' + names[i] + '/dt=' + '+'.join([str(x) + '*' + name for x, name in zip(M[i, :], names) if x != 0.]) + ' : ' + str(unit)) eqs.append(varname + '_in=' + names[nvar]) # alias eq_string = '\n'.join(eqs).replace('+-', '-') if matrix_output: return M, nvar, w else: return Equations(eq_string), w
def integral2differential(expr, T=20 * ms, level=0, N=20, suffix=None, matrix_output=False): ''' Example: eqs,w=integral2differential('g(t)=t*exp(-t/tau)') M,nvar,w=integral2differential('g(t)=t*exp(-t/tau)',matrix_output=True) Returns an Equations object corresponding to the time-invariant linear system specified by the impulse response g(t), and the value w to generate the impulse response: g_in->g_in+w. If matrix_output is True, returns the matrix of the corresponding differential system, the index nvar of the variable and the initial condition w=x_nvar(0). T is the interval over which the function is calculated. N is the number of points chosen in that interval. level is the frame level where the expression is defined. suffix is a string added to internal variable names (default: unique string). ''' # Expression matching varname, time, RHS = re.search('\s*(\w+)\s*\(\s*(\w+)\s*\)\s*=\s*(.+)\s*', expr).groups() # Build the namespace frame = inspect.stack()[level + 1][0] global_namespace, local_namespace = frame.f_globals, frame.f_locals # Find external objects vars = list(get_identifiers(RHS)) namespace = {} for var in vars: if var == time: # time variable pass elif var in local_namespace: #local namespace[var] = local_namespace[var] elif var in global_namespace: #global namespace[var] = global_namespace[var] elif var in globals(): # typically units namespace[var] = globals()[var] # Convert to a function f = eval('lambda ' + time + ':' + RHS, namespace) # Unit unit = get_unit(f(rand() * second)).name # Pick N points t = rand(N) * T # Calculate derivatives and find rank n = 0 rank = 0 M = f(t).reshape(N, 1) while rank == n: n += 1 dfn = differentiate(f, t, order=n).reshape(N, 1) x, _, rank, _ = linalg.lstsq(M, dfn) if rank == n: M = hstack([M, dfn]) oldx = x # oldx expresses dfn as a function of df0,..,dfn-1 (n=rank) # Find initial condition X0 = array([differentiate(f, 0 * ms, order=n) for n in range(rank)]) # Rescaling DOES NOT WORK #R=ones(rank) #for i in range(rank): # if X0[i]!=0.: # R[i]=1./X0[i] # else: # R[i]=1. #R=diag(R) #X0=dot(R,X0) #oldx=dot(R,oldx) # Build A A = diag(ones(rank - 1), 1) A[-1, :] = oldx.reshape(1, rank) # Find Q=P^{-1} Q = eye(rank) if X0[0] == 0.: # continuous g, spikes act on last variable: x->x+1 Q[:, -1] = X0 nvar = rank - 1 w = 1. # Exact inversion P = eye(rank) P[:-1, -1] = -X0[:-1] / X0[-1] # Has to be !=0 !! P[-1, -1] = 1. / X0[-1] else: # discontinuous g, spikes act on first variable: x->x+g(0) Q[:, 0] = X0 nvar = 0 w = X0[0] P = linalg.inv(Q) M = dot(dot(P, A), Q) #M=dot(linalg.inv(R),dot(M,R)) # Turn into string # Set variable names if rank < 5: names = [varname] + ['x', 'y', 'z'][:rank - 1] else: names = [varname] + ['x' + str(i) for i in range(rank - 1)] # Add suffix if suffix is None: suffix = unique_id() names[1:] = [name + suffix for name in names[1:]] # Build string eqs = [] for i in range(rank): eqs.append('d' + names[i] + '/dt=' + '+'.join([ str(x) + '*' + name for x, name in zip(M[i, :], names) if x != 0. ]) + ' : ' + str(unit)) eqs.append(varname + '_in=' + names[nvar]) # alias eq_string = '\n'.join(eqs).replace('+-', '-') if matrix_output: return M, nvar, w else: return Equations(eq_string), w