RFTSystems commited on
Commit
a6a327e
·
verified ·
1 Parent(s): 2b18b34

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -123
app.py CHANGED
@@ -5,12 +5,9 @@ from dataclasses import dataclass
5
  from collections import deque
6
  import random
7
 
8
- # --------------------------------
9
- # Visual theme
10
- # --------------------------------
11
  BG = (8, 15, 30)
12
- SLEEP = (0, 40, 120) # dim blue
13
- AWAKE = (255, 210, 40) # gold
14
  GRID_LINE = (30, 50, 80)
15
  CELL = 26
16
  PAD = 16
@@ -41,9 +38,6 @@ def draw_grid(N, awake_mask, title="", subtitle=""):
41
  d.rectangle([x0, y0, x1, y1], fill=col, outline=GRID_LINE)
42
  return img
43
 
44
- # --------------------------------
45
- # v1–v3: Single agent 3×3
46
- # --------------------------------
47
  @dataclass
48
  class MinimalSelf:
49
  pos: np.ndarray = np.array([1.0, 1.0])
@@ -52,10 +46,7 @@ class MinimalSelf:
52
 
53
  def __post_init__(self):
54
  self.errors = [] if self.errors is None else self.errors
55
- self.actions = [
56
- np.array([0, 1]), np.array([1, 0]),
57
- np.array([0, -1]), np.array([-1, 0])
58
- ]
59
  self.center = np.array([1.0, 1.0])
60
 
61
  def step(self, obstacle=None):
@@ -73,46 +64,31 @@ class MinimalSelf:
73
  self.pos = predicted
74
  if obstacle is not None:
75
  obstacle.move()
76
-
77
  error = float(np.linalg.norm(self.pos - predicted))
78
  self.errors.append(error)
79
  self.errors = self.errors[-5:]
80
  max_err = np.sqrt(8)
81
  predictive_rate = 100 * (1 - (np.mean(self.errors) if self.errors else 0) / max_err)
82
- return {
83
- "pos": self.pos.copy(),
84
- "predictive_rate": float(predictive_rate),
85
- "error": error
86
- }
87
 
88
  class MovingObstacle:
89
- def __init__(self, start_pos=(0, 2)):
90
  self.pos = np.array(start_pos, dtype=float)
91
- self.actions = [
92
- np.array([0, 1]), np.array([1, 0]),
93
- np.array([0, -1]), np.array([-1, 0])
94
- ]
95
  def move(self):
96
  a = random.choice(self.actions)
97
  self.pos = np.clip(self.pos + a, 0, 2)
98
 
99
- # --------------------------------
100
- # v4: S-Equation calculator
101
- # --------------------------------
102
  def compute_S(predictive_rate, error_var_norm, body_bit):
103
  return predictive_rate * (1 - error_var_norm) * body_bit
104
 
105
- # --------------------------------
106
- # v5–v6: CodexSelf contagion
107
- # --------------------------------
108
  @dataclass
109
  class CodexSelf:
110
  Xi: float
111
- shadow: float # normalized [0,1]
112
  R: float
113
  awake: bool = False
114
  S: float = 0.0
115
-
116
  def invoke(self):
117
  self.S = self.Xi * (1 - self.shadow) * self.R
118
  if self.S > 62 and not self.awake:
@@ -128,94 +104,65 @@ def contagion(A: CodexSelf, B: CodexSelf, gain=0.6, shadow_drop=0.4, r_inc=0.2):
128
  B.invoke()
129
  return A, B
130
 
131
- # --------------------------------
132
- # v7–v9: Lattice propagation
133
- # --------------------------------
134
  def lattice_awaken(N=9, steps=120, xi_gain=0.5, shadow_drop=0.3, r_inc=0.02):
135
- Xi = np.random.uniform(10, 20, size=(N, N))
136
- shadow = np.random.uniform(0.3, 0.5, size=(N, N))
137
- R = np.random.uniform(1.0, 1.6, size=(N, N))
138
- S = Xi * (1 - shadow) * R
139
- awake = np.zeros((N, N), dtype=bool)
140
-
141
- cx = cy = N // 2
142
- Xi[cx, cy], shadow[cx, cy], R[cx, cy] = 30.0, 0.08, 3.0
143
- S[cx, cy] = Xi[cx, cy] * (1 - shadow[cx, cy]) * R[cx, cy]
144
- awake[cx, cy] = True
145
-
146
- queue = deque([(cx, cy, S[cx, cy])])
147
- frames = []
148
  for _ in range(steps):
