| … | … | @@ -0,0 +1,142 @@ |
| 1 | /* belief_field_agent.c — the SEED: a world model that lives as a belief-FIELD over coupled units, |
| 2 | * infers by belief-mating, and drives a DECISION toward a setpoint. The base case of the design. |
| 3 | * |
| 4 | * Architecture (each piece traces to a validated result of the arc): |
| 5 | * SUBSTRATE : N units on a ring, each holding a belief vector b_i in R^3 (the "cells"). |
| 6 | * FIELD : the collective belief across units = the world model (pattern memory). Units are |
| 7 | * gap-junction-coupled (diffusion) -> a COHERENCE DOMAIN = the individual (the boundary). |
| 8 | * INFERENCE : fast loop = BELIEF-MATING (predict via the transition operator, correct via emission), |
| 9 | * exactly the mess3 filter (mess3.c) distributed over the field. |
| 10 | * DECISION : a setpoint g (goal state); the effector picks the action that its belief predicts will |
| 11 | * drive the world toward g. Active-inference flavored: act to reach the setpoint. |
| 12 | * LEARNING : (slow loop) the operators are learned by method-of-moments (mom.c, the validated |
| 13 | * valley-crosser). This SEED is handed the operators to isolate the NEW parts the arc |
| 14 | * never built -- the distributed FIELD and the closed perception->decision->action LOOP. |
| 15 | * |
| 16 | * Environment: a CONTROLLABLE 3-cause POMDP. Hidden s in {0,1,2}; action a shifts it (next=(s+a)%3 |
| 17 | * w.p. 1-tn, else uniform) so the effect depends on WHERE you are -> you must INFER s to act well; |
| 18 | * emission x is noisy (correct w.p. 1-en). Goal: hold state g. A no-model agent (acts on raw x) must |
| 19 | * lose to a belief agent. Validation: FIELD goal-occupancy ~ OPTIMAL filter >> NO-MODEL; and the field's |
| 20 | * collective belief tracks the exact filter (inference is correct) while staying coherent (boundary holds). |
| 21 | * |
| 22 | * build: cc -O2 -Wall -std=c11 -o belief_field_agent belief_field_agent.c -lm |
| 23 | */ |
| 24 | #include <math.h> |
| 25 | #include <stdio.h> |
| 26 | |
| 27 | #define K 3 /* causes / symbols / actions */ |
| 28 | #define N 12 /* field units (the coherence domain) */ |
| 29 | #define TICKS 300000 |
| 30 | #define TN 0.15 /* transition noise */ |
| 31 | #define EN 0.60 /* emission noise: raw x is right only 60% -> reactive control fails, inference wins */ |
| 32 | #define DIFF 0.30 /* gap-junction diffusion (keeps the coherence domain together) */ |
| 33 | #define UNITNOISE 0.01 /* per-unit corruption (the field must denoise via coupling) */ |
| 34 | #define GOAL 0 |
| 35 | |
| 36 | static unsigned long long RNG; |
| 37 | static unsigned long long xs(void){ RNG^=RNG<<13; RNG^=RNG>>7; RNG^=RNG<<17; return RNG; } |
| 38 | static double urand(void){ return (double)(xs()>>11)*(1.0/9007199254740992.0); } |
| 39 | static int rk(void){ return (int)(xs()%K); } |
| 40 | |
| 41 | /* --- environment operators (the agent's model = these; learning them = mom.c) --- */ |
| 42 | static double Ptrans(int j,int i,int a){ return (1.0-TN)*((j==(i+a)%K)?1.0:0.0) + TN/K; } /* P(next=j|s=i,a) */ |
| 43 | static double Pemit (int x,int s){ return (1.0-EN)*((x==s)?1.0:0.0) + EN/K; } /* P(x|s) */ |
| 44 | static int step_env(int s,int a){ return (urand()<1.0-TN)? (s+a)%K : rk(); } |
| 45 | static int emit_env(int s){ return (urand()<1.0-EN)? s : rk(); } |
| 46 | |
| 47 | static void norm(double*b){ double z=0; for(int j=0;j<K;j++) z+=b[j]; if(z<1e-300)z=1e-300; for(int j=0;j<K;j++) b[j]/=z; } |
| 48 | |
| 49 | /* belief-mating step (predict via transition op given action a, correct via emission of x) */ |
| 50 | static void mate(double*b,int a,int x){ |
| 51 | double bp[K]={0,0,0}; |
| 52 | for(int j=0;j<K;j++){ double s=0; for(int i=0;i<K;i++) s+=b[i]*Ptrans(j,i,a); bp[j]=s; } /* predict */ |
| 53 | for(int j=0;j<K;j++) bp[j]*=Pemit(x,j); /* correct */ |
| 54 | norm(bp); for(int j=0;j<K;j++) b[j]=bp[j]; |
| 55 | } |
| 56 | /* decision: pick the action whose predicted next-belief puts most mass on the goal g */ |
| 57 | static int decide(const double*b){ |
| 58 | int best=0; double bv=-1; |
| 59 | for(int a=0;a<K;a++){ double m=0; for(int i=0;i<K;i++) m+=b[i]*Ptrans(GOAL,i,a); if(m>bv){bv=m;best=a;} } |
| 60 | return best; |
| 61 | } |
| 62 | |
| 63 | enum { OPTIMAL, FIELD, NOMODEL, RANDOM }; |
| 64 | |
| 65 | /* run one controller for TICKS; returns goal-occupancy. For FIELD also writes belief-divergence vs the |
| 66 | exact filter and inter-unit coherence. */ |
| 67 | static double run(int type,double*field_kl,double*field_coh){ |
| 68 | RNG=0x9e3779b97f4a7c15ULL; |
| 69 | int s=rk(); long hits=0; |
| 70 | double bf[K]={1.0/K,1.0/K,1.0/K}; /* single exact belief (OPTIMAL / shadow for FIELD) */ |
| 71 | double u[N][K]; for(int n=0;n<N;n++) for(int j=0;j<K;j++) u[n][j]=1.0/K; /* the FIELD */ |
| 72 | double klsum=0, cohsum=0; long m=0; |
| 73 | |
| 74 | for(long t=0;t<TICKS;t++){ |
| 75 | /* collective belief of the field */ |
| 76 | double c[K]={0,0,0}; for(int n=0;n<N;n++) for(int j=0;j<K;j++) c[j]+=u[n][j]/N; |
| 77 | /* DECISION */ |
| 78 | int a; |
| 79 | if(type==OPTIMAL) a=decide(bf); |
| 80 | else if(type==FIELD) a=decide(c); |
| 81 | else a=rk(); |
| 82 | |
| 83 | /* world steps */ |
| 84 | s=step_env(s,a); int x=emit_env(s); if(s==GOAL) hits++; |
| 85 | |
| 86 | /* INFERENCE */ |
| 87 | if(type==OPTIMAL||type==FIELD) mate(bf,a,x); /* exact filter (also shadow for FIELD) */ |
| 88 | if(type==FIELD){ |
| 89 | /* each unit belief-mates, is corrupted, then diffuses toward its neighbours (gap junctions) */ |
| 90 | double nu[N][K]; |
| 91 | for(int n=0;n<N;n++){ double b[K]; for(int j=0;j<K;j++) b[j]=u[n][j]; |
| 92 | mate(b,a,x); |
| 93 | for(int j=0;j<K;j++) b[j]*= (1.0 + UNITNOISE*(urand()*2-1)); |
| 94 | norm(b); |
| 95 | int L=(n-1+N)%N,R=(n+1)%N; |
| 96 | for(int j=0;j<K;j++) nu[n][j]=(1-DIFF)*b[j]+DIFF*0.5*(u[L][j]+u[R][j]); |
| 97 | norm(nu[n]); } |
| 98 | for(int n=0;n<N;n++) for(int j=0;j<K;j++) u[n][j]=nu[n][j]; |
| 99 | /* validation: field collective vs exact filter (KL), and inter-unit coherence */ |
| 100 | double cc[K]={0,0,0}; for(int n=0;n<N;n++) for(int j=0;j<K;j++) cc[j]+=u[n][j]/N; |
| 101 | double kl=0; for(int j=0;j<K;j++){ double p=cc[j]<1e-9?1e-9:cc[j], q=bf[j]<1e-9?1e-9:bf[j]; kl+=p*log(p/q); } |
| 102 | double coh=0; for(int n=0;n<N;n++){ double d=0; for(int j=0;j<K;j++) d+=fabs(u[n][j]-cc[j]); coh+=1.0-d/2; } |
| 103 | klsum+=kl; cohsum+=coh/N; m++; |
| 104 | } |
| 105 | } |
| 106 | if(type==FIELD){ *field_kl=klsum/m; *field_coh=cohsum/m; } |
| 107 | return (double)hits/TICKS; |
| 108 | } |
| 109 | |
| 110 | /* NOMODEL needs to remember last obs; simplest: re-run a dedicated loop */ |
| 111 | static double run_nomodel(void){ |
| 112 | RNG=0x9e3779b97f4a7c15ULL; int s=rk(),lastx=0; long hits=0; |
| 113 | for(long t=0;t<TICKS;t++){ int a=((GOAL-lastx)%K+K)%K; s=step_env(s,a); int x=emit_env(s); if(s==GOAL)hits++; lastx=x; } |
| 114 | return (double)hits/TICKS; |
| 115 | } |
| 116 | |
| 117 | int main(void){ |
| 118 | double kl=0,coh=0,dummy; |
| 119 | double opt = run(OPTIMAL,&dummy,&dummy); |
| 120 | double fld = run(FIELD,&kl,&coh); |
| 121 | double nom = run_nomodel(); |
| 122 | double rnd = run(RANDOM,&dummy,&dummy); |
| 123 | |
| 124 | printf("=== Belief-field agent (SEED) on a controllable 3-cause POMDP ===\n"); |
| 125 | printf(" N=%d field units, goal=state %d, emission noise=%.2f, transition noise=%.2f.\n",N,GOAL,EN,TN); |
| 126 | printf(" Decision = act toward the setpoint using the field's belief. Chance occupancy = %.3f.\n\n",1.0/K); |
| 127 | printf(" controller | goal-occupancy\n"); |
| 128 | printf(" ----------------------+---------------\n"); |
| 129 | printf(" OPTIMAL exact filter | %.3f\n", opt); |
| 130 | printf(" FIELD (belief-field) | %.3f\n", fld); |
| 131 | printf(" NO-MODEL (raw obs) | %.3f\n", nom); |
| 132 | printf(" RANDOM | %.3f\n", rnd); |
| 133 | printf("\n"); |
| 134 | printf(" field inference fidelity: KL(field collective || exact filter) = %.4f nats (->0 = correct)\n", kl); |
| 135 | printf(" coherence domain: mean inter-unit agreement = %.3f (->1 = the field holds together = one individual)\n", coh); |
| 136 | printf("\n"); |
| 137 | if(fld > nom+0.04 && fld > opt-0.03 && coh > 0.95 && kl < 0.06) |
| 138 | printf(" => SEED WORKS. The world model living as a distributed belief-FIELD infers correctly (tracks the\n exact filter) while staying coherent (one individual), and DRIVES DECISIONS that reach the setpoint\n ~as well as the optimal controller and far better than acting on raw observation. Perception->belief\n ->decision->action is closed. Fast loop = belief-mating (mess3); boundary = the coherence domain;\n goal = the setpoint. Learning the operators = mom.c plugs into the slow loop. Base case complete.\n"); |
| 139 | else |
| 140 | printf(" => Not yet: FIELD did not clearly beat NO-MODEL and match OPTIMAL with faithful inference.\n Read the numbers; adjust before claiming the seed works.\n"); |
| 141 | return 0; |
| 142 | } |