brain-network-controllability

star 1

结构脑网络可控性分析实用指南。基于网络控制理论计算平均可控性、模态可控性、最小控制能量等指标,研究脑网络状态转移能力。适用于脑网络分析、神经调控研究、认知控制研究。触发词:脑网络可控性、控制理论、结构连接、控制能量、平均可控性、模态可控性、controllability、control theory、structural brain network。

hiyenwong By hiyenwong schedule Updated 6/3/2026

name: brain-network-controllability description: 结构脑网络可控性分析实用指南。基于网络控制理论计算平均可控性、模态可控性、最小控制能量等指标,研究脑网络状态转移能力。适用于脑网络分析、神经调控研究、认知控制研究。触发词:脑网络可控性、控制理论、结构连接、控制能量、平均可控性、模态可控性、controllability、control theory、structural brain network。 user-invocable: true

结构脑网络可控性分析方法论

来源论文: arXiv:1908.03514 - A practical guide to methodological considerations in the controllability of structural brain networks

核心方法论

基于网络控制理论的脑网络可控性框架:

1. 线性动力学模型

[\mathbf{x}(t+1) = \mathbf{A}\mathbf{x}(t) + \mathbf{B}\mathbf{u}(t)]

其中:

  • (\mathbf{x}(t)):脑区状态向量
  • (\mathbf{A}):结构连接矩阵(状态转移矩阵)
  • (\mathbf{B}):控制输入矩阵
  • (\mathbf{u}(t)):控制信号

2. 可控性指标

平均可控性(Average Controllability)

  • 驱动系统到附近状态的能力
  • 与网络整合和扩散相关

模态可控性(Modal Controllability)

  • 驱动系统到远距离状态的能力
  • 与网络分离和专门化相关

控制能量(Control Energy)

  • 实现状态转移所需的最小能量
  • 与认知努力相关

Python 实现

import numpy as np
from scipy.linalg import eig, inv, solve, svd
from scipy.integrate import solve_ivp
from typing import Dict, Tuple, List, Optional
from dataclasses import dataclass
import warnings


@dataclass
class ControllabilityConfig:
    """可控性分析配置"""
    normalize: bool = True           # 是否归一化可控性指标
    energy_horizon: float = 1.0      # 控制能量计算时间范围
    timesteps: int = 100             # 状态转移时间步数
    regularization: float = 1e-6     # 正则化参数
    eigenvalue_threshold: float = 0.99  # 特征值阈值(稳定性)


