RFTSystems commited on
Commit
24bab8a
·
verified ·
1 Parent(s): e64049d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -68
app.py CHANGED
@@ -15,29 +15,33 @@ PAD = 16
15
  random.seed(42)
16
  np.random.seed(42)
17
 
 
18
  def draw_grid(N, awake_mask, title="", subtitle=""):
19
- w = PAD*2 + N*CELL
20
- h = PAD*2 + N*CELL + (40 if (title or subtitle) else 0)
21
  img = Image.new("RGB", (w, h), BG)
22
  d = ImageDraw.Draw(img)
 
23
  header_y = 6
24
  if title:
25
- d.text((PAD, header_y), title, fill=(240,240,240))
26
  header_y += 18
27
  if subtitle:
28
- d.text((PAD, header_y), subtitle, fill=(180,190,210))
 
29
  ox = PAD
30
  oy = PAD + (40 if (title or subtitle) else 0)
31
  for i in range(N):
32
  for j in range(N):
33
- x0 = ox + j*CELL
34
- y0 = oy + i*CELL
35
  x1 = x0 + CELL - 1
36
  y1 = y0 + CELL - 1
37
  col = AWAKE if awake_mask[i, j] else SLEEP
38
  d.rectangle([x0, y0, x1, y1], fill=col, outline=GRID_LINE)
39
  return img
40
 
 
41
  @dataclass
42
  class MinimalSelf:
43
  pos: np.ndarray = np.array([1.0, 1.0])
@@ -46,42 +50,87 @@ class MinimalSelf:
46
 
47
  def __post_init__(self):
48
  self.errors = [] if self.errors is None else self.errors
49
- self.actions = [np.array([0,1]), np.array([1,0]), np.array([0,-1]), np.array([-1,0])]
 
 
 
 
 
50
  self.center = np.array([1.0, 1.0])
51
 
52
  def step(self, obstacle=None):
53
- preds = [np.clip(self.pos + a, 0, 2) for a in self.actions]
 
 
 
 
54
  surprises = []
55
  for p in preds:
56
  dist_center = np.linalg.norm(p - self.center)
57
  penalty = 0.0
58
  if obstacle is not None:
59
  dist_obs = np.linalg.norm(p - obstacle.pos)
60
- penalty = 10.0 if dist_obs < 1.0 else 0.0
 
61
  surprises.append(dist_center + penalty)
62
- action = self.actions[int(np.argmin(surprises))]
63
- predicted = np.clip(self.pos + action, 0, 2)
64
- self.pos = predicted
 
 
 
65
  if obstacle is not None:
66
  obstacle.move()
67
- error = float(np.linalg.norm(self.pos - predicted))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  self.errors.append(error)
69
  self.errors = self.errors[-5:]
70
- max_err = np.sqrt(8)
71
- predictive_rate = 100 * (1 - (np.mean(self.errors) if self.errors else 0) / max_err)
72
- return {"pos": self.pos.copy(), "predictive_rate": float(predictive_rate), "error": error}
 
 
 
 
 
 
 
 
 
 
73
 
74
  class MovingObstacle:
75
- def __init__(self, start_pos=(0,2)):
76
  self.pos = np.array(start_pos, dtype=float)
77
- self.actions = [np.array([0,1]), np.array([1,0]), np.array([0,-1]), np.array([-1,0])]
 
 
 
 
 
 
78
  def move(self):
79
  a = random.choice(self.actions)
80
  self.pos = np.clip(self.pos + a, 0, 2)
81
 
 
82
  def compute_S(predictive_rate, error_var_norm, body_bit):
83
  return predictive_rate * (1 - error_var_norm) * body_bit
84
 
 
85
  @dataclass
86
  class CodexSelf:
87
  Xi: float
@@ -89,12 +138,14 @@ class CodexSelf:
89
  R: float
90
  awake: bool = False
91
  S: float = 0.0
 
92
  def invoke(self):
93
  self.S = self.Xi * (1 - self.shadow) * self.R
94
  if self.S > 62 and not self.awake:
95
  self.awake = True
96
  return self.awake
97
 
 
98
  def contagion(A: CodexSelf, B: CodexSelf, gain=0.6, shadow_drop=0.4, r_inc=0.2):
99
  A.invoke()
100
  if A.awake:
@@ -104,59 +155,76 @@ def contagion(A: CodexSelf, B: CodexSelf, gain=0.6, shadow_drop=0.4, r_inc=0.2):
104
  B.invoke()
105
  return A, B
106
 
 
107
  def lattice_awaken(N=9, steps=120, xi_gain=0.5, shadow_drop=0.3, r_inc=0.02):