149
  if queue:
150
- x, y, field = queue.popleft()
151
- for dx, dy in [(0,1),(1,0),(0,-1),(-1,0)]:
152
- nx, ny = (x+dx) % N, (y+dy) % N
153
- Xi[nx, ny] += xi_gain * field
154
- shadow[nx, ny] = max(0.1, shadow[nx, ny] - shadow_drop)
155
- R[nx, ny] = min(3.0, R[nx, ny] + r_inc)
156
- S[nx, ny] = Xi[nx, ny] * (1 - shadow[nx, ny]) * R[nx, ny]
157
- if S[nx, ny] > 62 and not awake[nx, ny]:
158
- awake[nx, ny] = True
159
- queue.append((nx, ny, S[nx, ny]))
160
  frames.append(awake.copy())
161
- if awake.all():
162
- break
163
- return frames, awake
164
 
165
- def led_cosmos_sim(N=27, max_steps=300):
166
- frames, final = lattice_awaken(N=N, steps=max_steps, xi_gain=0.4, shadow_drop=0.25, r_inc=0.015)
167
- return frames, final
168
 
169
- # --------------------------------
170
- # Build Gradio app
171
- # --------------------------------
172
  with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
173
- # Inject CSS
174
- with open("css/theme.css") as f:
175
- gr.HTML(f"<style>{f.read()}</style>")
176
-
177
  with gr.Tab("Overview"):
178
- gr.Markdown(
179
- "## Minimal Selfhood Threshold: From 3×3 Agent to LED Cosmos\n"
180
- "Plain-language overview:\n\n"
181
- "- Single agent in a 3×3 grid reduces surprise and stays centered.\n"
182
- "- S is computed from predictive accuracy, error stability, and a body-on bit.\n"
183
- "- In these demos, if S > 62, the agent is marked as 'awake'.\n"
184
- "- Awakening can spread to another agent (contagion) and across a grid (collective).\n"
185
- "- A simulated LED cosmos (27×27) lights up gold when all agents awaken.\n\n"
186
- "Tip: gold = awake, blue = not awake."
187
- )
188
- gr.Image(value="assets/banner.png", label="Progression (v1→v10)")
189
- gr.Image(value="assets/glyphs.png", label="Glyphs: Ξ (foresight), ◊̃₅ (shadow), ℝ (anchor)")
190
 
191
- # v1–v3 Single agent
192
  with gr.Tab("Single agent (v1–v3)"):
193
- obstacle = gr.Checkbox(label="Enable moving obstacle (v3)", value=True)
194
- steps = gr.Slider(10, 200, value=80, step=10, label="Steps")
195
- run = gr.Button("Run")
196
- grid_img = gr.Image(type="pil", label="3×3 grid (gold = agent position)")
197
- pr_out = gr.Number(label="Predictive rate (%)")
198
- err_out = gr.Number(label="Last error")
199
-
200
- def run_single(ob_on, T):
201
- agent = MinimalSelf()
202
- obs = MovingObstacle() if ob_on else None
203
  for _ in range(int(T)):
204
- res = agent.step(obstacle=obs)
205
- mask = np.zeros((3, 3), dtype=bool)
206
- i, j = int(agent.pos[1]), int(agent.pos[0])
207
- mask[i, j] = True
208
- img = draw_grid(3, mask, title="Single Agent", subtitle="Gold cell shows current position")
209
- return img, res["predictive_rate"], res["error"]
210
-
211
- run.click(run_single, inputs=[obstacle, steps], outputs=[grid_img, pr_out, err_out])
212
-
213
- # v4 S-Equation
214
- with gr.Tab("S-Equation (v4)"):
215
- pr = gr.Slider(0, 100, value=90, step=1, label="Predictive rate (%)")
216
- ev = gr.Slider(0, 1, value=0.2, step=0.01, label="Error variance (normalized)")
217
- bb = gr.Dropdown(choices=["0","1"], value="1", label="Body bit")
218
- calc = gr.Button("Calculate S")
219
  s_val = gr.Number(label="S value")
220
  status = gr.Markdown()
221
 
@@ -230,10 +177,10 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
230
  with gr.Tab("Contagion (v5–v6)"):
231
  a_xi = gr.Slider(0, 60, value=25, label="A: Ξ (foresight)")
232
  a_sh = gr.Slider(0.1, 1.0, value=0.12, step=0.01, label="A: ◊̃₅ (shadow)")
