Source code for pyphi.substrate_generator.weights

# substrate_generator/weights.py
"""Generate different weight matrices."""

import functools
from itertools import product

import numpy as np


[docs] def normalize_inputs(W): return np.nan_to_num(W / W.sum(axis=0, keepdims=True), nan=0.0)
[docs] def normalize_outputs(W): return np.nan_to_num(W / W.sum(axis=1, keepdims=True), nan=0.0)
def _optionally_normalize_inputs(func): @functools.wraps(func) def wrapper(*args, normalize_input_weights=False, **kwargs): W = func(*args, **kwargs) if normalize_input_weights: W = normalize_inputs(W) return W return wrapper
[docs] def pareto_distribution(n, alpha=1.0): return np.ones(n) / np.arange(1, n + 1) ** alpha
[docs] @_optionally_normalize_inputs def nearest_neighbor( size, weights, k=1, periodic=False, **kwargs, ): if periodic: k_limit = (size + 1) / 2 else: k_limit = size if k >= k_limit: raise ValueError(f"k must be <= {k_limit} (n={size}, periodic={periodic})") weights = np.ones(k + 1) * weights # Self loops W = weights[0] * np.eye(size) # Laterals for i in range(1, k + 1): for j in [-1, 1]: W += weights[i] * np.eye(size, k=j * i) # Don't double-weight farthest laterals if periodic if periodic and i < (size / 2): W += weights[i] * np.eye(size, k=j * (size - i)) return W
[docs] @_optionally_normalize_inputs def pareto(size, alpha=1.0, periodic=False): W = np.zeros([size, size]) p = pareto_distribution(size, alpha=alpha) if periodic: middle = size // 2 if size % 2: # Odd p = np.concatenate([p[: middle + 1], np.flip(p[1 : middle + 1])]) else: # Even p = np.concatenate([p[: middle + 1], np.flip(p[1:middle])]) for k, w in zip(range(size), p, strict=False): W += np.eye(size, k=k) * w if k: W += np.eye(size, k=-k) * w return W
[docs] def potentiate_self(W, node_indicator, amount=0.0): potentiation = np.eye(len(W)) * node_indicator * amount return W + potentiation
[docs] def potentiate_laterals(W, node_indicator, amount=0.0): W = W.copy() for i, j in product(range(W.shape[0]), range(W.shape[1])): if node_indicator[i] != node_indicator[j]: W[i, j] += amount return W
[docs] def lateral_indices_from(W): n = len(W) idx = list(range(n)) rows = np.repeat(idx, n - 1) cols = np.concatenate([idx[:i] + idx[i + 1 :] for i in idx]) return rows, cols
[docs] def get_laterals(W): return W * (1 - np.eye(len(W)))
[docs] def separate_weights(W, node_indicator): """Separate weights between two sets of nodes.""" hom = np.zeros(W.shape) het = np.zeros(W.shape) for i, j in product(range(W.shape[0]), range(W.shape[1])): if node_indicator[i] == node_indicator[j]: hom[i, j] = W[i, j] else: het[i, j] = W[i, j] return hom, het
[docs] def on_weights(W, node_indicator): weights = np.zeros(W.shape) nodes = np.arange(len(node_indicator))[np.array(node_indicator, dtype=bool)] ix = np.ix_(nodes, nodes) weights[ix] = W[ix] return weights
[docs] def get_cross_laterals(W, node_indicator): return separate_weights(get_laterals(W), node_indicator)
[docs] def symmetric_triu(W): return np.triu(W) + np.tril(W.T, k=-1)
[docs] def compensatory_potentiation(W, node_indicator, self_amount=0.0, lateral_amount=0.0): node_indicator = np.array(node_indicator) self_potentiation = self_amount * np.eye(len(W)) * node_indicator _, het = separate_weights(W, node_indicator) het_laterals = get_laterals(het) on_laterals = get_laterals(on_weights(W, node_indicator)) # Adjust ON-laterals proportionally to output on_proportion = normalize_outputs(on_laterals) # Make lateral potentiation symmetric on_proportion = symmetric_triu(on_proportion) on_potentiation = lateral_amount * on_proportion # Compensate heterogeneous laterals proportionally to output het_proportion = normalize_outputs(het_laterals) # Make lateral potentiation symmetric het_proportion = symmetric_triu(het_proportion) het_potentiation = -1 * (self_amount + lateral_amount) * het_proportion potentiation = self_potentiation + on_potentiation + het_potentiation # Potentiate self loops to offset reduction in input compensatory_self_potentiation = ( -1 * np.eye(len(W)) * potentiation.sum(axis=0, keepdims=True) ) return W + potentiation + compensatory_self_potentiation
[docs] def compensatory_pareto( size, alpha, periodic, normalize_input_weights, w_self_potentiation, w_lateral_potentiation, layer_state, **kwargs, ): return compensatory_potentiation( pareto( size, alpha=alpha, periodic=periodic, normalize=normalize_input_weights, # pyright: ignore[reportCallIssue] - Added by @_optionally_normalize_inputs decorator ), node_indicator=layer_state, self_amount=w_self_potentiation, lateral_amount=w_lateral_potentiation, )
[docs] @_optionally_normalize_inputs def join_weights(weights_1, weights_2, weights_feedforward, weights_feedback): N, M = weights_1.shape[0], weights_2.shape[0] W = np.zeros([N + M] * 2) W[:N, :N] = weights_1 W[N:, N:] = weights_2 W[:N, N:] = weights_feedforward W[N:, :N] = weights_feedback return W
[docs] @_optionally_normalize_inputs def bridge(weights1, weights2, w_forward=1, w_back=0.0): N, M = weights1.shape[0], weights2.shape[0] feedforward = np.zeros([N, M]) feedforward[0, 0] = w_forward feedback = np.zeros([M, N]) feedback[0, 0] = w_back return join_weights(weights1, weights2, feedforward, feedback)
[docs] def copy_loop(size): W = np.eye(size, size, k=1) W[-1, 0] = 1.0 return W