class BrainNetworkControllability:
    """脑网络可控性分析器"""
    
    def __init__(self, A: np.ndarray, config: Optional[ControllabilityConfig] = None):
        """
        Args:
            A: 结构连接矩阵 (n x n)
            config: 分析配置
        """
        self.A = A.copy().astype(float)
        self.n = A.shape[0]
        self.config = config or ControllabilityConfig()
        
        # 稳定性处理
        self._stabilize_matrix()
        
    def _stabilize_matrix(self):
        """确保系统稳定(谱半径 < 1)"""
        eigenvalues = np.linalg.eigvals(self.A)
        spectral_radius = np.max(np.abs(eigenvalues))
        
        if spectral_radius >= 1:
            # 缩放矩阵
            self.A = self.A / spectral_radius * self.config.eigenvalue_threshold
            warnings.warn(f"Matrix scaled for stability. Original spectral radius: {spectral_radius:.4f}")
            
    def gramian(self, T: int = None) -> np.ndarray:
        """计算可控性格拉姆矩阵
        
        W_c = sum_{t=0}^{T-1} A^t @ B @ B^T @ (A^t)^T
        
        Args:
            T: 时间范围
            
        Returns:
            W_c: 可控性格拉姆矩阵 (n x n)
        """
        if T is None:
            T = self.config.timesteps
            
        # 假设B = I(所有节点都是控制节点)
        B = np.eye(self.n)
        
        W_c = np.zeros((self.n, self.n))
        A_power = np.eye(self.n)
        
        for t in range(T):
            W_c += A_power @ B @ B.T @ A_power.T
            A_power = A_power @ self.A
            
        return W_c
    
    def average_controllability(self, node_indices: List[int] = None) -> np.ndarray:
        """计算平均可控性
        
        对于控制节点i,平均可控性定义为:
        AC_i = trace(W_c^{(i)})
        
        其中 W_c^{(i)} 是以节点i为控制输入的格拉姆矩阵
        
        Args:
            node_indices: 要分析的节点索引,None则分析所有节点
            
        Returns:
            ac: 每个节点的平均可控性 (n,)
        """
        if node_indices is None:
            node_indices = list(range(self.n))
            
        ac = np.zeros(len(node_indices))
        T = self.config.timesteps
        
        for idx, i in enumerate(node_indices):
            # B矩阵:只有第i列非零
            B = np.zeros((self.n, 1))
            B[i, 0] = 1
            
            # 计算格拉姆矩阵
            W_c = np.zeros((self.n, self.n))
            A_power = np.eye(self.n)
            
            for t in range(T):
                W_c += A_power @ B @ B.T @ A_power.T
                A_power = A_power @ self.A
                
            # 平均可控性 = trace(W_c)
            ac[idx] = np.trace(W_c)
            
        if self.config.normalize:
            ac = ac / ac.max() if ac.max() > 0 else ac
            
        return ac
    
    def modal_controllability(self, node_indices: List[int] = None) -> np.ndarray:
        """计算模态可控性
        
        基于特征向量计算:
        phi_i = 1 - sum_j (A_ij)^2 * lambda_j^2 / ||v_j||^2
        
        其中 lambda_j 是特征值,v_j 是特征向量
        
        Args:
            node_indices: 要分析的节点索引
            
        Returns:
            mc: 每个节点的模态可控性 (n,)
        """
        if node_indices is None:
            node_indices = list(range(self.n))
            
        # 特征分解
        eigenvalues, eigenvectors = eig(self.A)
        eigenvalues = np.real(eigenvalues)
        eigenvectors = np.real(eigenvectors)
        
        # 计算每个节点的模态可控性
        mc = np.zeros(len(node_indices))
        
        for idx, i in enumerate(node_indices):
            # 节点i的模态可控性
            # phi_i = sum_j (1 - lambda_j^2) * v_{i,j}^2
            mc[idx] = np.sum(
                (1 - eigenvalues**2) * eigenvectors[i, :]**2
            )
            
        if self.config.normalize:
            mc = mc / mc.max() if mc.max() > 0 else mc
            
        return mc
    
    def minimum_control_energy(self, x0: np.ndarray, xf: np.ndarray,
                               T: float = 1.0, 
                               controlled_nodes: List[int] = None) -> float:
        """计算最小控制能量
        
        E = integral_0^T u(t)^T u(t) dt
        
        使用最优控制理论求解
        
        Args:
            x0: 初始状态 (n,)
            xf: 目标状态 (n,)
            T: 控制时间
            controlled_nodes: 控制节点索引
            
        Returns:
            E: 最小控制能量
        """
        if controlled_nodes is None:
            controlled_nodes = list(range(self.n))
            
        # 控制矩阵B
        B = np.zeros((self.n, len(controlled_nodes)))
        for j, i in enumerate(controlled_nodes):
            B[i, j] = 1
            
        # 计算控制能量
        # 离散时间控制
        timesteps = int(T * 100)
        dt = T / timesteps
        
        # 状态转移
        def dynamics(t, x, u_func):
            u = u_func(t)
            return self.A @ x + B @ u
        
        # 最优控制:u*(t) = B^T @ exp(A^T(T-t)) @ W^{-1} @ (xf - exp(AT) @ x0)
        # 其中 W = integral_0^T exp(At) @ B @ B^T @ exp(A^T t) dt
        
        # 简化计算:使用伪逆
        delta_x = xf - np.linalg.matrix_power(self.A, timesteps) @ x0
        
        # 计算可控性格拉姆矩阵
        W = np.zeros((self.n, self.n))
        A_power = np.eye(self.n)
        
        for t in range(timesteps):
            W += A_power @ B @ B.T @ A_power.T * dt
            A_power = A_power @ self.A
            
        # 正则化
        W += self.config.regularization * np.eye(self.n)
        
        # 最优控制输入
        try:
            v = solve(W, delta_x, assume_a='pos')
        except np.linalg.LinAlgError:
            v = np.linalg.lstsq(W, delta_x, rcond=None)[0]
            
        # 计算能量
        E = delta_x.T @ v
        
        return max(0, E)
    
    def optimal_control_trajectory(self, x0: np.ndarray, xf: np.ndarray,
                                    T: float = 1.0,
                                    controlled_nodes: List[int] = None) -> Dict:
        """计算最优控制轨迹
        
        Args:
            x0: 初始状态
            xf: 目标状态
            T: 控制时间
            controlled_nodes: 控制节点
            
        Returns:
            trajectory: 包含状态和控制轨迹的字典
        """
        if controlled_nodes is None:
            controlled_nodes = list(range(self.n))
            
        n_controls = len(controlled_nodes)
        timesteps = int(T * 100)
        dt = T / timesteps
        
        # B矩阵
        B = np.zeros((self.n, n_controls))
        for j, i in enumerate(controlled_nodes):
            B[i, j] = 1
            
        # 计算格拉姆矩阵
        W = np.zeros((self.n, self.n))
        A_power = np.eye(self.n)
        
        for t in range(timesteps):
            W += A_power @ B @ B.T @ A_power.T * dt
            A_power = A_power @ self.A
            
        W += self.config.regularization * np.eye(self.n)
        
        # 状态轨迹
        t = np.linspace(0, T, timesteps + 1)
        x = np.zeros((timesteps + 1, self.n))
        x[0] = x0
        
        # 控制轨迹
        u = np.zeros((timesteps, n_controls))
        
        # 最优控制计算
        delta_x = xf - np.linalg.matrix_power(self.A, timesteps) @ x0
        
        try:
            v = solve(W, delta_x, assume_a='pos')
        except np.linalg.LinAlgError:
            v = np.linalg.lstsq(W, delta_x, rcond=None)[0]
            
        # 模拟轨迹
        A_power = np.eye(self.n)
        for i in range(timesteps):
            # 控制输入
            exp_At = np.linalg.matrix_power(self.A, timesteps - i)
            u[i] = B.T @ exp_At.T @ v
            # 更新状态
            x[i+1] = self.A @ x[i] + B @ u[i]
            
        return {
            'time': t,
            'states': x,
            'controls': u,
            'energy': np.sum(u**2) * dt
        }
    
    def radial_propagation_connectivity(self, 
                                        distances: np.ndarray) -> np.ndarray:
        """计算径向传播连接性
        
        考虑活动通过相邻组织的径向传播
        
        Args:
            distances: 脑区之间的物理距离矩阵 (n x n)
            
        Returns:
            A_radial: 径向传播连接矩阵
        """
        # 径向传播因子
        # 连接强度随距离衰减
        sigma = np.mean(distances[distances > 0]) / 2  # 特征长度
        
        A_radial = np.exp(-distances**2 / (2 * sigma**2))
        
        # 结合结构连接
        A_combined = self.A * A_radial
        
        return A_combined
    
    def energy_landscape_complexity(self, 
                                    n_samples: int = 100) -> Dict:
        """计算能量景观复杂度
        
        随机采样状态转移,分析能量分布
        
        Args:
            n_samples: 采样数量
            
        Returns:
            metrics: 能量景观度量
        """
        energies = []
        
        for _ in range(n_samples):
            # 随机初始和目标状态
            x0 = np.random.randn(self.n)
            xf = np.random.randn(self.n)
            
            # 计算最小控制能量
            E = self.minimum_control_energy(x0, xf)
            energies.append(E)
            
        energies = np.array(energies)
        
        return {
            'mean_energy': np.mean(energies),
            'std_energy': np.std(energies),
            'min_energy': np.min(energies),
            'max_energy': np.max(energies),
            'energy_range': np.max(energies) - np.min(energies),
            'energy_skewness': self._skewness(energies),
            'energy_kurtosis': self._kurtosis(energies)
        }
    
    @staticmethod
    def _skewness(x: np.ndarray) -> float:
        """计算偏度"""
        return np.mean((x - np.mean(x))**3) / np.std(x)**3
    
    @staticmethod
    def _kurtosis(x: np.ndarray) -> float:
        """计算峰度"""
        return np.mean((x - np.mean(x))**4) / np.std(x)**4 - 3
    
    def full_analysis(self, 
                      x0: np.ndarray = None,
                      xf: np.ndarray = None) -> Dict:
        """完整可控性分析
        
        Returns:
            results: 所有可控性指标
        """
        results = {}
        
        # 1. 平均可控性
        results['average_controllability'] = self.average_controllability()
        
        # 2. 模态可控性
        results['modal_controllability'] = self.modal_controllability()
        
        # 3. 可控性权衡
        results['controllability_tradeoff'] = -np.corrcoef(
            results['average_controllability'],
            results['modal_controllability']
        )[0, 1]
        
        # 4. 控制能量(如果提供了状态)
        if x0 is not None and xf is not None:
            results['minimum_energy'] = self.minimum_control_energy(x0, xf)
            results['optimal_trajectory'] = self.optimal_control_trajectory(x0, xf)
            
        # 5. 能量景观复杂度
        results['energy_landscape'] = self.energy_landscape_complexity(n_samples=50)
        
        # 6. 网络统计
        results['network_stats'] = {
            'spectral_radius': np.max(np.abs(np.linalg.eigvals(self.A))),
            'density': np.mean(self.A > 0),
            'mean_weight': np.mean(self.A[self.A > 0]) if np.any(self.A > 0) else 0
        }
        
        return results