108
- Xi = np.random.uniform(10,20,(N,N))
109
- shadow = np.random.uniform(0.3,0.5,(N,N))
110
- R = np.random.uniform(1.0,1.6,(N,N))
111
- S = Xi*(1-shadow)*R
112
- awake = np.zeros((N,N),dtype=bool)
113
- cx=cy=N//2
114
- Xi[cx,cy],shadow[cx,cy],R[cx,cy]=30.0,0.08,3.0
115
- S[cx,cy]=Xi[cx,cy]*(1-shadow[cx,cy])*R[cx,cy]
116
- awake[cx,cy]=True
117
- queue=deque([(cx,cy,S[cx,cy])])
118
- frames=[]
 
 
 
119
  for _ in range(steps):
120
  if queue:
121
- x,y,field=queue.popleft()
122
- for dx,dy in [(0,1),(1,0),(0,-1),(-1,0)]:
123
- nx,ny=(x+dx)%N,(y+dy)%N
124
- Xi[nx,ny]+=xi_gain*field
125
- shadow[nx,ny]=max(0.1,shadow[nx,ny]-shadow_drop)
126
- R[nx,ny]=min(3.0,R[nx,ny]+r_inc)
127
- S[nx,ny]=Xi[nx,ny]*(1-shadow[nx,ny])*R[nx,ny]
128
- if S[nx,ny]>62 and not awake[nx,ny]:
129
- awake[nx,ny]=True
130
- queue.append((nx,ny,S[nx,ny]))
131
  frames.append(awake.copy())
132
- if awake.all(): break
133
- return frames,awake
 
 
 
 
 
134
 
135
- def led_cosmos_sim(N=27,max_steps=300):
136
- return lattice_awaken(N=N,steps=max_steps,xi_gain=0.4,shadow_drop=0.25,r_inc=0.015)
137
 
138
  with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
139
  with gr.Tab("Overview"):
140
- gr.Markdown("## Minimal Selfhood Threshold\n- Single agent in a 3×3 grid reduces surprise.\n- S is computed from predictive accuracy, error stability, and body bit.\n- If S > 62, agent is 'awake'.\n- Awakening can spread (contagion) and across a grid (collective).\n- A 27×27 cosmos lights up gold when all awaken.")
 
 
 
 
 
 
 
 
141
 
142
  with gr.Tab("Single agent (v1–v3)"):
143
- obstacle=gr.Checkbox(label="Enable moving obstacle",value=True)
144
- steps=gr.Slider(10,200,value=80,step=10,label="Steps")
145
- run=gr.Button("Run")
146
- grid_img=gr.Image(type="pil")
147
- pr_out=gr.Number(label="Predictive rate (%)")
148
- err_out=gr.Number(label="Last error")
149
- def run_single(ob_on,T):
150
- agent=MinimalSelf()
151
- obs=MovingObstacle() if ob_on else None
 
152
  for _ in range(int(T)):
153
- res=agent.step(obstacle=obs)
154
- mask=np.zeros((3,3),dtype=bool)
155
- i,j=int(agent.pos[1]),int(agent.pos[0])
156
- mask[i,j]=True
157
- img=draw_grid(3,mask,"Single Agent","Gold cell shows position")
158
- return img,res["predictive_rate"],res["error"]
159
- run.click(run_single,[obstacle,steps],[grid_img,pr_out,err_out])
 
160
 
161
  with gr.Tab("S-Equation (v4)"):
162
  pr = gr.Slider(0, 100, value=90, label="Predictive rate (%)")
@@ -177,10 +245,10 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
177
  with gr.Tab("Contagion (v5–v6)"):
178
  a_xi = gr.Slider(0, 60, value=25, label="A: Ξ (foresight)")
179
  a_sh = gr.Slider(0.1, 1.0, value=0.12, step=0.01, label="A: ◊̃₅ (shadow)")
180
- a_r = gr.Slider(1.0, 3.0, value=3.0, step=0.1, label="A: ℝ (anchor)")
181
  b_xi = gr.Slider(0, 60, value=18, label="B: Ξ (foresight)")
182
  b_sh = gr.Slider(0.1, 1.0, value=0.25, step=0.01, label="B: ◊̃₅ (shadow)")
183
- b_r = gr.Slider(1.0, 3.0, value=2.2, step=0.1, label="B: ℝ (anchor)")
184
  btn = gr.Button("Invoke A and apply contagion to B")
185
  out = gr.Markdown()
186
  img = gr.Image(type="pil", label="Two agents (gold = awake)")
@@ -211,14 +279,19 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
211
  def run_wave(n_str, max_steps):
