Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,9 @@ from dataclasses import dataclass
|
|
| 5 |
from collections import deque
|
| 6 |
import random
|
| 7 |
|
|
|
|
|
|
|
|
|
|
| 8 |
BG = (8, 15, 30)
|
| 9 |
SLEEP = (0, 40, 120)
|
| 10 |
AWAKE = (255, 210, 40)
|
|
@@ -42,6 +45,9 @@ def draw_grid(N, awake_mask, title="", subtitle=""):
|
|
| 42 |
return img
|
| 43 |
|
| 44 |
|
|
|
|
|
|
|
|
|
|
| 45 |
@dataclass
|
| 46 |
class MinimalSelf:
|
| 47 |
pos: np.ndarray = np.array([1.0, 1.0])
|
|
@@ -80,11 +86,13 @@ class MinimalSelf:
|
|
| 80 |
|
| 81 |
# environment decides what actually happens
|
| 82 |
if obstacle is not None:
|
|
|
|
| 83 |
obstacle.move()
|
| 84 |
actual = predicted.copy()
|
| 85 |
if np.allclose(actual, obstacle.pos):
|
| 86 |
actual = old_pos
|
| 87 |
else:
|
|
|
|
| 88 |
if random.random() < 0.25:
|
| 89 |
noise_action = random.choice(self.actions)
|
| 90 |
actual = np.clip(old_pos + noise_action, 0, 2)
|
|
@@ -99,16 +107,25 @@ class MinimalSelf:
|
|
| 99 |
self.errors.append(error)
|
| 100 |
self.errors = self.errors[-5:]
|
| 101 |
|
| 102 |
-
#
|
| 103 |
max_err = np.sqrt(8.0) # max distance corner-to-corner on 3×3
|
| 104 |
mean_err = np.mean(self.errors) if self.errors else 0.0
|
| 105 |
predictive_rate = 100.0 * (1.0 - mean_err / max_err)
|
| 106 |
predictive_rate = float(np.clip(predictive_rate, 0.0, 100.0))
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
return {
|
| 109 |
"pos": self.pos.copy(),
|
| 110 |
"predictive_rate": predictive_rate,
|
| 111 |
"error": error,
|
|
|
|
| 112 |
}
|
| 113 |
|
| 114 |
|
|
@@ -127,12 +144,17 @@ class MovingObstacle:
|
|
| 127 |
self.pos = np.clip(self.pos + a, 0, 2)
|
| 128 |
|
| 129 |
|
|
|
|
|
|
|
|
|
|
| 130 |
def compute_S(predictive_rate, error_var_norm, body_bit):
|
|
|
|
| 131 |
return predictive_rate * (1 - error_var_norm) * body_bit
|
| 132 |
|
| 133 |
|
| 134 |
@dataclass
|
| 135 |
class CodexSelf:
|
|
|
|
| 136 |
Xi: float
|
| 137 |
shadow: float
|
| 138 |
R: float
|
|
@@ -156,6 +178,9 @@ def contagion(A: CodexSelf, B: CodexSelf, gain=0.6, shadow_drop=0.4, r_inc=0.2):
|
|
| 156 |
return A, B
|
| 157 |
|
| 158 |
|
|
|
|
|
|
|
|
|
|
| 159 |
def lattice_awaken(N=9, steps=120, xi_gain=0.5, shadow_drop=0.3, r_inc=0.02):
|
| 160 |
Xi = np.random.uniform(10, 20, (N, N))
|
| 161 |
shadow = np.random.uniform(0.3, 0.5, (N, N))
|
|
@@ -179,10 +204,10 @@ def lattice_awaken(N=9, steps=120, xi_gain=0.5, shadow_drop=0.3, r_inc=0.02):
|
|
| 179 |
Xi[nx, ny] += xi_gain * field
|
| 180 |
shadow[nx, ny] = max(0.1, shadow[nx, ny] - shadow_drop)
|
| 181 |
R[nx, ny] = min(3.0, R[nx, ny] + r_inc)
|
| 182 |
-
S[nx, ny] = Xi[nx,
|
| 183 |
-
if S[nx,
|
| 184 |
-
awake[nx,
|
| 185 |
-
queue.append((nx,
|
| 186 |
frames.append(awake.copy())
|
| 187 |
if awake.all():
|
| 188 |
break
|
|
@@ -193,43 +218,62 @@ def led_cosmos_sim(N=27, max_steps=300):
|
|
| 193 |
return lattice_awaken(N=N, steps=max_steps, xi_gain=0.4, shadow_drop=0.25, r_inc=0.015)
|
| 194 |
|
| 195 |
|
|
|
|
|
|
|
|
|
|
| 196 |
with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
|
|
|
|
| 197 |
with gr.Tab("Overview"):
|
| 198 |
gr.Markdown(
|
| 199 |
"## Minimal Selfhood Threshold\n"
|
| 200 |
-
"- Single agent in a 3×3 grid reduces surprise.\n"
|
| 201 |
-
"-
|
| 202 |
-
"-
|
| 203 |
-
"-
|
| 204 |
-
"-
|
|
|
|
| 205 |
"- This is a sandbox for minimal-self / agency ideas, **not** a real consciousness test."
|
| 206 |
)
|
| 207 |
|
|
|
|
| 208 |
with gr.Tab("Single agent (v1–v3)"):
|
| 209 |
obstacle = gr.Checkbox(label="Enable moving obstacle", value=True)
|
| 210 |
steps = gr.Slider(10, 200, value=80, step=10, label="Steps")
|
| 211 |
run = gr.Button("Run")
|
| 212 |
grid_img = gr.Image(type="pil")
|
| 213 |
-
pr_out = gr.Number(label="Predictive rate (%)")
|
| 214 |
-
err_out = gr.Number(label="Last error")
|
|
|
|
|
|
|
|
|
|
| 215 |
|
| 216 |
def run_single(ob_on, T):
|
| 217 |
agent = MinimalSelf()
|
| 218 |
obs = MovingObstacle() if ob_on else None
|
|
|
|
| 219 |
for _ in range(int(T)):
|
| 220 |
res = agent.step(obstacle=obs)
|
|
|
|
| 221 |
mask = np.zeros((3, 3), dtype=bool)
|
| 222 |
i, j = int(agent.pos[1]), int(agent.pos[0])
|
| 223 |
mask[i, j] = True
|
| 224 |
img = draw_grid(3, mask, "Single Agent", "Gold cell shows position")
|
| 225 |
-
return img, res["predictive_rate"], res["error"]
|
| 226 |
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
with gr.Tab("S-Equation (v4)"):
|
| 230 |
-
pr = gr.Slider(0, 100, value=90, label="Predictive rate (%)")
|
| 231 |
-
ev = gr.Slider(0, 1, value=0.2, step=0.01, label="Error variance")
|
| 232 |
-
bb = gr.Dropdown(choices=["0", "1"], value="1", label="Body bit")
|
| 233 |
calc = gr.Button("Calculate")
|
| 234 |
s_val = gr.Number(label="S value")
|
| 235 |
status = gr.Markdown()
|
|
@@ -243,12 +287,12 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
|
|
| 243 |
|
| 244 |
# v5–v6 Contagion
|
| 245 |
with gr.Tab("Contagion (v5–v6)"):
|
| 246 |
-
a_xi = gr.Slider(0, 60, value=25, label="A: Ξ (foresight)")
|
| 247 |
-
a_sh = gr.Slider(0.1, 1.0, value=0.12, step=0.01, label="A:
|
| 248 |
-
a_r = gr.Slider(1.0, 3.0, value=3.0, step=0.1, label="A:
|
| 249 |
-
b_xi = gr.Slider(0, 60, value=18, label="B: Ξ (foresight)")
|
| 250 |
-
b_sh = gr.Slider(0.1, 1.0, value=0.25, step=0.01, label="B:
|
| 251 |
-
b_r = gr.Slider(1.0, 3.0, value=2.2, step=0.1, label="B:
|
| 252 |
btn = gr.Button("Invoke A and apply contagion to B")
|
| 253 |
out = gr.Markdown()
|
| 254 |
img = gr.Image(type="pil", label="Two agents (gold = awake)")
|
|
@@ -270,15 +314,31 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
|
|
| 270 |
with gr.Tab("Collective (v7–v9)"):
|
| 271 |
N = gr.Dropdown(choices=["3", "9", "27"], value="9", label="Grid size")
|
| 272 |
steps = gr.Slider(20, 300, value=120, step=10, label="Max steps")
|
|
|
|
| 273 |
run = gr.Button("Run")
|
| 274 |
frame = gr.Slider(0, 300, value=0, step=1, label="Preview frame")
|
| 275 |
img = gr.Image(type="pil", label="Awakening wave (gold spreads)")
|
| 276 |
note = gr.Markdown()
|
| 277 |
snaps_state = gr.State([])
|
| 278 |
|
| 279 |
-
def run_wave(n_str, max_steps):
|
| 280 |
n = int(n_str)
|
| 281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
last = draw_grid(
|
| 283 |
n,
|
| 284 |
frames[-1],
|
|
@@ -294,7 +354,7 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
|
|
| 294 |
i = int(np.clip(idx, 0, len(frames) - 1))
|
| 295 |
return draw_grid(n, frames[i], title=f"Frame {i}", subtitle="Gold cells are awake")
|
| 296 |
|
| 297 |
-
run.click(run_wave, inputs=[N, steps], outputs=[snaps_state, img, note, frame])
|
| 298 |
frame.change(show_frame, inputs=[snaps_state, frame, N], outputs=img)
|
| 299 |
|
| 300 |
# v10 LED cosmos
|
|
@@ -327,15 +387,13 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
|
|
| 327 |
# Footer
|
| 328 |
gr.Markdown(
|
| 329 |
"---\n"
|
| 330 |
-
"
|
| 331 |
-
"- The
|
| 332 |
-
"-
|
| 333 |
-
"
|
| 334 |
-
"
|
| 335 |
-
"Zenodo. https://doi.org/10.5281/zenodo.17752874\n\n"
|
| 336 |
-
"Permissions: See LICENSE. Explicit permission is required for reuse of code, visuals, and glyphs."
|
| 337 |
)
|
| 338 |
|
| 339 |
# Launch the app
|
| 340 |
if __name__ == "__main__":
|
| 341 |
-
demo.launch()
|
|
|
|
| 5 |
from collections import deque
|
| 6 |
import random
|
| 7 |
|
| 8 |
+
# ---------------------------------------------------------------------
|
| 9 |
+
# Visual config
|
| 10 |
+
# ---------------------------------------------------------------------
|
| 11 |
BG = (8, 15, 30)
|
| 12 |
SLEEP = (0, 40, 120)
|
| 13 |
AWAKE = (255, 210, 40)
|
|
|
|
| 45 |
return img
|
| 46 |
|
| 47 |
|
| 48 |
+
# ---------------------------------------------------------------------
|
| 49 |
+
# 3×3 minimal self agent
|
| 50 |
+
# ---------------------------------------------------------------------
|
| 51 |
@dataclass
|
| 52 |
class MinimalSelf:
|
| 53 |
pos: np.ndarray = np.array([1.0, 1.0])
|
|
|
|
| 86 |
|
| 87 |
# environment decides what actually happens
|
| 88 |
if obstacle is not None:
|
| 89 |
+
# moving obstacle can block the predicted move
|
| 90 |
obstacle.move()
|
| 91 |
actual = predicted.copy()
|
| 92 |
if np.allclose(actual, obstacle.pos):
|
| 93 |
actual = old_pos
|
| 94 |
else:
|
| 95 |
+
# simple stochastic slip when no obstacle is enabled
|
| 96 |
if random.random() < 0.25:
|
| 97 |
noise_action = random.choice(self.actions)
|
| 98 |
actual = np.clip(old_pos + noise_action, 0, 2)
|
|
|
|
| 107 |
self.errors.append(error)
|
| 108 |
self.errors = self.errors[-5:]
|
| 109 |
|
| 110 |
+
# predictive success rate P in [0, 100]
|
| 111 |
max_err = np.sqrt(8.0) # max distance corner-to-corner on 3×3
|
| 112 |
mean_err = np.mean(self.errors) if self.errors else 0.0
|
| 113 |
predictive_rate = 100.0 * (1.0 - mean_err / max_err)
|
| 114 |
predictive_rate = float(np.clip(predictive_rate, 0.0, 100.0))
|
| 115 |
|
| 116 |
+
# normalised error variance E in [0, 1]
|
| 117 |
+
if len(self.errors) > 1:
|
| 118 |
+
var_err = float(np.var(self.errors))
|
| 119 |
+
else:
|
| 120 |
+
var_err = 0.0
|
| 121 |
+
max_var = max_err ** 2
|
| 122 |
+
error_var_norm = float(np.clip(var_err / max_var, 0.0, 1.0)) if max_var > 0 else 0.0
|
| 123 |
+
|
| 124 |
return {
|
| 125 |
"pos": self.pos.copy(),
|
| 126 |
"predictive_rate": predictive_rate,
|
| 127 |
"error": error,
|
| 128 |
+
"error_var_norm": error_var_norm,
|
| 129 |
}
|
| 130 |
|
| 131 |
|
|
|
|
| 144 |
self.pos = np.clip(self.pos + a, 0, 2)
|
| 145 |
|
| 146 |
|
| 147 |
+
# ---------------------------------------------------------------------
|
| 148 |
+
# S-scores
|
| 149 |
+
# ---------------------------------------------------------------------
|
| 150 |
def compute_S(predictive_rate, error_var_norm, body_bit):
|
| 151 |
+
# v4: 3×3 agent toy score
|
| 152 |
return predictive_rate * (1 - error_var_norm) * body_bit
|
| 153 |
|
| 154 |
|
| 155 |
@dataclass
|
| 156 |
class CodexSelf:
|
| 157 |
+
# v5–v6: lattice toy score
|
| 158 |
Xi: float
|
| 159 |
shadow: float
|
| 160 |
R: float
|
|
|
|
| 178 |
return A, B
|
| 179 |
|
| 180 |
|
| 181 |
+
# ---------------------------------------------------------------------
|
| 182 |
+
# Lattice and cosmos
|
| 183 |
+
# ---------------------------------------------------------------------
|
| 184 |
def lattice_awaken(N=9, steps=120, xi_gain=0.5, shadow_drop=0.3, r_inc=0.02):
|
| 185 |
Xi = np.random.uniform(10, 20, (N, N))
|
| 186 |
shadow = np.random.uniform(0.3, 0.5, (N, N))
|
|
|
|
| 204 |
Xi[nx, ny] += xi_gain * field
|
| 205 |
shadow[nx, ny] = max(0.1, shadow[nx, ny] - shadow_drop)
|
| 206 |
R[nx, ny] = min(3.0, R[nx, ny] + r_inc)
|
| 207 |
+
S[nx, ny] = Xi[nx,ny] * (1 - shadow[nx,ny]) * R[nx,ny]
|
| 208 |
+
if S[nx,ny] > 62 and not awake[nx,ny]:
|
| 209 |
+
awake[nx,ny] = True
|
| 210 |
+
queue.append((nx,ny, S[nx,ny]))
|
| 211 |
frames.append(awake.copy())
|
| 212 |
if awake.all():
|
| 213 |
break
|
|
|
|
| 218 |
return lattice_awaken(N=N, steps=max_steps, xi_gain=0.4, shadow_drop=0.25, r_inc=0.015)
|
| 219 |
|
| 220 |
|
| 221 |
+
# ---------------------------------------------------------------------
|
| 222 |
+
# Gradio UI
|
| 223 |
+
# ---------------------------------------------------------------------
|
| 224 |
with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
|
| 225 |
+
# Overview
|
| 226 |
with gr.Tab("Overview"):
|
| 227 |
gr.Markdown(
|
| 228 |
"## Minimal Selfhood Threshold\n"
|
| 229 |
+
"- Single agent in a 3×3 grid reduces surprise around a preferred centre.\n"
|
| 230 |
+
"- v4 (3×3): a toy score `S = P × (1−E) × B` combines predictive rate P, error stability E and body bit B.\n"
|
| 231 |
+
"- v5–v6 (contagion / lattice): a separate toy score `S = Ξ × (1−shadow) × R` drives neighbour coupling.\n"
|
| 232 |
+
"- If S > 62, the corresponding unit is labelled 'awake' **inside this demo**.\n"
|
| 233 |
+
"- Awakening can spread between two agents and across a grid via explicit neighbour coupling.\n"
|
| 234 |
+
"- A 27×27 cosmos lights up gold when all units cross the internal threshold.\n"
|
| 235 |
"- This is a sandbox for minimal-self / agency ideas, **not** a real consciousness test."
|
| 236 |
)
|
| 237 |
|
| 238 |
+
# Single 3×3 agent
|
| 239 |
with gr.Tab("Single agent (v1–v3)"):
|
| 240 |
obstacle = gr.Checkbox(label="Enable moving obstacle", value=True)
|
| 241 |
steps = gr.Slider(10, 200, value=80, step=10, label="Steps")
|
| 242 |
run = gr.Button("Run")
|
| 243 |
grid_img = gr.Image(type="pil")
|
| 244 |
+
pr_out = gr.Number(label="Predictive rate P (%)")
|
| 245 |
+
err_out = gr.Number(label="Last prediction error")
|
| 246 |
+
e_out = gr.Number(label="Error variance E (normalised)")
|
| 247 |
+
s_out = gr.Number(label="S = P × (1−E) × B (B=1)")
|
| 248 |
+
awake_label = gr.Markdown()
|
| 249 |
|
| 250 |
def run_single(ob_on, T):
|
| 251 |
agent = MinimalSelf()
|
| 252 |
obs = MovingObstacle() if ob_on else None
|
| 253 |
+
res = None
|
| 254 |
for _ in range(int(T)):
|
| 255 |
res = agent.step(obstacle=obs)
|
| 256 |
+
|
| 257 |
mask = np.zeros((3, 3), dtype=bool)
|
| 258 |
i, j = int(agent.pos[1]), int(agent.pos[0])
|
| 259 |
mask[i, j] = True
|
| 260 |
img = draw_grid(3, mask, "Single Agent", "Gold cell shows position")
|
|
|
|
| 261 |
|
| 262 |
+
P = res["predictive_rate"]
|
| 263 |
+
E = res["error_var_norm"]
|
| 264 |
+
B = 1.0
|
| 265 |
+
S_val = compute_S(P, E, B)
|
| 266 |
+
status = "**Status:** " + ("Awake (S > 62)" if S_val > 62 else "Not awake (S ≤ 62)")
|
| 267 |
|
| 268 |
+
return img, P, res["error"], E, S_val, status
|
| 269 |
+
|
| 270 |
+
run.click(run_single, [obstacle, steps], [grid_img, pr_out, err_out, e_out, s_out, awake_label])
|
| 271 |
+
|
| 272 |
+
# v4 S-equation
|
| 273 |
with gr.Tab("S-Equation (v4)"):
|
| 274 |
+
pr = gr.Slider(0, 100, value=90, label="Predictive rate P (%)")
|
| 275 |
+
ev = gr.Slider(0, 1, value=0.2, step=0.01, label="Error variance E")
|
| 276 |
+
bb = gr.Dropdown(choices=["0", "1"], value="1", label="Body bit B")
|
| 277 |
calc = gr.Button("Calculate")
|
| 278 |
s_val = gr.Number(label="S value")
|
| 279 |
status = gr.Markdown()
|
|
|
|
| 287 |
|
| 288 |
# v5–v6 Contagion
|
| 289 |
with gr.Tab("Contagion (v5–v6)"):
|
| 290 |
+
a_xi = gr.Slider(0, 60, value=25, label="A: Ξ (foresight field)")
|
| 291 |
+
a_sh = gr.Slider(0.1, 1.0, value=0.12, step=0.01, label="A: shadow (occlusion)")
|
| 292 |
+
a_r = gr.Slider(1.0, 3.0, value=3.0, step=0.1, label="A: R (anchor / resonance)")
|
| 293 |
+
b_xi = gr.Slider(0, 60, value=18, label="B: Ξ (foresight field)")
|
| 294 |
+
b_sh = gr.Slider(0.1, 1.0, value=0.25, step=0.01, label="B: shadow (occlusion)")
|
| 295 |
+
b_r = gr.Slider(1.0, 3.0, value=2.2, step=0.1, label="B: R (anchor / resonance)")
|
| 296 |
btn = gr.Button("Invoke A and apply contagion to B")
|
| 297 |
out = gr.Markdown()
|
| 298 |
img = gr.Image(type="pil", label="Two agents (gold = awake)")
|
|
|
|
| 314 |
with gr.Tab("Collective (v7–v9)"):
|
| 315 |
N = gr.Dropdown(choices=["3", "9", "27"], value="9", label="Grid size")
|
| 316 |
steps = gr.Slider(20, 300, value=120, step=10, label="Max steps")
|
| 317 |
+
no_coupling = gr.Checkbox(label="Disable neighbour coupling (control)", value=False)
|
| 318 |
run = gr.Button("Run")
|
| 319 |
frame = gr.Slider(0, 300, value=0, step=1, label="Preview frame")
|
| 320 |
img = gr.Image(type="pil", label="Awakening wave (gold spreads)")
|
| 321 |
note = gr.Markdown()
|
| 322 |
snaps_state = gr.State([])
|
| 323 |
|
| 324 |
+
def run_wave(n_str, max_steps, disable):
|
| 325 |
n = int(n_str)
|
| 326 |
+
if disable:
|
| 327 |
+
frames, final = lattice_awaken(
|
| 328 |
+
N=n,
|
| 329 |
+
steps=int(max_steps),
|
| 330 |
+
xi_gain=0.0,
|
| 331 |
+
shadow_drop=0.0,
|
| 332 |
+
r_inc=0.0,
|
| 333 |
+
)
|
| 334 |
+
else:
|
| 335 |
+
frames, final = lattice_awaken(
|
| 336 |
+
N=n,
|
| 337 |
+
steps=int(max_steps),
|
| 338 |
+
xi_gain=0.5,
|
| 339 |
+
shadow_drop=0.3,
|
| 340 |
+
r_inc=0.02,
|
| 341 |
+
)
|
| 342 |
last = draw_grid(
|
| 343 |
n,
|
| 344 |
frames[-1],
|
|
|
|
| 354 |
i = int(np.clip(idx, 0, len(frames) - 1))
|
| 355 |
return draw_grid(n, frames[i], title=f"Frame {i}", subtitle="Gold cells are awake")
|
| 356 |
|
| 357 |
+
run.click(run_wave, inputs=[N, steps, no_coupling], outputs=[snaps_state, img, note, frame])
|
| 358 |
frame.change(show_frame, inputs=[snaps_state, frame, N], outputs=img)
|
| 359 |
|
| 360 |
# v10 LED cosmos
|
|
|
|
| 387 |
# Footer
|
| 388 |
gr.Markdown(
|
| 389 |
"---\n"
|
| 390 |
+
"Notes:\n"
|
| 391 |
+
"- The 3×3 agent computes P, E and S = P×(1−E)×B directly in this Space; S>62 is the internal ‘awake’ label for v4.\n"
|
| 392 |
+
"- The contagion and lattice views use a separate toy rule S = Ξ×(1−shadow)×R with explicit neighbour coupling.\n"
|
| 393 |
+
"- Disabling coupling (xi_gain=0, shadow_drop=0, r_inc=0) in the collective tab prevents any wave from propagating.\n\n"
|
| 394 |
+
"These demos are designed as transparent, minimal models of self-linked scoring and threshold cascades, not as a real consciousness test."
|
|
|
|
|
|
|
| 395 |
)
|
| 396 |
|
| 397 |
# Launch the app
|
| 398 |
if __name__ == "__main__":
|
| 399 |
+
demo.launch()
|