Source code for brian2.codegen.permutation_analysis

"""
Module for analysing synaptic pre and post code for synapse order independence.
"""

from brian2.core.functions import Function
from brian2.core.variables import Constant
from brian2.utils.stringtools import get_identifiers

__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 = { 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 {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...")