File size: 2,355 Bytes
5b4a3bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
"""Higher-order Hypergraph Message Passing and matched graph baseline comparisons."""
from __future__ import annotations

from typing import Any, Dict, List, Optional, Tuple
import numpy as np


class HypergraphMessagePassing:
    """Implements higher-order hypergraph communication:
    
        m_i = sum_{e in E, i in e} phi_e({x_j : j in e})
        
    where phi_e computes non-linear higher-order interactions across all nodes in hyperedge e.
    """

    def __init__(self, hyperedges: List[List[int]], node_dim: int = 4):
        self.hyperedges = hyperedges
        self.node_dim = node_dim

    def forward(self, X: np.ndarray) -> np.ndarray:
        """Computes hypergraph message passing across all nodes."""
        num_nodes = X.shape[0]
        messages = np.zeros_like(X)

        for edge in self.hyperedges:
            if not edge:
                continue
            # Higher-order interaction: product or non-linear combination across member nodes
            edge_states = X[edge]  # shape: (order, node_dim)
            # Synergistic interaction: order-scaled non-linear aggregation
            order = len(edge)
            synergy = np.prod(np.tanh(edge_states), axis=0) * np.sqrt(order)
            mean_state = np.mean(edge_states, axis=0)
            combined_message = 0.5 * mean_state + 0.5 * synergy

            for node_idx in edge:
                messages[node_idx] += combined_message

        return messages


class MatchedGraphBaseline:
    """Parameter-matched standard pairwise graph baseline for controlled comparison."""

    def __init__(self, hyperedges: List[List[int]], num_nodes: int, node_dim: int = 4):
        self.num_nodes = num_nodes
        self.node_dim = node_dim
        # Project hyperedges into pairwise clique adjacency
        self.adj = np.zeros((num_nodes, num_nodes), dtype=np.float64)
        for edge in hyperedges:
            for u in edge:
                for v in edge:
                    if u != v:
                        self.adj[u, v] += 1.0 / max(1, len(edge) - 1)
        # Normalize adjacency
        row_sums = self.adj.sum(axis=1, keepdims=True)
        row_sums[row_sums == 0] = 1.0
        self.adj /= row_sums

    def forward(self, X: np.ndarray) -> np.ndarray:
        """Standard linear pairwise message passing: m_i = sum_j W_ij x_j."""
        return self.adj @ X