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.
- package/README.md +54 -1
- package/dist/beliefs/gaussian.belief.d.ts +31 -0
- package/dist/beliefs/gaussian.belief.js +36 -0
- package/dist/factory.d.ts +45 -149
- package/dist/factory.js +74 -44
- package/dist/index.d.ts +10 -1
- package/dist/index.js +7 -1
- package/dist/models/agent.model.d.ts +34 -209
- package/dist/models/agent.model.js +63 -259
- package/dist/models/learnable.model.d.ts +11 -0
- package/dist/models/learnable.model.js +1 -0
- package/dist/models/observation.model.d.ts +18 -1
- package/dist/models/transition.model.d.ts +9 -0
- package/dist/observation/dirichlet.observation.d.ts +82 -0
- package/dist/observation/dirichlet.observation.js +121 -0
- package/dist/observation/discrete.observation.d.ts +2 -1
- package/dist/observation/discrete.observation.js +3 -0
- package/dist/observation/gaussian.observation.d.ts +48 -0
- package/dist/observation/gaussian.observation.js +45 -0
- package/dist/preferences/dirichlet.preferences.d.ts +62 -0
- package/dist/preferences/dirichlet.preferences.js +79 -0
- package/dist/transition/dirichlet.transition.d.ts +80 -0
- package/dist/transition/dirichlet.transition.js +134 -0
- package/dist/transition/gaussian.transition.d.ts +49 -0
- package/dist/transition/gaussian.transition.js +46 -0
- package/package.json +6 -5
|
@@ -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.
|
|
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
|
-
"
|
|
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/
|
|
30
|
+
"url": "https://github.com/Codevanger/active-inference"
|
|
30
31
|
},
|
|
31
32
|
"bugs": {
|
|
32
|
-
"url": "https://github.com/
|
|
33
|
+
"url": "https://github.com/Codevanger/active-inference/issues"
|
|
33
34
|
},
|
|
34
|
-
"homepage": "https://github.com/
|
|
35
|
+
"homepage": "https://github.com/Codevanger/active-inference#readme",
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"prettier": "^3.8.1",
|
|
37
38
|
"typescript": "^5.9.3",
|