def generate_modular_network(n_nodes: int, n_modules: int = 4,
                            p_intra: float = 0.3, p_inter: float = 0.05,
                            weight_mean: float = 0.5, weight_std: float = 0.2) -> np.ndarray:
    """生成模块化脑网络
    
    Args:
        n_nodes: 节点数量
        n_modules: 模块数量
        p_intra: 模块内连接概率
        p_inter: 模块间连接概率
        weight_mean: 连接权重均值
        weight_std: 连接权重标准差
        
    Returns:
        A: 连接矩阵
    """
    A = np.zeros((n_nodes, n_nodes))
    module_size = n_nodes // n_modules
    
    rng = np.random.default_rng(42)
    
    for i in range(n_nodes):
        for j in range(n_nodes):
            if i == j:
                continue
                
            # 确定是否在同一模块
            i_module = i // module_size
            j_module = j // module_size
            
            if i_module == j_module:
                p = p_intra
            else:
                p = p_inter
                
            # 建立连接
            if rng.random() < p:
                A[i, j] = np.clip(
                    rng.normal(weight_mean, weight_std),
                    0.1, 1.0
                )
                
    # 对称化
    A = (A + A.T) / 2
    
    return A


# 使用示例
def example_analysis():
    """示例:脑网络可控性分析"""
    # 生成模拟连接矩阵
    n_nodes = 50
    A = generate_modular_network(n_nodes, n_modules=5)
    
    # 创建分析器
    analyzer = BrainNetworkControllability(A)
    
    # 完整分析
    results = analyzer.full_analysis()
    
    print("="*60)
    print("脑网络可控性分析结果")
    print("="*60)
    
    print(f"\n网络统计:")
    print(f"  谱半径: {results['network_stats']['spectral_radius']:.4f}")
    print(f"  连接密度: {results['network_stats']['density']:.4f}")
    print(f"  平均权重: {results['network_stats']['mean_weight']:.4f}")
    
    print(f"\n可控性指标:")
    ac = results['average_controllability']
    mc = results['modal_controllability']
    print(f"  平均可控性: mean={ac.mean():.4f}, std={ac.std():.4f}")
    print(f"  模态可控性: mean={mc.mean():.4f}, std={mc.std():.4f}")
    print(f"  可控性权衡 (负相关): r={results['controllability_tradeoff']:.4f}")
    
    print(f"\n能量景观:")
    el = results['energy_landscape']
    print(f"  平均能量: {el['mean_energy']:.4f}")
    print(f"  能量标准差: {el['std_energy']:.4f}")
    print(f"  能量范围: {el['energy_range']:.4f}")
    
    # 识别高可控性节点
    print(f"\n高平均可控性节点 (前5):")
    top_ac = np.argsort(ac)[-5:][::-1]
    for i, node in enumerate(top_ac):
        print(f"  {i+1}. 节点 {node}: AC={ac[node]:.4f}")
        
    print(f"\n高模态可控性节点 (前5):")
    top_mc = np.argsort(mc)[-5:][::-1]
    for i, node in enumerate(top_mc):
        print(f"  {i+1}. 节点 {node}: MC={mc[node]:.4f}")
        
    return analyzer, results