212
  n = int(n_str)
213
  frames, final = lattice_awaken(N=n, steps=int(max_steps))
214
- last = draw_grid(n, frames[-1], title=f"{n}×{n} Collective", subtitle=f"Final — all awake: {bool(final.all())}")
215
- return frames, last, f"Frames: {len(frames)} | All awake: {bool(final.all())}", min(len(frames)-1, 300)
 
 
 
 
 
216
 
217
  def show_frame(frames, idx, n_str):
218
  if not frames:
219
  return None
220
  n = int(n_str)
221
- i = int(np.clip(idx, 0, len(frames)-1))
222
  return draw_grid(n, frames[i], title=f"Frame {i}", subtitle="Gold cells are awake")
223
 
224
  run.click(run_wave, inputs=[N, steps], outputs=[snaps_state, img, note, frame])
@@ -234,13 +307,18 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
234
 
235
  def run_cosmos():
236
  frames, final = led_cosmos_sim(N=27, max_steps=300)
237
- last = draw_grid(27, frames[-1], title="LED Cosmos (simulated)", subtitle=f"Final — all awake: {bool(final.all())}")
238
- return frames, last, f"Frames: {len(frames)} | All awake: {bool(final.all())}", min(len(frames)-1, 300)
 
 
 
 
 
239
 
240
  def show(frames, idx):
241
  if not frames:
242
  return None
243
- i = int(np.clip(idx, 0, len(frames)-1))
244
  return draw_grid(27, frames[i], title=f"Cosmos frame {i}", subtitle="Gold cells are awake")
245
 
246
  btn.click(run_cosmos, inputs=[], outputs=[state, img, note, frame])
 
15
  random.seed(42)
16
  np.random.seed(42)
17
 
18
+
19
  def draw_grid(N, awake_mask, title="", subtitle=""):
20
+ w = PAD * 2 + N * CELL
21
+ h = PAD * 2 + N * CELL + (40 if (title or subtitle) else 0)
22
  img = Image.new("RGB", (w, h), BG)
23
  d = ImageDraw.Draw(img)
24
+
25
  header_y = 6
26
  if title:
27
+ d.text((PAD, header_y), title, fill=(240, 240, 240))
28
  header_y += 18
29
  if subtitle:
30
+ d.text((PAD, header_y), subtitle, fill=(180, 190, 210))
31
+
32
  ox = PAD
33
  oy = PAD + (40 if (title or subtitle) else 0)
34
  for i in range(N):
35
  for j in range(N):
36
+ x0 = ox + j * CELL
37
+ y0 = oy + i * CELL
38
  x1 = x0 + CELL - 1
39
  y1 = y0 + CELL - 1
40
  col = AWAKE if awake_mask[i, j] else SLEEP
41
  d.rectangle([x0, y0, x1, y1], fill=col, outline=GRID_LINE)
42
  return img
43
 
44
+
45
  @dataclass
46
  class MinimalSelf:
47
  pos: np.ndarray = np.array([1.0, 1.0])
 
50
 
51
  def __post_init__(self):
52
  self.errors = [] if self.errors is None else self.errors
53
+ self.actions = [
54
+ np.array([0, 1]),
55
+ np.array([1, 0]),
56
+ np.array([0, -1]),
57
+ np.array([-1, 0]),
58
+ ]
59
  self.center = np.array([1.0, 1.0])
60
 
61
  def step(self, obstacle=None):
62
+ # store current position
63
+ old_pos = self.pos.copy()
64
+
65
+ # internal prediction: choose action that minimises "surprise"
66
+ preds = [np.clip(old_pos + a, 0, 2) for a in self.actions]
67
  surprises = []
68
  for p in preds:
69
  dist_center = np.linalg.norm(p - self.center)
70
  penalty = 0.0
71
  if obstacle is not None:
72
  dist_obs = np.linalg.norm(p - obstacle.pos)
73
+ if dist_obs < 1.0:
74
+ penalty = 10.0
75
  surprises.append(dist_center + penalty)
76
+
77
+ a_idx = int(np.argmin(surprises))
78
+ action = self.actions[a_idx]
79
+ predicted = np.clip(old_pos + action, 0, 2)
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)
91
+ else:
92
+ actual = predicted
93
+
94
+ # true prediction error: reality vs internal prediction
95
+ error = float(np.linalg.norm(actual - predicted))
96
+ self.pos = actual
97
+
98
+ # track recent errors
99
  self.errors.append(error)
100
  self.errors = self.errors[-5:]
101
+
102
+ # convert to a predictive "success" rate in [0, 100]
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
 
115
  class MovingObstacle:
