active-inference 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Seeded pseudo-random number generator using the Mulberry32 algorithm.
3
+ *
4
+ * Provides reproducible random sequences when initialized with the same seed.
5
+ * This is essential for testing and debugging Active Inference agents,
6
+ * as it allows deterministic replay of stochastic action selection.
7
+ *
8
+ * Mulberry32 is a fast, high-quality 32-bit PRNG with a period of 2^32.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const rng = new Random(42);
13
+ * console.log(rng.next()); // 0.8817... (always same for seed 42)
14
+ * console.log(rng.next()); // 0.3951...
15
+ *
16
+ * // Reset to get same sequence again
17
+ * rng.reset(42);
18
+ * console.log(rng.next()); // 0.8817... (same as before)
19
+ * ```
20
+ */
21
+ export class Random {
22
+ /**
23
+ * Create a new random number generator.
24
+ *
25
+ * @param seed - Initial seed value. Defaults to current timestamp
26
+ * for non-reproducible behavior.
27
+ */
28
+ constructor(seed = Date.now()) {
29
+ this.state = seed;
30
+ }
31
+ /**
32
+ * Generate the next random number in [0, 1).
33
+ *
34
+ * Works like Math.random() but with deterministic sequence
35
+ * based on the seed.
36
+ *
37
+ * @returns Random number between 0 (inclusive) and 1 (exclusive)
38
+ */
39
+ next() {
40
+ this.state |= 0;
41
+ this.state = (this.state + 0x6d2b79f5) | 0;
42
+ let t = Math.imul(this.state ^ (this.state >>> 15), 1 | this.state);
43
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
44
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
45
+ }
46
+ /**
47
+ * Generate a random number from standard normal distribution N(0, 1).
48
+ *
49
+ * Uses the Box-Muller transform to convert uniform random numbers
50
+ * into normally distributed values.
51
+ *
52
+ * @returns Random number from standard normal distribution
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const rng = new Random(42);
57
+ * const samples = Array.from({ length: 1000 }, () => rng.gaussian());
58
+ * // samples will have mean ≈ 0 and std ≈ 1
59
+ * ```
60
+ */
61
+ gaussian() {
62
+ const u1 = this.next();
63
+ const u2 = this.next();
64
+ return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
65
+ }
66
+ /**
67
+ * Reset the generator to a new seed.
68
+ *
69
+ * Useful for replaying random sequences from a known state.
70
+ *
71
+ * @param seed - New seed value
72
+ */
73
+ reset(seed) {
74
+ this.state = seed;
75
+ }
76
+ }
77
+ /**
78
+ * Linear algebra utilities for Active Inference computations.
79
+ *
80
+ * Provides common operations needed for probability manipulation,
81
+ * including softmax/softmin for converting values to probabilities.
82
+ */
83
+ export class LinearAlgebra {
84
+ /**
85
+ * Softmin function - converts values to probabilities inversely.
86
+ *
87
+ * Lower values get higher probabilities. Used in Active Inference
88
+ * to convert Expected Free Energy (where lower is better) into
89
+ * action probabilities.
90
+ *
91
+ * Formula: P(i) = exp(-β × x_i) / Σ exp(-β × x_j)
92
+ *
93
+ * @param arr - Array of values to convert
94
+ * @param beta - Precision/temperature parameter (default: 1).
95
+ * Higher β = more deterministic (concentrates on minimum).
96
+ * β = 0 gives uniform distribution.
97
+ * @returns Normalized probability distribution
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const efe = [2.5, 1.0, 3.0]; // Policy EFEs (lower is better)
102
+ * const probs = LinearAlgebra.softmin(efe, 4);
103
+ * // probs ≈ [0.05, 0.93, 0.02] - strongly prefers policy with EFE=1.0
104
+ * ```
105
+ */
106
+ static softmin(arr, beta = 1) {
107
+ return this.softmax(arr.map((x) => -x), beta);
108
+ }
109
+ /**
110
+ * Softmax function - converts values to probabilities.
111
+ *
112
+ * Higher values get higher probabilities. Standard operation for
113
+ * converting logits or preferences into a probability distribution.
114
+ *
115
+ * Formula: P(i) = exp(β × x_i) / Σ exp(β × x_j)
116
+ *
117
+ * Uses numerical stability trick of subtracting max before exp.
118
+ *
119
+ * @param arr - Array of values to convert
120
+ * @param beta - Precision/temperature parameter (default: 1).
121
+ * Higher β = more deterministic (concentrates on maximum).
122
+ * β = 0 gives uniform distribution.
123
+ * @returns Normalized probability distribution
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * const logits = [1.0, 2.0, 0.5];
128
+ * const probs = LinearAlgebra.softmax(logits);
129
+ * // probs ≈ [0.24, 0.67, 0.09]
130
+ * ```
131
+ */
132
+ static softmax(arr, beta = 1) {
133
+ const max = Math.max(...arr);
134
+ const exp = arr.map((x) => Math.exp(beta * (x - max)));
135
+ const sum = exp.reduce((a, b) => a + b);
136
+ return exp.map((x) => x / sum);
137
+ }
138
+ /**
139
+ * Normalize an array to sum to 1.
140
+ *
141
+ * @param arr - Array of non-negative values
142
+ * @returns Normalized array summing to 1
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * LinearAlgebra.normalize([2, 3, 5]); // [0.2, 0.3, 0.5]
147
+ * ```
148
+ */
149
+ static normalize(arr) {
150
+ const sum = arr.reduce((a, b) => a + b, 0);
151
+ return arr.map((x) => x / sum);
152
+ }
153
+ /**
154
+ * Compute dot product of two arrays.
155
+ *
156
+ * @param a - First array
157
+ * @param b - Second array (must be same length as a)
158
+ * @returns Sum of element-wise products
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * LinearAlgebra.dotProduct([1, 2, 3], [4, 5, 6]); // 32
163
+ * ```
164
+ */
165
+ static dotProduct(a, b) {
166
+ return a.reduce((sum, x, i) => sum + x * b[i], 0);
167
+ }
168
+ }
@@ -0,0 +1,5 @@
1
+ export { Agent } from './models/agent.model';
2
+ export { DiscreteBelief } from './beliefs/discrete.belief';
3
+ export { DiscreteTransition } from './transition/discrete.transition';
4
+ export { DiscreteObservation } from './observation/discrete.observation';
5
+ export { createAgent, AgentConfig } from './factory';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { Agent } from './models/agent.model';
2
+ export { DiscreteBelief } from './beliefs/discrete.belief';
3
+ export { DiscreteTransition } from './transition/discrete.transition';
4
+ export { DiscreteObservation } from './observation/discrete.observation';
5
+ export { createAgent } from './factory';
@@ -0,0 +1,252 @@
1
+ import { Belief, Distribution, Preferences } from './belief.model';
2
+ import { ITransitionModel } from './transition.model';
3
+ import { IObservationModel } from './observation.model';
4
+ import { Random } from '../helpers/math.helpers';
5
+ /**
6
+ * Prior probability over actions, representing habitual action tendencies.
7
+ * In Active Inference notation, this is the E matrix.
8
+ *
9
+ * Habits bias action selection independently of Expected Free Energy.
10
+ * They model learned action preferences or "default" behaviors.
11
+ *
12
+ * @typeParam A - Union type of possible action names
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Agent has a habit of staying still
17
+ * const habits: Habits<'move' | 'stay'> = {
18
+ * move: 0.3,
19
+ * stay: 0.7
20
+ * };
21
+ * ```
22
+ */
23
+ export type Habits<A extends string = string> = Record<A, number>;
24
+ /**
25
+ * Active Inference agent implementing the Free Energy Principle.
26
+ *
27
+ * This agent perceives the world through observations, maintains beliefs
28
+ * about hidden states, and selects actions to minimize Expected Free Energy.
29
+ *
30
+ * ## Core Concepts
31
+ *
32
+ * **Generative Model**: The agent has an internal model of how the world works:
33
+ * - Transition model (B): How states change given actions
34
+ * - Observation model (A): How states generate observations
35
+ * - Preferences (C): What observations the agent "wants" to experience
36
+ *
37
+ * **Perception**: When the agent observes something, it updates its beliefs
38
+ * using Bayesian inference: P(state|obs) ∝ P(obs|state) × P(state)
39
+ *
40
+ * **Action Selection**: The agent evaluates possible action sequences (policies)
41
+ * by computing Expected Free Energy, which balances:
42
+ * - **Risk**: Avoiding unpreferred observations
43
+ * - **Ambiguity**: Seeking informative states
44
+ *
45
+ * ## Key Parameters
46
+ *
47
+ * - `planningHorizon`: How many steps ahead to plan (1 = greedy/reactive)
48
+ * - `precision` (β): Temperature for action selection (higher = more deterministic)
49
+ * - `habits` (E): Prior preferences over actions independent of goals
50
+ *
51
+ * @typeParam A - Union type of possible action names
52
+ * @typeParam O - Union type of possible observation names
53
+ * @typeParam S - Union type of possible state names
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const agent = createAgent({
58
+ * belief: new DiscreteBelief({ safe: 0.5, danger: 0.5 }),
59
+ * transitionModel: myTransitions,
60
+ * observationModel: myObservations,
61
+ * preferences: { good: 0, bad: -5 },
62
+ * planningHorizon: 2,
63
+ * precision: 4
64
+ * });
65
+ *
66
+ * // Perception-action loop
67
+ * while (running) {
68
+ * const observation = environment.getObservation();
69
+ * const action = agent.step(observation);
70
+ * environment.execute(action);
71
+ * }
72
+ * ```
73
+ *
74
+ * @see {@link https://www.fil.ion.ucl.ac.uk/~karl/The%20free-energy%20principle%20A%20unified%20brain%20theory.pdf | Friston (2010) - The Free Energy Principle}
75
+ */
76
+ export declare class Agent<A extends string = string, O extends string = string, S extends string = string> {
77
+ private transitionModel;
78
+ private observationModel;
79
+ private preferences;
80
+ private _belief;
81
+ private _random;
82
+ private _planningHorizon;
83
+ private _precision;
84
+ private _habits;
85
+ /**
86
+ * Create a new Active Inference agent.
87
+ *
88
+ * @param belief - Initial belief over hidden states
89
+ * @param transitionModel - Model of state transitions P(s'|s,a)
90
+ * @param observationModel - Model of observations P(o|s)
91
+ * @param preferences - Preferred observations (log probabilities)
92
+ * @param random - Random number generator (optional, for reproducibility)
93
+ * @param planningHorizon - Steps to plan ahead (default: 1)
94
+ * @param precision - Action selection temperature (default: 1)
95
+ * @param habits - Prior over actions (default: uniform)
96
+ */
97
+ constructor(belief: Belief<S>, transitionModel: ITransitionModel<A, S>, observationModel: IObservationModel<O, S>, preferences: Preferences<O>, random?: Random, planningHorizon?: number, precision?: number, habits?: Partial<Habits<A>>);
98
+ /**
99
+ * Most likely hidden state (Maximum A Posteriori estimate).
100
+ */
101
+ get state(): S;
102
+ /**
103
+ * Uncertainty in the agent's beliefs (Shannon entropy in nats).
104
+ * Higher values indicate more uncertainty about the current state.
105
+ */
106
+ get uncertainty(): number;
107
+ /**
108
+ * Update beliefs based on a new observation (perception).
109
+ *
110
+ * Performs Bayesian inference:
111
+ * posterior ∝ likelihood × prior
112
+ * P(s|o) ∝ P(o|s) × P(s)
113
+ *
114
+ * This is the "perception" step of the Active Inference loop,
115
+ * where the agent updates its model of the world state.
116
+ *
117
+ * @param observation - The observation received from the environment
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * agent.observe('see_reward');
122
+ * console.log(agent.belief.argmax()); // Most likely state after observation
123
+ * ```
124
+ */
125
+ observe(observation: O): void;
126
+ /**
127
+ * Select an action by minimizing Expected Free Energy.
128
+ *
129
+ * The agent:
130
+ * 1. Generates all possible policies (action sequences) up to the planning horizon
131
+ * 2. Evaluates each policy's Expected Free Energy: G(π) = ambiguity + risk
132
+ * 3. Computes policy probabilities: P(π) ∝ E(π) × exp(-β × G(π))
133
+ * 4. Samples a policy and returns its first action
134
+ *
135
+ * Expected Free Energy (G) combines:
136
+ * - **Ambiguity**: Expected uncertainty about observations (epistemic)
137
+ * - **Risk**: Expected deviation from preferred observations (pragmatic)
138
+ *
139
+ * @returns The selected action to execute
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * const action = agent.act();
144
+ * environment.execute(action);
145
+ * ```
146
+ */
147
+ act(): A;
148
+ /**
149
+ * Complete perception-action cycle: observe then act.
150
+ *
151
+ * Convenience method that combines observe() and act() into
152
+ * a single call, representing one full cycle of the Active
153
+ * Inference loop.
154
+ *
155
+ * @param observation - The observation received from the environment
156
+ * @returns The selected action to execute
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * // Main loop
161
+ * let obs = environment.reset();
162
+ * while (!done) {
163
+ * const action = agent.step(obs);
164
+ * obs = environment.execute(action);
165
+ * }
166
+ * ```
167
+ */
168
+ step(observation: O): A;
169
+ /**
170
+ * Export current belief as a plain object for serialization.
171
+ *
172
+ * Useful for:
173
+ * - Saving agent state to storage
174
+ * - Transferring beliefs between agents
175
+ * - Debugging/visualization
176
+ *
177
+ * @returns Plain object mapping states to probabilities
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * const saved = agent.exportBelief();
182
+ * localStorage.setItem('belief', JSON.stringify(saved));
183
+ *
184
+ * // Later: restore
185
+ * const loaded = JSON.parse(localStorage.getItem('belief'));
186
+ * const newAgent = createAgent({
187
+ * belief: new DiscreteBelief(loaded),
188
+ * // ... other config
189
+ * });
190
+ * ```
191
+ */
192
+ exportBelief(): Distribution<S>;
193
+ /**
194
+ * Variational Free Energy of the current belief state.
195
+ *
196
+ * F = -H(Q) + E_Q[H(o|s)]
197
+ * = negative_entropy + ambiguity
198
+ *
199
+ * This is a measure of "surprise" or model-data mismatch.
200
+ * The Free Energy Principle states that agents act to minimize
201
+ * this quantity over time.
202
+ *
203
+ * Note: This is VFE (perception), not EFE (action selection).
204
+ *
205
+ * @returns Variational Free Energy (can be negative)
206
+ */
207
+ get freeEnergy(): number;
208
+ /**
209
+ * Generate all possible policies (action sequences) of given depth.
210
+ * For depth=2 with actions [a,b]: [[a,a], [a,b], [b,a], [b,b]]
211
+ */
212
+ private generatePolicies;
213
+ /**
214
+ * Evaluate a policy by computing its Expected Free Energy.
215
+ * G(π) = Σ_τ G(a_τ | Q_τ) where Q_τ is the predicted belief at time τ
216
+ */
217
+ private evaluatePolicy;
218
+ /**
219
+ * Compute ambiguity term of Expected Free Energy.
220
+ *
221
+ * Ambiguity = E_Q[H(o|s)] = -Σ_s Q(s) Σ_o P(o|s) log P(o|s)
222
+ *
223
+ * High ambiguity means the agent is uncertain about what
224
+ * observations to expect - the state-observation mapping is noisy.
225
+ * Minimizing ambiguity drives epistemic/exploratory behavior.
226
+ *
227
+ * @param predictedBelief - Predicted belief state
228
+ * @returns Ambiguity (non-negative)
229
+ */
230
+ private computeAmbiguity;
231
+ /**
232
+ * Compute risk term of Expected Free Energy.
233
+ *
234
+ * Risk = -E_Q[log P(o)] = -Σ_o Q(o) log C(o)
235
+ * where Q(o) = Σ_s P(o|s)Q(s) and C(o) = preferred observations
236
+ *
237
+ * High risk means expected observations are far from preferences.
238
+ * Minimizing risk drives pragmatic/goal-directed behavior.
239
+ *
240
+ * @param predictedBelief - Predicted belief state
241
+ * @returns Risk (higher = worse outcomes expected)
242
+ */
243
+ private computeRisk;
244
+ /**
245
+ * Sample an index from a probability distribution.
246
+ */
247
+ private sampleIndex;
248
+ /**
249
+ * Get habit prior for a policy (product of action habits).
250
+ */
251
+ private getPolicyHabit;
252
+ }