active-inference 0.0.1 → 0.2.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,134 @@
1
+ import { DiscreteBelief } from '../beliefs/discrete.belief';
2
+ /**
3
+ * Learnable transition model using Dirichlet concentrations.
4
+ *
5
+ * Instead of a fixed B matrix, this model maintains Dirichlet pseudo-counts
6
+ * from which the probability matrix P(s'|s,a) is derived by normalization:
7
+ *
8
+ * P(s'|s,a) = b[a][s][s'] / Σ_s'' b[a][s][s'']
9
+ *
10
+ * After each state transition, concentrations are updated using the
11
+ * outer product of prior and posterior beliefs:
12
+ *
13
+ * b[a][s][s'] += Q_prior(s) × Q_posterior(s')
14
+ *
15
+ * @typeParam A - Union type of possible action names
16
+ * @typeParam S - Union type of possible state names
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const transition = new DirichletTransition({
21
+ * move: {
22
+ * here: { here: 1, there: 5 }, // move from here → likely end up there
23
+ * there: { here: 1, there: 5 },
24
+ * },
25
+ * stay: {
26
+ * here: { here: 5, there: 1 },
27
+ * there: { here: 1, there: 5 },
28
+ * },
29
+ * });
30
+ * ```
31
+ */
32
+ export class DirichletTransition {
33
+ /**
34
+ * @param concentrations - Dirichlet pseudo-counts b[a][s][s'].
35
+ * Structure: action → current_state → next_state → count.
36
+ * Each value must be > 0.
37
+ */
38
+ constructor(concentrations) {
39
+ this.concentrations = concentrations;
40
+ this._matrix = null;
41
+ // Deep copy to avoid aliasing
42
+ this.concentrations = {};
43
+ for (const a of Object.keys(concentrations)) {
44
+ this.concentrations[a] = {};
45
+ for (const s of Object.keys(concentrations[a])) {
46
+ this.concentrations[a][s] = { ...concentrations[a][s] };
47
+ }
48
+ }
49
+ }
50
+ get actions() {
51
+ return Object.keys(this.concentrations);
52
+ }
53
+ get states() {
54
+ const firstAction = this.actions[0];
55
+ return Object.keys(this.concentrations[firstAction] || {});
56
+ }
57
+ /**
58
+ * Normalized probability matrix derived from concentrations.
59
+ * Lazily computed and cached; invalidated on learn().
60
+ *
61
+ * Row-wise normalization (per action a, per current state s):
62
+ * P(s'|s,a) = b[a][s][s'] / Σ_s'' b[a][s][s'']
63
+ */
64
+ get matrix() {
65
+ if (this._matrix === null) {
66
+ this._matrix = this.normalize();
67
+ }
68
+ return this._matrix;
69
+ }
70
+ getTransition(state, action) {
71
+ return this.matrix[action]?.[state] ?? {};
72
+ }
73
+ predict(belief, action) {
74
+ const newDist = {};
75
+ for (const state of this.states) {
76
+ newDist[state] = 0;
77
+ }
78
+ for (const currentState of belief.states) {
79
+ const transition = this.getTransition(currentState, action);
80
+ const currentProb = belief.probability(currentState);
81
+ for (const nextState of Object.keys(transition)) {
82
+ newDist[nextState] += transition[nextState] * currentProb;
83
+ }
84
+ }
85
+ return new DiscreteBelief(newDist);
86
+ }
87
+ /**
88
+ * Update concentrations from a state transition.
89
+ *
90
+ * Uses outer product of prior and posterior beliefs:
91
+ * b[a][s][s'] += Q_prior(s) × Q_posterior(s')
92
+ *
93
+ * This encodes the agent's best estimate of the transition
94
+ * that occurred, weighted by uncertainty in both states.
95
+ *
96
+ * @param action - The action that was taken
97
+ * @param priorBelief - Belief distribution before the action
98
+ * @param posteriorBelief - Belief distribution after observing the outcome
99
+ */
100
+ learn(action, priorBelief, posteriorBelief) {
101
+ for (const s of this.states) {
102
+ for (const sPrime of this.states) {
103
+ this.concentrations[action][s][sPrime] +=
104
+ (priorBelief[s] ?? 0) * (posteriorBelief[sPrime] ?? 0);
105
+ }
106
+ }
107
+ this._matrix = null;
108
+ }
109
+ /**
110
+ * Row-wise normalization of concentrations.
111
+ * For each (action, current_state):
112
+ * P(s'|s,a) = b[a][s][s'] / Σ_s'' b[a][s][s'']
113
+ */
114
+ normalize() {
115
+ const matrix = {};
116
+ for (const a of this.actions) {
117
+ matrix[a] = {};
118
+ for (const s of this.states) {
119
+ matrix[a][s] = {};
120
+ let rowSum = 0;
121
+ for (const sPrime of this.states) {
122
+ rowSum += this.concentrations[a][s][sPrime];
123
+ }
124
+ for (const sPrime of this.states) {
125
+ matrix[a][s][sPrime] =
126
+ rowSum > 0
127
+ ? this.concentrations[a][s][sPrime] / rowSum
128
+ : 0;
129
+ }
130
+ }
131
+ }
132
+ return matrix;
133
+ }
134
+ }
@@ -0,0 +1,49 @@
1
+ import { GaussianBelief } from '../beliefs/gaussian.belief';
2
+ /**
3
+ * Configuration for a single action's effect on continuous state.
4
+ *
5
+ * @param fn - Deterministic transition function: μ' = fn(μ)
6
+ * @param noise - Process noise variance added after transition
7
+ */
8
+ export interface GaussianActionModel {
9
+ fn: (x: number) => number;
10
+ noise: number;
11
+ }
12
+ /**
13
+ * Maps each discrete action to its continuous state transition.
14
+ */
15
+ export type GaussianTransitionConfig<A extends string = string> = Record<A, GaussianActionModel>;
16
+ /**
17
+ * State transition model for continuous (Gaussian) Active Inference
18
+ * with discrete actions.
19
+ *
20
+ * Each action maps to a transition function and process noise.
21
+ * Belief propagation uses first-order approximation:
22
+ * μ' = fn(μ)
23
+ * σ²' = fn'(μ)² · σ² + noise
24
+ *
25
+ * where fn'(μ) is approximated via finite differences.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const transition = new GaussianTransition({
30
+ * accelerate: { fn: x => x + 1, noise: 0.1 },
31
+ * brake: { fn: x => x * 0.5, noise: 0.05 },
32
+ * });
33
+ *
34
+ * const predicted = transition.predict(belief, 'accelerate');
35
+ * ```
36
+ */
37
+ export declare class GaussianTransition<A extends string = string> {
38
+ private readonly config;
39
+ private readonly _actions;
40
+ constructor(config: GaussianTransitionConfig<A>);
41
+ get actions(): A[];
42
+ /**
43
+ * Predict belief after taking an action.
44
+ *
45
+ * Uses first-order (Extended Kalman) approximation for
46
+ * nonlinear transition functions.
47
+ */
48
+ predict(belief: GaussianBelief, action: A): GaussianBelief;
49
+ }
@@ -0,0 +1,46 @@
1
+ import { GaussianBelief } from '../beliefs/gaussian.belief';
2
+ /**
3
+ * State transition model for continuous (Gaussian) Active Inference
4
+ * with discrete actions.
5
+ *
6
+ * Each action maps to a transition function and process noise.
7
+ * Belief propagation uses first-order approximation:
8
+ * μ' = fn(μ)
9
+ * σ²' = fn'(μ)² · σ² + noise
10
+ *
11
+ * where fn'(μ) is approximated via finite differences.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const transition = new GaussianTransition({
16
+ * accelerate: { fn: x => x + 1, noise: 0.1 },
17
+ * brake: { fn: x => x * 0.5, noise: 0.05 },
18
+ * });
19
+ *
20
+ * const predicted = transition.predict(belief, 'accelerate');
21
+ * ```
22
+ */
23
+ export class GaussianTransition {
24
+ constructor(config) {
25
+ this.config = config;
26
+ this._actions = Object.keys(config);
27
+ }
28
+ get actions() {
29
+ return this._actions;
30
+ }
31
+ /**
32
+ * Predict belief after taking an action.
33
+ *
34
+ * Uses first-order (Extended Kalman) approximation for
35
+ * nonlinear transition functions.
36
+ */
37
+ predict(belief, action) {
38
+ const model = this.config[action];
39
+ const newMean = model.fn(belief.mean);
40
+ // Numerical derivative via central finite difference
41
+ const h = 1e-5;
42
+ const derivative = (model.fn(belief.mean + h) - model.fn(belief.mean - h)) / (2 * h);
43
+ const newVariance = derivative * derivative * belief.variance + model.noise;
44
+ return new GaussianBelief(newMean, newVariance);
45
+ }
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "active-inference",
3
- "version": "0.0.1",
3
+ "version": "0.2.1",
4
4
  "description": "Active Inference Framework implementation for JavaScript/TypeScript",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,8 @@
11
11
  "test": "vitest run",
12
12
  "test:watch": "vitest",
13
13
  "build": "tsc",
14
- "prepublishOnly": "npm run build"
14
+ "prepublish": "npm run build",
15
+ "build:examples": "npx esbuild src/index.ts --bundle --format=esm --outfile=examples/cart-pole/active-inference.bundle.js && cp examples/cart-pole/active-inference.bundle.js examples/trolley-problem/active-inference.bundle.js"
15
16
  },
16
17
  "keywords": [
17
18
  "active-inference",
@@ -26,12 +27,12 @@
26
27
  "license": "MIT",
27
28
  "repository": {
28
29
  "type": "git",
29
- "url": "https://github.com/codevanger/active-inference"
30
+ "url": "https://github.com/Codevanger/active-inference"
30
31
  },
31
32
  "bugs": {
32
- "url": "https://github.com/codevanger/active-inference/issues"
33
+ "url": "https://github.com/Codevanger/active-inference/issues"
33
34
  },
34
- "homepage": "https://github.com/codevanger/active-inference#readme",
35
+ "homepage": "https://github.com/Codevanger/active-inference#readme",
35
36
  "devDependencies": {
36
37
  "prettier": "^3.8.1",
37
38
  "typescript": "^5.9.3",