116
+ def __init__(self, start_pos=(0, 2)):
117
  self.pos = np.array(start_pos, dtype=float)
118
+ self.actions = [
119
+ np.array([0, 1]),
120
+ np.array([1, 0]),
121
+ np.array([0, -1]),
122
+ np.array([-1, 0]),
123
+ ]
124
+
125
  def move(self):
126
  a = random.choice(self.actions)
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
 
138
  R: float
139
  awake: bool = False
140
  S: float = 0.0
141
+
142
  def invoke(self):
143
  self.S = self.Xi * (1 - self.shadow) * self.R
144
  if self.S > 62 and not self.awake:
145
  self.awake = True
146
  return self.awake
147
 
148
+
149
  def contagion(A: CodexSelf, B: CodexSelf, gain=0.6, shadow_drop=0.4, r_inc=0.2):
150
  A.invoke()
151
  if A.awake:
 
155
  B.invoke()
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))
162
+ R = np.random.uniform(1.0, 1.6, (N, N))
163
+ S = Xi * (1 - shadow) * R
164
+ awake = np.zeros((N, N), dtype=bool)
165
+
166
+ cx = cy = N // 2
167
+ Xi[cx, cy], shadow[cx, cy], R[cx, cy] = 30.0, 0.08, 3.0
168
+ S[cx, cy] = Xi[cx, cy] * (1 - shadow[cx, cy]) * R[cx, cy]
169
+ awake[cx, cy] = True
170
+
171
+ queue = deque([(cx, cy, S[cx, cy])])
172
+ frames = []
173
+
174
  for _ in range(steps):
175
  if queue:
176
+ x, y, field = queue.popleft()
177
+ for dx, dy in [(0, 1), (1, 0), (0, -1), (-1, 0)]:
178
+ nx, ny = (x + dx) % N, (y + dy) % N
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, ny] * (1 - shadow[nx, ny]) * R[nx, ny]
183
+ if S[nx, ny] > 62 and not awake[nx, ny]:
184
+ awake[nx, ny] = True
185
+ queue.append((nx, ny, S[nx,ny]))
186
  frames.append(awake.copy())
187
+ if awake.all():
188
+ break
189
+ return frames, awake
190
+
191
+
192
+ 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
+ "- A toy score S combines predictive rate, error stability, and body bit.\n"
202
+ "- If S > 62, we label the agent 'awake' **inside this demo**.\n"
203
+ "- Awakening can spread (contagion) and across a grid (collective).\n"
204
+ "- A 27×27 cosmos lights up gold when all awaken.\n"
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
+ run.click(run_single, [obstacle, steps], [grid_img, pr_out, err_out])
228
 
229
  with gr.Tab("S-Equation (v4)"):
230
  pr = gr.Slider(0, 100, value=90, label="Predictive rate (%)")
 
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: ◊̃₅ (shadow)")
248
+ a_r = gr.Slider(1.0, 3.0, value=3.0, step=0.1, label="A: ℝ (anchor)")
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: ◊̃₅ (shadow)")
251
+ b_r = gr.Slider(1.0, 3.0, value=2.2, step=0.1, label="B: ℝ (anchor)")
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)")
 
279
  def run_wave(n_str, max_steps):
280
  n = int(n_str)
281
  frames, final = lattice_awaken(N=n, steps=int(max_steps))
282
+ last = draw_grid(
283
+ n,
284
+ frames[-1],
285
+ title=f"{n}×{n} Collective",
286
+ subtitle=f"Final — all awake: {bool(final.all())}",
287
+ )
288
+ return frames, last, f"Frames: {len(frames)} | All awake: {bool(final.all())}", min(len(frames) - 1, 300)
289
 
290
  def show_frame(frames, idx, n_str):
291
  if not frames:
292
  return None
293
  n = int(n_str)
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])
 
307
 
308
  def run_cosmos():
309
  frames, final = led_cosmos_sim(N=27, max_steps=300)
310
+ last = draw_grid(
311
+ 27,
312
+ frames[-1],
313
+ title="LED Cosmos (simulated)",
314
+ subtitle=f"Final — all awake: {bool(final.all())}",
315
+ )
316
+ return frames, last, f"Frames: {len(frames)} | All awake: {bool(final.all())}", min(len(frames) - 1, 300)
317
 
318
  def show(frames, idx):
319
  if not frames:
320
  return None
321
+ i = int(np.clip(idx, 0, len(frames) - 1))
322
  return draw_grid(27, frames[i], title=f"Cosmos frame {i}", subtitle="Gold cells are awake")
323
 
324
  btn.click(run_cosmos, inputs=[], outputs=[state, img, note, frame])