def example_state_transition():
    """示例:状态转移控制"""
    n_nodes = 20
    A = generate_modular_network(n_nodes)
    
    analyzer = BrainNetworkControllability(A)
    
    # 定义初始和目标状态
    x0 = np.zeros(n_nodes)
    x0[0:5] = 1.0  # 前5个节点激活
    
    xf = np.zeros(n_nodes)
    xf[15:20] = 1.0  # 后5个节点激活
    
    # 计算最优轨迹
    trajectory = analyzer.optimal_control_trajectory(x0, xf, T=1.0)
    
    print("\n状态转移控制:")
    print(f"  控制能量: {trajectory['energy']:.4f}")
    print(f"  时间步数: {len(trajectory['time'])}")
    print(f"  初始状态范数: {np.linalg.norm(x0):.4f}")
    print(f"  最终状态范数: {np.linalg.norm(trajectory['states'][-1]):.4f}")
    
    return trajectory


## Activation Keywords
- 脑网络可控性
- 控制理论
- 结构连接
- 控制能量
- 平均可控性
- 模态可控性
- controllability
- control theory
- structural brain network
- 能量景观

## Tools Used
- numpy
- scipy
- networkx
- matplotlib

## Instructions for Agents
1. 理解线性动力学模型:x(t+1) = Ax(t) + Bu(t)
2. 计算平均可控性:驱动系统到附近状态的能力
3. 计算模态可控性:驱动系统到远距离状态的能力
4. 计算控制能量:实现状态转移所需的最小能量
5. 分析可控性权衡:平均可控性与模态可控性的负相关关系

## Examples
```python
# 使用示例
from brain_network_controllability import BrainNetworkControllability, generate_modular_network

# 1. 生成模块化脑网络
A = generate_modular_network(n_nodes=50, n_modules=5)

# 2. 创建可控性分析器
analyzer = BrainNetworkControllability(A)

# 3. 完整分析
results = analyzer.full_analysis()
print(f"平均可控性: mean={results['average_controllability'].mean():.4f}")
print(f"模态可控性: mean={results['modal_controllability'].mean():.4f}")

# 4. 状态转移控制
x0 = np.zeros(50)
xf = np.zeros(50)
xf[15:20] = 1.0
energy = analyzer.minimum_control_energy(x0, xf)

if name == "main": analyzer, results = example_analysis() print("\n" + "="*60) trajectory = example_state_transition()

Install via CLI
npx skills add https://github.com/hiyenwong/ai_collection --skill brain-network-controllability
Repository Details
star Stars 1
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator