Physics — Stochastic PDE Solvers + Wolfram Hypergraph
Physics simulation using stochastic methods: heat equation via random walks (Feynman-Kac) and Wolfram Physics Project hypergraph evolution.
StochasticHeatSolver — 1D Heat Equation via Random Walks
Solves the 1D heat equation using the Feynman-Kac connection between diffusion PDEs and Brownian motion. N random walkers perform discrete random walks on a 1D lattice; their density at time t approximates the temperature profile u(x,t).
Walker dynamics: at each step, move -1, 0, or +1 with probabilities [0.25, 0.5, 0.25]. Reflective boundary conditions (walkers clipped to [0, length-1]).
| Parameter |
Meaning |
length |
Lattice size (spatial resolution) |
num_walkers |
Number of random walkers (more = smoother profile) |
alpha |
Diffusion coefficient (reserved for future dt scaling) |
Methods: step() (advance one timestep), get_temperature_profile() → normalized density histogram.
WolframHypergraph — Discrete Space-Time Evolution
Simulates the Wolfram Physics Project hypergraph rewrite system. The universe is a set of hyperedges (relations between nodes). Evolution applies a rewrite rule:
{{x, y}, {y, z}} → {{x, z}, {x, w}, {y, w}}
This is triangle completion with a new node w. The graph grows at each step, and the effective spatial dimension emerges from the topology.
| Parameter |
Meaning |
edges |
Initial hyperedge list (list of int tuples) |
max_node_id |
Highest existing node ID |
Methods:
evolve(steps) — Apply rewrite rule for N steps
dimension_estimate() — Estimate effective dimension via BFS neighborhood growth: measures how |B(r)| scales with r, fits d from V(r) ~ r^d
Usage
from sc_neurocore.physics.heat import StochasticHeatSolver
from sc_neurocore.physics.wolfram_hypergraph import WolframHypergraph
import numpy as np
# Heat equation
solver = StochasticHeatSolver(length=100, num_walkers=10000, alpha=0.1)
solver.walkers[:] = 50 # point source at center
for _ in range(200):
solver.step()
profile = solver.get_temperature_profile() # diffused Gaussian-like
# Wolfram hypergraph
wh = WolframHypergraph(
edges=[(0, 1), (1, 2), (2, 3), (3, 4)],
max_node_id=4,
)
wh.evolve(steps=10)
print(f"Edges: {len(wh.edges)}, Nodes: {wh.max_node_id}")
print(f"Effective dimension: {wh.dimension_estimate():.2f}")
sc_neurocore.physics
sc_neurocore.physics -- Tier: research (experimental / research).
StochasticHeatSolver
Solves 1D Heat Equation using Stochastic Random Walks (Feynman-Kac).
Source code in src/sc_neurocore/physics/heat.py
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 | class StochasticHeatSolver:
"""
Solves 1D Heat Equation using Stochastic Random Walks (Feynman-Kac).
"""
def __init__(self, length: int, num_walkers: int, alpha: float):
self.length = length
self.walkers = np.random.randint(0, length, num_walkers)
self.alpha = alpha
def step(self) -> None:
"""
Move walkers.
"""
# Random step -1, 0, 1
steps = np.random.choice([-1, 0, 1], size=len(self.walkers), p=[0.25, 0.5, 0.25])
self.walkers += steps
# Boundary conditions (Reflective)
self.walkers = np.clip(self.walkers, 0, self.length - 1)
def get_temperature_profile(self) -> np.ndarray[Any, Any]:
"""
Convert walker density to temperature.
"""
density, _ = np.histogram(self.walkers, bins=self.length, range=(0, self.length))
return density / len(self.walkers)
|
step()
Move walkers.
Source code in src/sc_neurocore/physics/heat.py
22
23
24
25
26
27
28
29
30
31 | def step(self) -> None:
"""
Move walkers.
"""
# Random step -1, 0, 1
steps = np.random.choice([-1, 0, 1], size=len(self.walkers), p=[0.25, 0.5, 0.25])
self.walkers += steps
# Boundary conditions (Reflective)
self.walkers = np.clip(self.walkers, 0, self.length - 1)
|
get_temperature_profile()
Convert walker density to temperature.
Source code in src/sc_neurocore/physics/heat.py
| def get_temperature_profile(self) -> np.ndarray[Any, Any]:
"""
Convert walker density to temperature.
"""
density, _ = np.histogram(self.walkers, bins=self.length, range=(0, self.length))
return density / len(self.walkers)
|
WolframHypergraph
dataclass
Simulates the Wolfram Physics Project Hypergraph.
Universe is a set of relations (Hyperedges).
Source code in src/sc_neurocore/physics/wolfram_hypergraph.py
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 | @dataclass
class WolframHypergraph:
"""
Simulates the Wolfram Physics Project Hypergraph.
Universe is a set of relations (Hyperedges).
"""
edges: List[Tuple[int, ...]]
max_node_id: int
def evolve(self, steps: int = 1) -> None:
"""
Applies a rewrite rule.
Rule: {{x, y}, {y, z}} -> {{x, z}, {x, w}, {y, w}}
(Triangle completion with new node w)
"""
for _ in range(steps):
new_edges = []
matched_indices = set()
# Naive pattern matching O(E^2)
# Find (x, y) and (y, z)
for i, e1 in enumerate(self.edges):
if i in matched_indices:
continue
if len(e1) != 2:
continue
x, y = e1
for j, e2 in enumerate(self.edges):
if i == j or j in matched_indices:
continue
if len(e2) != 2:
continue
if e2[0] == y: # Found chain x->y->z
z = e2[1]
# Apply Rule
w = self.max_node_id + 1
self.max_node_id += 1
# New edges: {x,z}, {x,w}, {y,w}
new_edges.append((x, z))
new_edges.append((x, w))
new_edges.append((y, w))
matched_indices.add(i)
matched_indices.add(j)
break
# Keep unmatched edges
for k, e in enumerate(self.edges):
if k not in matched_indices:
new_edges.append(e)
self.edges = new_edges
def dimension_estimate(self) -> float:
"""Estimate effective dimension via BFS neighborhood growth.
Builds an adjacency graph from hyperedges, picks a random node,
measures how |B(r)| grows with r. Fits d from V(r) ~ r^d.
Returns 0.0 if the graph is too small for meaningful estimation.
"""
if len(self.edges) < 3:
return 0.0
adj: dict[int, set[int]] = {}
for edge in self.edges:
for node in edge:
adj.setdefault(node, set())
for i in range(len(edge)):
for j in range(i + 1, len(edge)):
adj[edge[i]].add(edge[j])
adj[edge[j]].add(edge[i])
nodes = list(adj.keys())
if len(nodes) < 4:
return 0.0
start = nodes[len(nodes) // 2]
visited = {start}
frontier = {start}
volumes = []
for _ in range(min(10, len(nodes))):
next_frontier: set[int] = set()
for n in frontier:
for nb in adj.get(n, set()):
if nb not in visited:
visited.add(nb)
next_frontier.add(nb)
if not next_frontier:
break
frontier = next_frontier
volumes.append(len(visited))
if len(volumes) < 2:
return 0.0
import numpy as np
r_vals = np.arange(1, len(volumes) + 1, dtype=np.float64)
v_vals = np.array(volumes, dtype=np.float64)
log_r = np.log(r_vals)
log_v = np.log(np.clip(v_vals, 1, None))
if log_r[-1] - log_r[0] < 1e-10: # pragma: no cover
return 0.0
slope = (log_v[-1] - log_v[0]) / (log_r[-1] - log_r[0])
return float(max(slope, 0.0))
|
evolve(steps=1)
Applies a rewrite rule.
Rule: {{x, y}, {y, z}} -> {{x, z}, {x, w}, {y, w}}
(Triangle completion with new node w)
Source code in src/sc_neurocore/physics/wolfram_hypergraph.py
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 | def evolve(self, steps: int = 1) -> None:
"""
Applies a rewrite rule.
Rule: {{x, y}, {y, z}} -> {{x, z}, {x, w}, {y, w}}
(Triangle completion with new node w)
"""
for _ in range(steps):
new_edges = []
matched_indices = set()
# Naive pattern matching O(E^2)
# Find (x, y) and (y, z)
for i, e1 in enumerate(self.edges):
if i in matched_indices:
continue
if len(e1) != 2:
continue
x, y = e1
for j, e2 in enumerate(self.edges):
if i == j or j in matched_indices:
continue
if len(e2) != 2:
continue
if e2[0] == y: # Found chain x->y->z
z = e2[1]
# Apply Rule
w = self.max_node_id + 1
self.max_node_id += 1
# New edges: {x,z}, {x,w}, {y,w}
new_edges.append((x, z))
new_edges.append((x, w))
new_edges.append((y, w))
matched_indices.add(i)
matched_indices.add(j)
break
# Keep unmatched edges
for k, e in enumerate(self.edges):
if k not in matched_indices:
new_edges.append(e)
self.edges = new_edges
|
dimension_estimate()
Estimate effective dimension via BFS neighborhood growth.
Builds an adjacency graph from hyperedges, picks a random node,
measures how |B(r)| grows with r. Fits d from V(r) ~ r^d.
Returns 0.0 if the graph is too small for meaningful estimation.
Source code in src/sc_neurocore/physics/wolfram_hypergraph.py
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 | def dimension_estimate(self) -> float:
"""Estimate effective dimension via BFS neighborhood growth.
Builds an adjacency graph from hyperedges, picks a random node,
measures how |B(r)| grows with r. Fits d from V(r) ~ r^d.
Returns 0.0 if the graph is too small for meaningful estimation.
"""
if len(self.edges) < 3:
return 0.0
adj: dict[int, set[int]] = {}
for edge in self.edges:
for node in edge:
adj.setdefault(node, set())
for i in range(len(edge)):
for j in range(i + 1, len(edge)):
adj[edge[i]].add(edge[j])
adj[edge[j]].add(edge[i])
nodes = list(adj.keys())
if len(nodes) < 4:
return 0.0
start = nodes[len(nodes) // 2]
visited = {start}
frontier = {start}
volumes = []
for _ in range(min(10, len(nodes))):
next_frontier: set[int] = set()
for n in frontier:
for nb in adj.get(n, set()):
if nb not in visited:
visited.add(nb)
next_frontier.add(nb)
if not next_frontier:
break
frontier = next_frontier
volumes.append(len(visited))
if len(volumes) < 2:
return 0.0
import numpy as np
r_vals = np.arange(1, len(volumes) + 1, dtype=np.float64)
v_vals = np.array(volumes, dtype=np.float64)
log_r = np.log(r_vals)
log_v = np.log(np.clip(v_vals, 1, None))
if log_r[-1] - log_r[0] < 1e-10: # pragma: no cover
return 0.0
slope = (log_v[-1] - log_v[0]) / (log_r[-1] - log_r[0])
return float(max(slope, 0.0))
|