Spaces:
Running
Running
File size: 7,030 Bytes
3bb804c |
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 |
"""
Antti's Superfluid Node - Simulates a 1D complex field with knots
Physics based on the 1D NLSE from knotiverse_interactive_viewer.py
Requires: pip install scipy
Place this file in the 'nodes' folder
"""
import numpy as np
from PyQt6 import QtGui
import cv2
import sys
import os
# --- This is the new, correct block ---
import __main__
BaseNode = __main__.BaseNode
PA_INSTANCE = getattr(__main__, "PA_INSTANCE", None)
# ------------------------------------
try:
from scipy.signal import hilbert
SCIPY_AVAILABLE = True
except ImportError:
SCIPY_AVAILABLE = False
print("Warning: AnttiSuperfluidNode requires 'scipy'.")
print("Please run: pip install scipy")
class AnttiSuperfluidNode(BaseNode):
NODE_CATEGORY = "Transform"
NODE_COLOR = QtGui.QColor(180, 80, 180) # Superfluid purple
def __init__(self, grid_size=512, coupling=0.5, nonlinear=0.8, damping=0.005):
super().__init__()
self.node_title = "Antti's Superfluid"
self.inputs = {
'signal_in': 'signal',
'coupling': 'signal',
'nonlinearity': 'signal',
'damping': 'signal'
}
self.outputs = {
'field_image': 'image',
'angular_momentum': 'signal',
'knot_count': 'signal'
}
# --- Parameters from knotiverse_interactive_viewer.py ---
self.L = int(grid_size)
self.dt = 0.05
self.detect_threshold = 0.5
self.saturation_threshold = 2.0
self.max_amplitude_clip = 1e3
# Default physics values (will be overridden by signals)
self.coupling = coupling
self.nonlinear = nonlinear
self.damping = damping
# --- Internal State ---
rng = np.random.default_rng()
self.psi = (rng.standard_normal(self.L) + 1j * rng.standard_normal(self.L)) * 0.01
# Seed with a pulse
x = np.arange(self.L)
p = self.L // 2
gauss = 1.0 * np.exp(-((x - p)**2) / (2 * 4**2))
self.psi += gauss * np.exp(1j * 2.0 * np.pi * rng.random())
self.knots = np.array([], dtype=int)
self.angular_momentum_out = 0.0
if not SCIPY_AVAILABLE:
self.node_title = "Superfluid (No SciPy!)"
def laplacian_1d(self, arr):
"""Discrete laplacian with periodic boundary."""
return np.roll(arr, -1) - 2*arr + np.roll(arr, 1)
def step(self):
if not SCIPY_AVAILABLE:
return
# --- Get inputs ---
signal_in = self.get_blended_input('signal_in', 'sum') or 0.0
coupling = self.get_blended_input('coupling', 'sum')
nonlinear = self.get_blended_input('nonlinearity', 'sum')
damping = self.get_blended_input('damping', 'sum')
# Use signal if connected, else use internal value
c = coupling if coupling is not None else self.coupling
n = nonlinear if nonlinear is not None else self.nonlinear
d = damping if damping is not None else self.damping
# --- Physics Step (from knotiverse_interactive_viewer.py) ---
lap = self.laplacian_1d(self.psi)
coupling_term = 1j * c * lap
amp = np.abs(self.psi)
sat = np.tanh(amp / self.saturation_threshold)
nonlin_term = -1j * n * (sat**2) * self.psi
damping_term = -d * self.psi
self.psi = self.psi + self.dt * (coupling_term + nonlin_term + damping_term)
# --- Resonance from input signal ---
# "Pluck" the center of the string
self.psi[self.L // 2] += signal_in * 0.5 # Scale input
# Stability checks
self.psi = np.nan_to_num(self.psi, nan=0.0, posinf=0.0, neginf=0.0)
amp_new = np.abs(self.psi)
over = amp_new > self.max_amplitude_clip
if np.any(over):
self.psi[over] = self.psi[over] * (self.max_amplitude_clip / amp_new[over])
amp_now = np.abs(self.psi)
# --- Knot Detection ---
left = np.roll(amp_now, 1)
right = np.roll(amp_now, -1)
mask_thresh = amp_now > self.detect_threshold
mask_local_max = (amp_now >= left) & (amp_now >= right)
self.knots = np.where(mask_thresh & mask_local_max)[0]
self.knot_count_out = len(self.knots)
# --- Angular Momentum ---
grad_psi = np.roll(self.psi, -1) - np.roll(self.psi, 1)
moment_density = np.imag(np.conj(self.psi) * grad_psi)
self.angular_momentum_out = float(np.sum(moment_density))
def get_output(self, port_name):
if port_name == 'field_image':
return self._draw_field_image(as_float=True)
elif port_name == 'angular_momentum':
return self.angular_momentum_out
elif port_name == 'knot_count':
return self.knot_count_out
return None
def _draw_field_image(self, as_float=False):
h, w = 64, self.L
img_color = np.zeros((h, w, 3), dtype=np.uint8)
# Get field data
amp_now = np.abs(self.psi)
phase_now = np.angle(hilbert(self.psi.real))
# Normalize
amp_norm = np.clip(amp_now / self.saturation_threshold, 0, 1)
phase_norm = (phase_now + np.pi) / (2 * np.pi)
# Draw amplitude (top half) and phase (bottom half)
h_half = h // 2
for x in range(w):
# Amplitude (Cyan)
y_amp = int((h_half - 1) - amp_norm[x] * (h_half - 1))
img_color[y_amp, x] = (255, 255, 0) # BGR for Cyan
# Phase (Magenta)
y_phase = int(h_half + (h_half - 1) - phase_norm[x] * (h_half - 1))
img_color[y_phase, x] = (255, 0, 255) # BGR for Magenta
# Draw center line
cv2.line(img_color, (0, h // 2), (w, h // 2), (50, 50, 50), 1)
# Draw knots (Red)
for kx in self.knots:
ky = int((h_half - 1) - amp_norm[kx] * (h_half - 1))
cv2.circle(img_color, (kx, ky), 3, (0, 0, 255), -1) # BGR for Red
if as_float:
return img_color.astype(np.float32) / 255.0
img_color = np.ascontiguousarray(img_color)
return QtGui.QImage(img_color.data, w, h, 3*w, QtGui.QImage.Format.Format_BGR888)
def get_display_image(self):
return self._draw_field_image(as_float=False)
def get_config_options(self):
return [
("Grid Size", "L", self.L, None),
("Knot Threshold", "detect_threshold", self.detect_threshold, None),
("Coupling", "coupling", self.coupling, None),
("Nonlinearity", "nonlinear", self.nonlinear, None),
("Damping", "damping", self.damping, None),
] |