File size: 11,404 Bytes
d4fff7c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
"""
Mathematical utilities for FlowNet.
Wave mechanics, topology helpers, and novel computation primitives.
"""

import torch
import torch.nn.functional as F
import numpy as np
from typing import Tuple, List, Optional


# =============================================================================
# Wave Mathematics
# =============================================================================

def complex_wave(amplitude: torch.Tensor, phase: torch.Tensor) -> torch.Tensor:
    """Create a complex wave from amplitude and phase.
    
    wave = amplitude * exp(i * phase)
    
    This is the fundamental representation for wave-based processing.
    Real and imaginary parts encode different aspects of meaning.
    """
    return amplitude * torch.exp(1j * phase)


def wave_interference(wave1: torch.Tensor, wave2: torch.Tensor) -> torch.Tensor:
    """Compute interference between two waves.
    
    Constructive interference (in-phase) → amplified signal
    Destructive interference (out-of-phase) → cancelled signal
    
    Returns the interference pattern as a real tensor.
    """
    interference = wave1 + wave2
    return torch.abs(interference)


def phase_coherence(phases: torch.Tensor, dim: int = -1) -> torch.Tensor:
    """Measure how synchronized a set of phases are.
    
    Returns 1.0 if all phases are identical, 0.0 if uniformly distributed.
    This measures "semantic alignment" between particles.
    
    Uses the order parameter from Kuramoto model:
        R = |1/N * sum(exp(i * theta_j))|
    """
    complex_phases = torch.exp(1j * phases)
    order_param = torch.abs(torch.mean(complex_phases, dim=dim))
    return order_param


def resonance_detect(
    wave_field: torch.Tensor,
    min_amplitude: float = 0.1,
    coherence_threshold: float = 0.7
) -> List[torch.Tensor]:
    """Detect standing wave patterns (resonances) in a wave field.
    
    Resonances represent stable meaning patterns — when multiple particles
    oscillate in sync, their shared meaning is amplified.
    
    Returns list of resonance regions (tensors of particle indices).
    """
    amplitudes = torch.abs(wave_field)
    phases = torch.angle(wave_field)
    
    # Find high-amplitude regions
    active_mask = amplitudes > min_amplitude
    
    # Group by phase similarity (particles that are "in sync")
    n = phases.shape[0]
    visited = torch.zeros(n, dtype=torch.bool)
    resonances = []
    
    phase_diff_matrix = torch.abs(
        phases.unsqueeze(0) - phases.unsqueeze(1)
    )
    # Wrap to [0, pi]
    phase_diff_matrix = torch.min(phase_diff_matrix, 2 * np.pi - phase_diff_matrix)
    
    for i in range(n):
        if visited[i] or not active_mask[i]:
            continue
        # Find all particles in phase with particle i
        in_phase = (phase_diff_matrix[i] < coherence_threshold) & active_mask
        resonances.append(torch.where(in_phase)[0])
        visited[in_phase] = True
    
    return resonances


def wave_propagate(
    source_amplitude: torch.Tensor,
    source_phase: torch.Tensor,
    distance: torch.Tensor,
    decay_rate: float = 0.1,
    wavelength: float = 1.0
) -> Tuple[torch.Tensor, torch.Tensor]:
    """Propagate a wave through space with distance-based decay.
    
    Waves carry information across the particle field without needing
    direct pairwise attention. Information travels at wave speed.
    
    Returns (amplitude_at_distance, phase_at_distance).
    """
    # Amplitude decays with distance (inverse square-ish)
    received_amplitude = source_amplitude / (1.0 + decay_rate * distance)
    
    # Phase shifts with distance (propagation delay)
    received_phase = source_phase + 2 * np.pi * distance / wavelength
    
    return received_amplitude, received_phase


# =============================================================================
# Topological Mathematics
# =============================================================================

def pairwise_distances(positions: torch.Tensor) -> torch.Tensor:
    """Compute pairwise Euclidean distances between particle positions."""
    diff = positions.unsqueeze(0) - positions.unsqueeze(1)
    return torch.norm(diff, dim=-1)


def build_adjacency(
    positions: torch.Tensor,
    threshold: float = 1.0,
    use_bonds: Optional[torch.Tensor] = None
) -> torch.Tensor:
    """Build adjacency matrix from particle positions.
    
    If use_bonds is provided, uses bond strengths instead of distance.
    """
    if use_bonds is not None:
        return use_bonds
    
    distances = pairwise_distances(positions)
    adjacency = (distances < threshold).float()
    # Remove self-loops
    adjacency.fill_diagonal_(0)
    return adjacency


def compute_betti_numbers(adjacency: torch.Tensor, max_dim: int = 2) -> List[int]:
    """Compute Betti numbers (topological features) from adjacency.
    
    β₀ = number of connected components
    β₁ = number of loops/holes
    β₂ = number of voids
    
    This is a simplified computation for the prototype.
    Full persistent homology would use giotto-tda or similar.
    """
    n = adjacency.shape[0]
    
    # β₀: connected components via BFS
    visited = torch.zeros(n, dtype=torch.bool)
    beta_0 = 0
    for i in range(n):
        if visited[i]:
            continue
        beta_0 += 1
        queue = [i]
        while queue:
            node = queue.pop(0)
            if visited[node]:
                continue
            visited[node] = True
            neighbors = torch.where(adjacency[node] > 0)[0]
            queue.extend(neighbors[~visited[neighbors]].tolist())
    
    # β₁: approximate cycle count (edges - vertices + components)
    edges = torch.sum(adjacency > 0).item() / 2
    beta_1 = max(0, int(edges - n + beta_0))
    
    # β₂: placeholder (would need simplicial complex computation)
    beta_2 = 0
    
    return [beta_0, beta_1, beta_2]


