'''
Module for analysing synaptic pre and post code for synapse order independence.
'''
from brian2.utils.stringtools import get_identifiers
from brian2.core.functions import Function
from brian2.core.variables import Constant
__all__ = ['OrderDependenceError', 'check_for_order_independence']
[docs]class OrderDependenceError(Exception):
pass
[docs]def check_for_order_independence(statements, variables, indices):
'''
Check that the sequence of statements doesn't depend on the order in which the indices are iterated through.
'''
# Remove stateless functions from variables (only bother with ones that are used)
all_used_vars = set()
for statement in statements:
all_used_vars.update(get_identifiers(statement.expr))
variables = variables.copy()
for var in set(variables.keys()).intersection(all_used_vars):
val = variables[var]
if isinstance(val, Function):
if val.stateless:
del variables[var]
else:
raise OrderDependenceError("Function %s may have internal state, "
"which can lead to order dependence." % var)
all_variables = [v for v in variables if not isinstance(variables[v], Constant)]
# Main index variables are those whose index corresponds to the main index being iterated through. By
# assumption/definition, these indices are unique, and any order-dependence cannot come from their values,
# only from the values of the derived indices. In the most common case of Synapses, the main index would be
# the synapse index, and the derived indices would be pre and postsynaptic indices (which can be repeated).
unique_index = lambda v: (indices[v] != '0'
and getattr(variables[indices[v]],
'unique',
False))
main_index_variables = set([v for v in all_variables
if indices[v] == '_idx' or unique_index(v)])
different_index_variables = set(all_variables) - main_index_variables
# At the start, we assume all the different/derived index variables are permutation independent and we continue
# to scan through the list of statements checking whether or not permutation-dependence has been introduced
# until the permutation_independent set has stopped changing.
permutation_independent = list(different_index_variables)
permutation_dependent_aux_vars = set()
changed_permutation_independent = True
for statement in statements:
if statement.op == ':=' and statement.var not in all_variables:
main_index_variables.add(statement.var)
all_variables.append(statement.var)
while changed_permutation_independent:
changed_permutation_independent = False
for statement in statements:
vars_in_expr = get_identifiers(statement.expr).intersection(all_variables)
# any time a statement involves a LHS and RHS which only depend on itself, this doesn't change anything
if set([statement.var]) == vars_in_expr:
continue
nonsyn_vars_in_expr = vars_in_expr.intersection(different_index_variables)
permdep = any(var not in permutation_independent
for var in nonsyn_vars_in_expr)
permdep = permdep or any(var in permutation_dependent_aux_vars
for var in vars_in_expr)
if statement.op == ':=': # auxiliary variable created
if permdep:
if statement.var not in permutation_dependent_aux_vars:
permutation_dependent_aux_vars.add(statement.var)
changed_permutation_independent = True
continue
elif statement.var in main_index_variables:
if permdep:
raise OrderDependenceError()
elif statement.var in different_index_variables:
if statement.op in ('+=', '*=', '-=', '/='):
if permdep:
raise OrderDependenceError()
if statement.var in permutation_independent:
permutation_independent.remove(statement.var)
changed_permutation_independent = True
elif statement.op == '=':
otheridx = [v for v in variables
if indices[v] not in (indices[statement.var],
'_idx', '0')]
if any(var in otheridx for var in vars_in_expr):
raise OrderDependenceError()
if permdep:
raise OrderDependenceError()
if any(var in main_index_variables for var in vars_in_expr):
raise OrderDependenceError()
else:
raise OrderDependenceError()
else:
raise AssertionError('Should never get here...')