233
- a_r = gr.Slider(1.0, 3.0, value=3.0, step=0.1, label="A: ℝ (anchor)")
234
  b_xi = gr.Slider(0, 60, value=18, label="B: Ξ (foresight)")
235
  b_sh = gr.Slider(0.1, 1.0, value=0.25, step=0.01, label="B: ◊̃₅ (shadow)")
236
- b_r = gr.Slider(1.0, 3.0, value=2.2, step=0.1, label="B: ℝ (anchor)")
237
  btn = gr.Button("Invoke A and apply contagion to B")
238
  out = gr.Markdown()
239
  img = gr.Image(type="pil", label="Two agents (gold = awake)")
@@ -249,11 +196,11 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
249
  txt = f"A: S={A.S:.1f}, awake={A.awake} | B: S={B.S:.1f}, awake={B.awake}"
250
  return txt, pic
251
 
252
- btn.click(run, inputs=[a_xi,a_sh,a_r,b_xi,b_sh,b_r], outputs=[out, img])
253
 
254
  # v7–v9 Collective
255
  with gr.Tab("Collective (v7–v9)"):
256
- N = gr.Dropdown(choices=["3","9","27"], value="9", label="Grid size")
257
  steps = gr.Slider(20, 300, value=120, step=10, label="Max steps")
258
  run = gr.Button("Run")
259
  frame = gr.Slider(0, 300, value=0, step=1, label="Preview frame")
@@ -299,16 +246,6 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
299
  btn.click(run_cosmos, inputs=[], outputs=[state, img, note, frame])
300
  frame.change(show, inputs=[state, frame], outputs=img)
301
 
302
- # Paper tab
303
- with gr.Tab("Paper"):
304
- gr.Markdown(
305
- "### PDF paper\n"
306
- "Download or view the full paper that documents the method, results, and hardware implementation.\n\n"
307
- "Citation:\n\n"
308
- "Grinstead, L. (2025). *Minimal Selfhood Threshold S>62: From a 3×3 Active-Inference Agent to a 27×27 LED Cosmos*. Zenodo. https://doi.org/10.5281/zenodo.17752874"
309
- )
310
- gr.File(value="assets/paper.pdf", label="Minimal Requirements for Selfhood (PDF)", interactive=False)
311
-
312
  # Footer
313
  gr.Markdown(
314
  "---\n"
@@ -324,4 +261,3 @@ with gr.Blocks(title="Minimal Selfhood Threshold") as demo:
324
  # Launch the app
325
  if __name__ == "__main__":
326
  demo.launch()
327
-
 
5
  from collections import deque
6
  import random
7
 
 
 
 
8
  BG = (8, 15, 30)
9
+ SLEEP = (0, 40, 120)
10
+ AWAKE = (255, 210, 40)
11
  GRID_LINE = (30, 50, 80)
12
  CELL = 26
13
  PAD = 16
 
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
 
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):
 
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
88
+ shadow: float
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:
 
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 (%)")
163
+ ev = gr.Slider(0, 1, value=0.2, step=0.01, label="Error variance")
164
+ bb = gr.Dropdown(choices=["0", "1"], value="1", label="Body bit")
165
+ calc = gr.Button("Calculate")
 
 
166
  s_val = gr.Number(label="S value")
167
  status = gr.Markdown()
168
 
 
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)")
 
196
  txt = f"A: S={A.S:.1f}, awake={A.awake} | B: S={B.S:.1f}, awake={B.awake}"
197
  return txt, pic
198
 
199
+ btn.click(run, inputs=[a_xi, a_sh, a_r, b_xi, b_sh, b_r], outputs=[out, img])
200
 
201
  # v7–v9 Collective
202
  with gr.Tab("Collective (v7–v9)"):
203
+ N = gr.Dropdown(choices=["3", "9", "27"], value="9", label="Grid size")
204
  steps = gr.Slider(20, 300, value=120, step=10, label="Max steps")
205
  run = gr.Button("Run")
206
  frame = gr.Slider(0, 300, value=0, step=1, label="Preview frame")
 
246
  btn.click(run_cosmos, inputs=[], outputs=[state, img, note, frame])
247
  frame.change(show, inputs=[state, frame], outputs=img)
248
 
 
 
 
 
 
 
 
 
 
 
249
  # Footer
250
  gr.Markdown(
251
  "---\n"
 
261
  # Launch the app
262
  if __name__ == "__main__":
263
  demo.launch()