def topological_signature(adjacency: torch.Tensor) -> torch.Tensor:
    """Compute a topological signature vector for memory comparison.
    
    This is a fixed-size representation of the topology that can be
    used for similarity search in topological memory.
    """
    betti = compute_betti_numbers(adjacency)
    
    # Additional topological features
    n = adjacency.shape[0]
    degrees = torch.sum(adjacency > 0, dim=-1).float()
    
    features = torch.tensor([
        float(betti[0]),           # connected components
        float(betti[1]),           # loops
        float(betti[2]),           # voids
        n,                         # size
        torch.mean(degrees),       # average degree
        torch.std(degrees),        # degree variance
        torch.max(degrees),        # max degree
        float(torch.sum(adjacency > 0).item()) / 2,  # edge count
    ], dtype=torch.float32)
    
    return features


# =============================================================================
# Dynamical Systems
# =============================================================================

def kuramoto_step(
    phases: torch.Tensor,
    frequencies: torch.Tensor,
    coupling: torch.Tensor,
    dt: float = 0.01
) -> torch.Tensor:
    """One step of the Kuramoto model for phase synchronization.
    
    dθᵢ/dt = ωᵢ + Σⱼ Kᵢⱼ sin(θⱼ - θᵢ)
    
    This governs how particles synchronize their phases —
    strongly coupled particles synchronize, weakly coupled drift apart.
    Synchronization = semantic alignment.
    """
    n = phases.shape[0]
    phase_diff = phases.unsqueeze(0) - phases.unsqueeze(1)  # [n, n]
    
    # Coupling force (sin of phase difference)
    sync_force = torch.sum(coupling * torch.sin(-phase_diff), dim=-1)  # [n]
    
    # Update phases
    new_phases = phases + dt * (frequencies + sync_force)
    
    # Wrap to [0, 2π]
    new_phases = new_phases % (2 * np.pi)
    
    return new_phases


def lennard_jones_force(
    positions: torch.Tensor,
    epsilon: float = 1.0,
    sigma: float = 1.0,
    cutoff: float = 3.0
) -> torch.Tensor:
    """Compute Lennard-Jones-like forces between particles.
    
    Attraction at medium range, repulsion at close range.
    This creates natural "preferred distances" between particles —
    semantically related particles settle at stable distances.
    
    F = 24ε * [(σ/r)⁶ * (2(σ/r)⁶ - 1)] / r
    
    Returns force vectors for each particle [n, dim].
    """
    n = positions.shape[0]
    diff = positions.unsqueeze(0) - positions.unsqueeze(1)  # [n, n, dim]
    distances = torch.norm(diff, dim=-1, keepdim=True)  # [n, n, 1]
    distances = torch.clamp(distances, min=0.1)  # avoid division by zero
    
    # Apply cutoff
    mask = (distances.squeeze(-1) < cutoff).float()
    mask.fill_diagonal_(0)
    
    # LJ force magnitude
    sr6 = (sigma / distances) ** 6
    force_mag = 24 * epsilon * sr6 * (2 * sr6 - 1) / distances
    
    # Force vectors (direction from j to i)
    direction = diff / distances
    forces = force_mag * direction * mask.unsqueeze(-1)
    
    return forces.sum(dim=1)  # [n, dim]


def simulated_annealing_step(
    state: torch.Tensor,
    energy_fn,
    temperature: float,
    perturbation_scale: float = 0.1
) -> Tuple[torch.Tensor, float]:
    """One step of simulated annealing for energy minimization.
    
    Used by the Consistency Field to find low-energy (true) states.
    High temperature → accept worse states (explore)
    Low temperature → only accept better states (exploit)
    """
    current_energy = energy_fn(state)
    
    # Propose perturbation
    perturbation = perturbation_scale * torch.randn_like(state)
    proposed_state = state + perturbation
    proposed_energy = energy_fn(proposed_state)
    
    # Metropolis criterion
    delta_e = proposed_energy - current_energy
    if delta_e < 0:
        # Always accept improvements
        return proposed_state, proposed_energy
    else:
        # Accept worse states with probability exp(-ΔE/T)
        acceptance_prob = torch.exp(-delta_e / max(temperature, 1e-8))
        if torch.rand(1).item() < acceptance_prob.item():
            return proposed_state, proposed_energy
        else:
            return state, current_energy


# =============================================================================
# Activation & Normalization (Novel)
# =============================================================================

def phase_activation(x: torch.Tensor) -> torch.Tensor:
    """Activation function that maps to phase space [0, 2π].
    
    Unlike ReLU/GELU, this preserves periodic structure.
    """
    return torch.sigmoid(x) * 2 * np.pi


def amplitude_activation(x: torch.Tensor) -> torch.Tensor:
    """Activation for amplitudes — always positive, bounded.
    
    Uses softplus to ensure positivity with smooth gradients.
    """
    return F.softplus(x)


def topological_normalize(x: torch.Tensor, dim: int = -1) -> torch.Tensor:
    """Normalize while preserving topological structure.
    
    Instead of LayerNorm (which destroys phase information),
    normalize amplitudes independently of phases.
    """
    # Split into magnitude and direction
    magnitude = torch.norm(x, dim=dim, keepdim=True)
    direction = x / (magnitude + 1e-8)
    
    # Normalize magnitude to unit sphere
    normalized_magnitude = F.softmax(magnitude, dim=dim)
    
    return direction * normalized_magnitude