@thi.ng/cellular 0.1.0
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/1d.d.ts +196 -0
- package/1d.js +312 -0
- package/CHANGELOG.md +19 -0
- package/LICENSE +201 -0
- package/README.md +310 -0
- package/api.d.ts +69 -0
- package/api.js +1 -0
- package/index.d.ts +3 -0
- package/index.js +2 -0
- package/package.json +96 -0
package/1d.d.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { IClear, UIntArray } from "@thi.ng/api";
|
|
2
|
+
import type { IRandom } from "@thi.ng/random";
|
|
3
|
+
import type { CAConfig1D, CASpec1D, Kernel, Target } from "./api.js";
|
|
4
|
+
/**
|
|
5
|
+
* Standard Wolfram automata 3-neighborhood (no history)
|
|
6
|
+
*/
|
|
7
|
+
export declare const WOLFRAM3: Kernel;
|
|
8
|
+
/**
|
|
9
|
+
* Standard 5-neighborhood (no history)
|
|
10
|
+
*/
|
|
11
|
+
export declare const WOLFRAM5: Kernel;
|
|
12
|
+
/**
|
|
13
|
+
* Standard 7-neighborhood (no history)
|
|
14
|
+
*/
|
|
15
|
+
export declare const WOLFRAM7: Kernel;
|
|
16
|
+
/**
|
|
17
|
+
* Implementation of a 1D cellular automata environment with support for
|
|
18
|
+
* multiple automata, each with its own settings for replication rules,
|
|
19
|
+
* arbitrary neighborhood kernels (optionally with short term memory) and number
|
|
20
|
+
* of cell states, all selectable via a shared per-cell mask array. This generic
|
|
21
|
+
* setup enables many novel and unusual CA setups as well as coevolution of
|
|
22
|
+
* multiple CAs within a shared environment.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* ### Neighborhoods
|
|
26
|
+
*
|
|
27
|
+
* Cell neighborhoods are defined via an arbitrary number of 2D offset vectors
|
|
28
|
+
* `[x, y]`, where `x` coordinates are horizontal offsets and positive `y`
|
|
29
|
+
* coordinates are used to refer to previous generations (e.g. 0 = current gen,
|
|
30
|
+
* 1 = T-1, 2 = T-2 etc.) and thereby providing a form of short term memory for
|
|
31
|
+
* that specific automata. Negative `y` coords will lead to cells being ignored.
|
|
32
|
+
*
|
|
33
|
+
* ### Rule encoding
|
|
34
|
+
*
|
|
35
|
+
* Automata rules are encoded as JS `BigInt` values and are considered
|
|
36
|
+
* anisotropic by default. If isotropy is desired, it has to be explicitly
|
|
37
|
+
* pre-encoded [out of scope of this library]. There's also built-in optional
|
|
38
|
+
* support for position independent neighborhood encoding, only considering the
|
|
39
|
+
* number/count of non-zero cells. An encoded rule ID and its overall magnitude
|
|
40
|
+
* is directly related and dependent on the size and shape of its kernel config,
|
|
41
|
+
* e.g.:
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* kernel = [[-2, 1], [-1, 0], [0, 0], [1, 0], [2, 1]]
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* This example kernel defines a 5-cell neighborhood with a max. short term
|
|
48
|
+
* memory of one additional previous generation (i.e. the [-2,1] and [2,1]
|
|
49
|
+
* offsets)
|
|
50
|
+
*
|
|
51
|
+
* The rules related to this kernel have a 32 bit address space (4 billion
|
|
52
|
+
* possibilities), due to 2^5 = 32 and each kernel offset being assigned a
|
|
53
|
+
* distinct bit value by default, i.e. first kernel offset = 2^0, second kernel
|
|
54
|
+
* offset = 2^1, third = 2^2, fourth = 2^3, fifth = 2^4. Via the
|
|
55
|
+
* {@link CASpec1D.positional} config option, this behavior can be overridden
|
|
56
|
+
* per kernel, to achieve position-independent kernels (with much smaller rule
|
|
57
|
+
* spaces).
|
|
58
|
+
*
|
|
59
|
+
* Given the following example cell matrix with the center cell highlighted with
|
|
60
|
+
* caret (`^`):
|
|
61
|
+
*
|
|
62
|
+
* ```text
|
|
63
|
+
* T-1: 2 0 1 2 1
|
|
64
|
+
* T-0: 0 1 0 3 0
|
|
65
|
+
* ^
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* The above example kernel will select the following values and assign bit
|
|
69
|
+
* positions (for all non-zero cell states) to compute a summed ID:
|
|
70
|
+
*
|
|
71
|
+
* | k index | offset | cell value | encoded |
|
|
72
|
+
* |--------:|-----------|-----------:|--------:|
|
|
73
|
+
* | 0 | `[-2, 1]` | 2 | 1 |
|
|
74
|
+
* | 1 | `[-1, 0]` | 1 | 2 |
|
|
75
|
+
* | 2 | `[0, 0]` | 0 | 0 |
|
|
76
|
+
* | 3 | `[1, 0]` | 3 | 8 |
|
|
77
|
+
* | 4 | `[2, 1]` | 1 | 16 |
|
|
78
|
+
*
|
|
79
|
+
* Final encoded neighborhood sum: 1 + 2 + 8 + 16 = 27
|
|
80
|
+
*
|
|
81
|
+
* To determine if a the current cell should be active or not in the next
|
|
82
|
+
* generation, we now use that encoded sum as bit position to test a single bit
|
|
83
|
+
* of the automata's rule ID, i.e. here we're testing bit 27. If that
|
|
84
|
+
* corresponding bit is set in the rule ID, the cell's state will be increased
|
|
85
|
+
* by 1.
|
|
86
|
+
*
|
|
87
|
+
* ### Cell states
|
|
88
|
+
*
|
|
89
|
+
* Each automata config can define a max. number of possible cell states (aka
|
|
90
|
+
* age). Once a cell reaches the configured `numStates`, it automatically resets
|
|
91
|
+
* to zero. This is by default, but can be overridden via the
|
|
92
|
+
* {@link CASpec1D.reset} option. Conversely, if the corresponding bit is _not_
|
|
93
|
+
* set in the rule ID, the cell state will be zeroed too.
|
|
94
|
+
*
|
|
95
|
+
* ### Wraparound
|
|
96
|
+
*
|
|
97
|
+
* By default the environment is configured to be toroidal, i.e. both left/right
|
|
98
|
+
* sides of the env are connected. The behavior can be controlled via a ctor arg
|
|
99
|
+
* and/or at runtime via the {@link MultiCA1D.wrap} property.
|
|
100
|
+
*
|
|
101
|
+
* ### Masks
|
|
102
|
+
*
|
|
103
|
+
* The {@link MultiCA1D.mask} array can be used to select different CA
|
|
104
|
+
* configurations for each cell in the environment. Because this mask array is
|
|
105
|
+
* initialized to zero, only the first CA configuration will be used for all
|
|
106
|
+
* cells in the environment by default. It's the user's responsibility to manage
|
|
107
|
+
* the mask and select/enable other (if any) CA configs for individual cells
|
|
108
|
+
* (usually cell ranges). The values stored in this array correspond to the
|
|
109
|
+
* indices of the {@link MultiCA1D.config} array given at construction.
|
|
110
|
+
*
|
|
111
|
+
* ### Limits
|
|
112
|
+
*
|
|
113
|
+
* Due to using `Uint8Arrays` for storage, only up to 256 cell states are
|
|
114
|
+
* supported. The same limit applies to the number of CA configs given.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* // classic Wolfram Rule 110 automata
|
|
119
|
+
* const wolfram = new MultiCA1D(
|
|
120
|
+
* [{
|
|
121
|
+
* kernel: [[-1, 0], [0, 0], [1, 0]],
|
|
122
|
+
* rule: 110,
|
|
123
|
+
* states: 2,
|
|
124
|
+
* reset: false
|
|
125
|
+
* }],
|
|
126
|
+
* 256
|
|
127
|
+
* )
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export declare class MultiCA1D implements IClear {
|
|
131
|
+
width: number;
|
|
132
|
+
wrap: boolean;
|
|
133
|
+
configs: CAConfig1D[];
|
|
134
|
+
rows: number;
|
|
135
|
+
numStates: number;
|
|
136
|
+
mask: Uint8Array;
|
|
137
|
+
gens: Uint8Array[];
|
|
138
|
+
constructor(configs: CASpec1D[], width: number, wrap?: boolean);
|
|
139
|
+
get current(): Uint8Array;
|
|
140
|
+
get previous(): Uint8Array;
|
|
141
|
+
clear(): void;
|
|
142
|
+
clearCurrent(): void;
|
|
143
|
+
resize(width: number): void;
|
|
144
|
+
/**
|
|
145
|
+
* Sets a parametric pattern in the current generation or mask array.
|
|
146
|
+
*
|
|
147
|
+
* @param target - target buffer ID to apply pattern
|
|
148
|
+
* @param width - number of consecutive cells per segment
|
|
149
|
+
* @param stride - number of cells between each pattern segment
|
|
150
|
+
* @param val - start cell value per segment
|
|
151
|
+
* @param inc - cell value increment
|
|
152
|
+
* @param offset - start cell offset
|
|
153
|
+
*/
|
|
154
|
+
setPattern(target: Target, width: number, stride: number, val?: number, inc?: number, offset?: number): this;
|
|
155
|
+
/**
|
|
156
|
+
* Sets cells in current generation array to a random state using given
|
|
157
|
+
* `probability` and optional PRNG ({@link @thi.ng/random#IRandom} instance).
|
|
158
|
+
*
|
|
159
|
+
* @param target
|
|
160
|
+
* @param prob
|
|
161
|
+
* @param rnd
|
|
162
|
+
*/
|
|
163
|
+
setNoise(target: Target, prob?: number, rnd?: IRandom): this;
|
|
164
|
+
/**
|
|
165
|
+
* Computes a single new generation using current cell states and mask. See
|
|
166
|
+
* {@link MultiCA1D.updateImage} for batch updates.
|
|
167
|
+
*/
|
|
168
|
+
update(): void;
|
|
169
|
+
/**
|
|
170
|
+
* Batch version of {@link MultiCA1D.update} to compute an entire image of
|
|
171
|
+
* given `height` (and same width as this CA instance has been configured
|
|
172
|
+
* to). Fill given `pixels` array with consecutive generations. For each
|
|
173
|
+
* iteration there's `perturb` probability (default: 0%) to call
|
|
174
|
+
* {@link MultiCA1D.setNoise} with given `density` (default: 5%) and using
|
|
175
|
+
* optionally provided PRNG. This can be helpful to sporadically introduce
|
|
176
|
+
* noise into the sim and break otherwise constant patterns emerging.
|
|
177
|
+
*
|
|
178
|
+
* @param pixels
|
|
179
|
+
* @param height
|
|
180
|
+
* @param perturb
|
|
181
|
+
* @param density
|
|
182
|
+
* @param rnd
|
|
183
|
+
*/
|
|
184
|
+
updateImage(pixels: UIntArray, height: number, perturb?: number, density?: number, rnd?: IRandom): void;
|
|
185
|
+
rotate(dir: number): void;
|
|
186
|
+
protected _getTarget(target: Target): [Uint8Array, number];
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Creates a random rule ID for given `kernelSize` and using optionally provided
|
|
190
|
+
* `rnd` {@link @thi.ng/random#IRandom} instance.
|
|
191
|
+
*
|
|
192
|
+
* @param kernelSize
|
|
193
|
+
* @param rnd
|
|
194
|
+
*/
|
|
195
|
+
export declare const randomRule1D: (kernelSize: number, rnd?: IRandom) => bigint;
|
|
196
|
+
//# sourceMappingURL=1d.d.ts.map
|
package/1d.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { isBigInt } from "@thi.ng/checks/is-bigint";
|
|
2
|
+
import { assert } from "@thi.ng/errors/assert";
|
|
3
|
+
import { SYSTEM } from "@thi.ng/random/system";
|
|
4
|
+
import { repeat } from "@thi.ng/transducers";
|
|
5
|
+
import { map } from "@thi.ng/transducers/map";
|
|
6
|
+
import { mapcat } from "@thi.ng/transducers/mapcat";
|
|
7
|
+
import { max } from "@thi.ng/transducers/max";
|
|
8
|
+
import { pluck } from "@thi.ng/transducers/pluck";
|
|
9
|
+
import { repeatedly } from "@thi.ng/transducers/repeatedly";
|
|
10
|
+
import { transduce } from "@thi.ng/transducers/transduce";
|
|
11
|
+
const $0 = BigInt(0);
|
|
12
|
+
const $1 = BigInt(1);
|
|
13
|
+
const $32 = BigInt(32);
|
|
14
|
+
/**
|
|
15
|
+
* Standard Wolfram automata 3-neighborhood (no history)
|
|
16
|
+
*/
|
|
17
|
+
export const WOLFRAM3 = [
|
|
18
|
+
[-1, 0],
|
|
19
|
+
[0, 0],
|
|
20
|
+
[1, 0],
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Standard 5-neighborhood (no history)
|
|
24
|
+
*/
|
|
25
|
+
export const WOLFRAM5 = [[-2, 0], ...WOLFRAM3, [2, 0]];
|
|
26
|
+
/**
|
|
27
|
+
* Standard 7-neighborhood (no history)
|
|
28
|
+
*/
|
|
29
|
+
export const WOLFRAM7 = [[-3, 0], ...WOLFRAM5, [3, 0]];
|
|
30
|
+
/**
|
|
31
|
+
* Implementation of a 1D cellular automata environment with support for
|
|
32
|
+
* multiple automata, each with its own settings for replication rules,
|
|
33
|
+
* arbitrary neighborhood kernels (optionally with short term memory) and number
|
|
34
|
+
* of cell states, all selectable via a shared per-cell mask array. This generic
|
|
35
|
+
* setup enables many novel and unusual CA setups as well as coevolution of
|
|
36
|
+
* multiple CAs within a shared environment.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* ### Neighborhoods
|
|
40
|
+
*
|
|
41
|
+
* Cell neighborhoods are defined via an arbitrary number of 2D offset vectors
|
|
42
|
+
* `[x, y]`, where `x` coordinates are horizontal offsets and positive `y`
|
|
43
|
+
* coordinates are used to refer to previous generations (e.g. 0 = current gen,
|
|
44
|
+
* 1 = T-1, 2 = T-2 etc.) and thereby providing a form of short term memory for
|
|
45
|
+
* that specific automata. Negative `y` coords will lead to cells being ignored.
|
|
46
|
+
*
|
|
47
|
+
* ### Rule encoding
|
|
48
|
+
*
|
|
49
|
+
* Automata rules are encoded as JS `BigInt` values and are considered
|
|
50
|
+
* anisotropic by default. If isotropy is desired, it has to be explicitly
|
|
51
|
+
* pre-encoded [out of scope of this library]. There's also built-in optional
|
|
52
|
+
* support for position independent neighborhood encoding, only considering the
|
|
53
|
+
* number/count of non-zero cells. An encoded rule ID and its overall magnitude
|
|
54
|
+
* is directly related and dependent on the size and shape of its kernel config,
|
|
55
|
+
* e.g.:
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* kernel = [[-2, 1], [-1, 0], [0, 0], [1, 0], [2, 1]]
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* This example kernel defines a 5-cell neighborhood with a max. short term
|
|
62
|
+
* memory of one additional previous generation (i.e. the [-2,1] and [2,1]
|
|
63
|
+
* offsets)
|
|
64
|
+
*
|
|
65
|
+
* The rules related to this kernel have a 32 bit address space (4 billion
|
|
66
|
+
* possibilities), due to 2^5 = 32 and each kernel offset being assigned a
|
|
67
|
+
* distinct bit value by default, i.e. first kernel offset = 2^0, second kernel
|
|
68
|
+
* offset = 2^1, third = 2^2, fourth = 2^3, fifth = 2^4. Via the
|
|
69
|
+
* {@link CASpec1D.positional} config option, this behavior can be overridden
|
|
70
|
+
* per kernel, to achieve position-independent kernels (with much smaller rule
|
|
71
|
+
* spaces).
|
|
72
|
+
*
|
|
73
|
+
* Given the following example cell matrix with the center cell highlighted with
|
|
74
|
+
* caret (`^`):
|
|
75
|
+
*
|
|
76
|
+
* ```text
|
|
77
|
+
* T-1: 2 0 1 2 1
|
|
78
|
+
* T-0: 0 1 0 3 0
|
|
79
|
+
* ^
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* The above example kernel will select the following values and assign bit
|
|
83
|
+
* positions (for all non-zero cell states) to compute a summed ID:
|
|
84
|
+
*
|
|
85
|
+
* | k index | offset | cell value | encoded |
|
|
86
|
+
* |--------:|-----------|-----------:|--------:|
|
|
87
|
+
* | 0 | `[-2, 1]` | 2 | 1 |
|
|
88
|
+
* | 1 | `[-1, 0]` | 1 | 2 |
|
|
89
|
+
* | 2 | `[0, 0]` | 0 | 0 |
|
|
90
|
+
* | 3 | `[1, 0]` | 3 | 8 |
|
|
91
|
+
* | 4 | `[2, 1]` | 1 | 16 |
|
|
92
|
+
*
|
|
93
|
+
* Final encoded neighborhood sum: 1 + 2 + 8 + 16 = 27
|
|
94
|
+
*
|
|
95
|
+
* To determine if a the current cell should be active or not in the next
|
|
96
|
+
* generation, we now use that encoded sum as bit position to test a single bit
|
|
97
|
+
* of the automata's rule ID, i.e. here we're testing bit 27. If that
|
|
98
|
+
* corresponding bit is set in the rule ID, the cell's state will be increased
|
|
99
|
+
* by 1.
|
|
100
|
+
*
|
|
101
|
+
* ### Cell states
|
|
102
|
+
*
|
|
103
|
+
* Each automata config can define a max. number of possible cell states (aka
|
|
104
|
+
* age). Once a cell reaches the configured `numStates`, it automatically resets
|
|
105
|
+
* to zero. This is by default, but can be overridden via the
|
|
106
|
+
* {@link CASpec1D.reset} option. Conversely, if the corresponding bit is _not_
|
|
107
|
+
* set in the rule ID, the cell state will be zeroed too.
|
|
108
|
+
*
|
|
109
|
+
* ### Wraparound
|
|
110
|
+
*
|
|
111
|
+
* By default the environment is configured to be toroidal, i.e. both left/right
|
|
112
|
+
* sides of the env are connected. The behavior can be controlled via a ctor arg
|
|
113
|
+
* and/or at runtime via the {@link MultiCA1D.wrap} property.
|
|
114
|
+
*
|
|
115
|
+
* ### Masks
|
|
116
|
+
*
|
|
117
|
+
* The {@link MultiCA1D.mask} array can be used to select different CA
|
|
118
|
+
* configurations for each cell in the environment. Because this mask array is
|
|
119
|
+
* initialized to zero, only the first CA configuration will be used for all
|
|
120
|
+
* cells in the environment by default. It's the user's responsibility to manage
|
|
121
|
+
* the mask and select/enable other (if any) CA configs for individual cells
|
|
122
|
+
* (usually cell ranges). The values stored in this array correspond to the
|
|
123
|
+
* indices of the {@link MultiCA1D.config} array given at construction.
|
|
124
|
+
*
|
|
125
|
+
* ### Limits
|
|
126
|
+
*
|
|
127
|
+
* Due to using `Uint8Arrays` for storage, only up to 256 cell states are
|
|
128
|
+
* supported. The same limit applies to the number of CA configs given.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* // classic Wolfram Rule 110 automata
|
|
133
|
+
* const wolfram = new MultiCA1D(
|
|
134
|
+
* [{
|
|
135
|
+
* kernel: [[-1, 0], [0, 0], [1, 0]],
|
|
136
|
+
* rule: 110,
|
|
137
|
+
* states: 2,
|
|
138
|
+
* reset: false
|
|
139
|
+
* }],
|
|
140
|
+
* 256
|
|
141
|
+
* )
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export class MultiCA1D {
|
|
145
|
+
constructor(configs, width, wrap = true) {
|
|
146
|
+
this.width = width;
|
|
147
|
+
this.wrap = wrap;
|
|
148
|
+
this.configs = configs.map(__compileSpec);
|
|
149
|
+
this.rows =
|
|
150
|
+
transduce(mapcat((c) => map((k) => k[1], c.kernel)), max(), configs) + 1;
|
|
151
|
+
this.numStates = transduce(pluck("states"), max(), configs);
|
|
152
|
+
assert(this.numStates >= 2 && this.numStates <= 256, "num states must be in [2..256] range");
|
|
153
|
+
this.resize(width);
|
|
154
|
+
}
|
|
155
|
+
get current() {
|
|
156
|
+
return this.gens[1];
|
|
157
|
+
}
|
|
158
|
+
get previous() {
|
|
159
|
+
return this.gens[2 % this.gens.length];
|
|
160
|
+
}
|
|
161
|
+
clear() {
|
|
162
|
+
this.gens.forEach((g) => g.fill(0));
|
|
163
|
+
}
|
|
164
|
+
clearCurrent() {
|
|
165
|
+
this.current.fill(0);
|
|
166
|
+
}
|
|
167
|
+
resize(width) {
|
|
168
|
+
this.width = width;
|
|
169
|
+
this.mask = new Uint8Array(width);
|
|
170
|
+
this.gens = [...repeatedly(() => new Uint8Array(width), this.rows + 1)];
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Sets a parametric pattern in the current generation or mask array.
|
|
174
|
+
*
|
|
175
|
+
* @param target - target buffer ID to apply pattern
|
|
176
|
+
* @param width - number of consecutive cells per segment
|
|
177
|
+
* @param stride - number of cells between each pattern segment
|
|
178
|
+
* @param val - start cell value per segment
|
|
179
|
+
* @param inc - cell value increment
|
|
180
|
+
* @param offset - start cell offset
|
|
181
|
+
*/
|
|
182
|
+
setPattern(target, width, stride, val = 1, inc = 0, offset = 0) {
|
|
183
|
+
const [dest, num] = this._getTarget(target);
|
|
184
|
+
for (let x = offset, w = this.width; x < w; x += stride) {
|
|
185
|
+
for (let k = 0, v = val; k < width; k++, v += inc) {
|
|
186
|
+
dest[x + k] = v % num;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Sets cells in current generation array to a random state using given
|
|
193
|
+
* `probability` and optional PRNG ({@link @thi.ng/random#IRandom} instance).
|
|
194
|
+
*
|
|
195
|
+
* @param target
|
|
196
|
+
* @param prob
|
|
197
|
+
* @param rnd
|
|
198
|
+
*/
|
|
199
|
+
setNoise(target, prob = 0.5, rnd = SYSTEM) {
|
|
200
|
+
const [dest, num] = this._getTarget(target);
|
|
201
|
+
for (let x = 0, width = this.width; x < width; x++) {
|
|
202
|
+
if (rnd.float() < prob)
|
|
203
|
+
dest[x] = rnd.int() % num;
|
|
204
|
+
}
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Computes a single new generation using current cell states and mask. See
|
|
209
|
+
* {@link MultiCA1D.updateImage} for batch updates.
|
|
210
|
+
*/
|
|
211
|
+
update() {
|
|
212
|
+
const { width, gens, configs, mask, wrap } = this;
|
|
213
|
+
const [next, curr] = gens;
|
|
214
|
+
for (let x = 0; x < width; x++) {
|
|
215
|
+
const { rule, kernel, weights, fn } = configs[mask[x]];
|
|
216
|
+
let sum = $0;
|
|
217
|
+
for (let i = 0, n = kernel.length; i < n; i++) {
|
|
218
|
+
const k = kernel[i];
|
|
219
|
+
let xx = x + k[0];
|
|
220
|
+
if (wrap) {
|
|
221
|
+
if (xx < 0)
|
|
222
|
+
xx += width;
|
|
223
|
+
else if (xx >= width)
|
|
224
|
+
xx -= width;
|
|
225
|
+
}
|
|
226
|
+
else if (xx < 0 || xx >= width)
|
|
227
|
+
continue;
|
|
228
|
+
const y = k[1];
|
|
229
|
+
if (y >= 0 && gens[1 + y][xx] !== 0)
|
|
230
|
+
sum += weights[i];
|
|
231
|
+
}
|
|
232
|
+
next[x] = rule & ($1 << sum) ? fn(curr[x]) : 0;
|
|
233
|
+
}
|
|
234
|
+
gens.unshift(gens.pop());
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Batch version of {@link MultiCA1D.update} to compute an entire image of
|
|
238
|
+
* given `height` (and same width as this CA instance has been configured
|
|
239
|
+
* to). Fill given `pixels` array with consecutive generations. For each
|
|
240
|
+
* iteration there's `perturb` probability (default: 0%) to call
|
|
241
|
+
* {@link MultiCA1D.setNoise} with given `density` (default: 5%) and using
|
|
242
|
+
* optionally provided PRNG. This can be helpful to sporadically introduce
|
|
243
|
+
* noise into the sim and break otherwise constant patterns emerging.
|
|
244
|
+
*
|
|
245
|
+
* @param pixels
|
|
246
|
+
* @param height
|
|
247
|
+
* @param perturb
|
|
248
|
+
* @param density
|
|
249
|
+
* @param rnd
|
|
250
|
+
*/
|
|
251
|
+
updateImage(pixels, height, perturb = 0, density = 0.05, rnd = SYSTEM) {
|
|
252
|
+
for (let y = 0; y < height; y++) {
|
|
253
|
+
rnd.float() < perturb && this.setNoise("cells", density, rnd);
|
|
254
|
+
this.update();
|
|
255
|
+
pixels.set(this.current, y * this.width);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
rotate(dir) {
|
|
259
|
+
__rotate(this.current, dir);
|
|
260
|
+
__rotate(this.mask, dir);
|
|
261
|
+
}
|
|
262
|
+
_getTarget(target) {
|
|
263
|
+
return target === "cells"
|
|
264
|
+
? [this.current, this.numStates]
|
|
265
|
+
: [this.mask, this.configs.length];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const __compileSpec = ({ rule, kernel, positional, states, reset, }) => {
|
|
269
|
+
const max = states - 1;
|
|
270
|
+
return {
|
|
271
|
+
kernel,
|
|
272
|
+
states,
|
|
273
|
+
rule: isBigInt(rule) ? rule : BigInt(rule),
|
|
274
|
+
weights: positional !== false
|
|
275
|
+
? kernel.map((_, i) => BigInt(2) ** BigInt(i))
|
|
276
|
+
: [...repeat($1, kernel.length)],
|
|
277
|
+
fn: reset !== false
|
|
278
|
+
? (y) => (++y >= states ? 0 : y)
|
|
279
|
+
: (y) => (++y >= max ? max : y),
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
const __rotate = (buf, dir) => {
|
|
283
|
+
if (dir < 0) {
|
|
284
|
+
const tmp = buf.slice(0, -dir);
|
|
285
|
+
buf.copyWithin(0, -dir);
|
|
286
|
+
buf.set(tmp, buf.length + dir);
|
|
287
|
+
}
|
|
288
|
+
else if (dir > 0) {
|
|
289
|
+
const tmp = buf.slice(buf.length - dir);
|
|
290
|
+
buf.copyWithin(dir, 0);
|
|
291
|
+
buf.set(tmp, 0);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* Creates a random rule ID for given `kernelSize` and using optionally provided
|
|
296
|
+
* `rnd` {@link @thi.ng/random#IRandom} instance.
|
|
297
|
+
*
|
|
298
|
+
* @param kernelSize
|
|
299
|
+
* @param rnd
|
|
300
|
+
*/
|
|
301
|
+
export const randomRule1D = (kernelSize, rnd = SYSTEM) => {
|
|
302
|
+
const n = BigInt(2 ** kernelSize);
|
|
303
|
+
let id = $0;
|
|
304
|
+
for (let i = $0; i < n; i += $32) {
|
|
305
|
+
id <<= $32;
|
|
306
|
+
let mask = n - i;
|
|
307
|
+
if (mask > $32)
|
|
308
|
+
mask = $32;
|
|
309
|
+
id |= BigInt(rnd.int()) & (($1 << mask) - $1);
|
|
310
|
+
}
|
|
311
|
+
return id;
|
|
312
|
+
};
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
- **Last updated**: 2022-06-09T16:14:01Z
|
|
4
|
+
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
|
+
|
|
6
|
+
All notable changes to this project will be documented in this file.
|
|
7
|
+
See [Conventional Commits](https://conventionalcommits.org/) for commit guidelines.
|
|
8
|
+
|
|
9
|
+
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
|
+
and/or version bumps of transitive dependencies.
|
|
11
|
+
|
|
12
|
+
## [0.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/cellular@0.1.0) (2022-06-09)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- add kernel presets, simplify CASpec1D ([3ee4a25](https://github.com/thi-ng/umbrella/commit/3ee4a25))
|
|
17
|
+
- update setPattern/setNoise() ([2ef2013](https://github.com/thi-ng/umbrella/commit/2ef2013))
|
|
18
|
+
- add support to operate on diff target buffers (cells/mask)
|
|
19
|
+
- import as new pkg ([aaec2ed](https://github.com/thi-ng/umbrella/commit/aaec2ed))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright {yyyy} {name of copyright owner}
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
<!-- This file is generated - DO NOT EDIT! -->
|
|
2
|
+
|
|
3
|
+
# 
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@thi.ng/cellular)
|
|
6
|
+

|
|
7
|
+
[](https://twitter.com/thing_umbrella)
|
|
8
|
+
|
|
9
|
+
This project is part of the
|
|
10
|
+
[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo.
|
|
11
|
+
|
|
12
|
+
- [About](#about)
|
|
13
|
+
- [Neighborhoods](#neighborhoods)
|
|
14
|
+
- [Rule encoding](#rule-encoding)
|
|
15
|
+
- [Cell states](#cell-states)
|
|
16
|
+
- [Wraparound](#wraparound)
|
|
17
|
+
- [Masks](#masks)
|
|
18
|
+
- [Limits](#limits)
|
|
19
|
+
- [Status](#status)
|
|
20
|
+
- [Related packages](#related-packages)
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Dependencies](#dependencies)
|
|
23
|
+
- [API](#api)
|
|
24
|
+
- [Code examples](#code-examples)
|
|
25
|
+
- [Classic Wolfram](#classic-wolfram)
|
|
26
|
+
- [Custom kernels & multiple rules](#custom-kernels--multiple-rules)
|
|
27
|
+
- [Authors](#authors)
|
|
28
|
+
- [License](#license)
|
|
29
|
+
|
|
30
|
+
## About
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
Highly customizable 1D cellular automata, shared env, multiple rules, arbitrary sized/shaped neighborhoods, short term memory, cell states etc..
|
|
35
|
+
|
|
36
|
+
The generic implementation provided by this package enables many novel and
|
|
37
|
+
unusual CA setups as well as coevolution of multiple CAs within a shared
|
|
38
|
+
environment.
|
|
39
|
+
|
|
40
|
+
This library also forms the core of **C-SCAPE**, a generative art project by the
|
|
41
|
+
author. Visit the [project site](https://www.fxhash.xyz/generative/slug/c-scape)
|
|
42
|
+
to get a better impression and overview of the multitude of possible results
|
|
43
|
+
(still nothing more than a small glimpse only)...
|
|
44
|
+
|
|
45
|
+
### Neighborhoods
|
|
46
|
+
|
|
47
|
+
Cell neighborhoods are defined via an arbitrary number of 2D offset vectors `[x,
|
|
48
|
+
y]`, where `x` coordinates are horizontal offsets and positive `y` coordinates
|
|
49
|
+
are used to refer to previous generations (e.g. 0 = current gen, 1 = T-1, 2 =
|
|
50
|
+
T-2 etc.) and thereby providing a form of short term memory for that specific
|
|
51
|
+
automata. Negative `y` coords will lead to cells being ignored.
|
|
52
|
+
|
|
53
|
+
### Rule encoding
|
|
54
|
+
|
|
55
|
+
Automata rules are encoded as JS `BigInt` values and are considered anisotropic
|
|
56
|
+
by default. If isotropy is desired, it has to be explicitly pre-encoded (out of
|
|
57
|
+
scope of this library). There's also built-in optional support for position
|
|
58
|
+
independent neighborhood encoding, only considering the number/count of non-zero
|
|
59
|
+
cells. An encoded rule ID and its overall magnitude is directly related and
|
|
60
|
+
dependent on the size and shape of its kernel config, e.g.:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
kernel = [[-2, 1], [-1, 0], [0, 0], [1, 0], [2, 1]]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This example kernel defines a 5-cell neighborhood with a max. short term memory
|
|
67
|
+
of one additional previous generation (i.e. the [-2,1] and [2,1] offsets)
|
|
68
|
+
|
|
69
|
+
The rules related to this kernel have a 32 bit address space (4 billion
|
|
70
|
+
possibilities), due to 2^5 = 32 and each kernel offset being assigned a distinct
|
|
71
|
+
bit value by default, i.e. first kernel offset = 2^0, second kernel offset =
|
|
72
|
+
2^1, third = 2^2, fourth = 2^3, fifth = 2^4. Via the `positional` config option,
|
|
73
|
+
this behavior can be overridden per kernel, to achieve position-independent
|
|
74
|
+
kernels (with much smaller rule spaces).
|
|
75
|
+
|
|
76
|
+
Given the following example cell matrix with the center cell highlighted with
|
|
77
|
+
caret (`^`):
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
T-1: 2 0 1 2 1
|
|
81
|
+
T-0: 0 1 0 3 0
|
|
82
|
+
^
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The above example kernel will select the following values and assign bit
|
|
86
|
+
positions (for all non-zero cell states) to compute a summed ID:
|
|
87
|
+
|
|
88
|
+
| k index | offset | cell value | encoded |
|
|
89
|
+
|--------:|-----------|-----------:|---------:|
|
|
90
|
+
| 0 | `[-2, 1]` | 2 | 2^0 = 1 |
|
|
91
|
+
| 1 | `[-1, 0]` | 1 | 2^1 = 2 |
|
|
92
|
+
| 2 | `[0, 0]` | 0 | 0 |
|
|
93
|
+
| 3 | `[1, 0]` | 3 | 2^3 = 8 |
|
|
94
|
+
| 4 | `[2, 1]` | 1 | 2^4 = 16 |
|
|
95
|
+
|
|
96
|
+
Final encoded neighborhood sum: 1 + 2 + 8 + 16 = 27
|
|
97
|
+
|
|
98
|
+
To determine if a the current cell should be active or not in the next
|
|
99
|
+
generation, we now use that encoded sum as bit position to test a single bit of
|
|
100
|
+
the automata's rule ID, i.e. here we're testing bit 27. If that corresponding
|
|
101
|
+
bit is set in the rule ID, the cell's state will be increased by 1.
|
|
102
|
+
|
|
103
|
+
### Cell states
|
|
104
|
+
|
|
105
|
+
Each automata config can define a max. number of possible cell states (aka age).
|
|
106
|
+
Once a cell reaches the configured `numStates`, it automatically resets to zero.
|
|
107
|
+
This is by default, but can be overridden via the `reset` option. Conversely, if
|
|
108
|
+
the corresponding bit is _not_ set in the rule ID, the cell state will be zeroed
|
|
109
|
+
too.
|
|
110
|
+
|
|
111
|
+
### Wraparound
|
|
112
|
+
|
|
113
|
+
By default the environment is configured to be toroidal, i.e. both left/right
|
|
114
|
+
sides of the env are connected. The behavior can be controlled via a ctor arg
|
|
115
|
+
and/or at runtime via the `wrap` property.
|
|
116
|
+
|
|
117
|
+
### Masks
|
|
118
|
+
|
|
119
|
+
The `mask` array can be used to select different CA configurations for each cell
|
|
120
|
+
in the environment. Because this mask array is initialized to zero, only the
|
|
121
|
+
first CA configuration will be used for all cells in the environment by default.
|
|
122
|
+
It's the user's responsibility to manage the mask and select/enable other (if
|
|
123
|
+
any) CA configs for individual cells (usually cell ranges). The values stored in
|
|
124
|
+
this array correspond to the indices of the CA configurations given at
|
|
125
|
+
construction.
|
|
126
|
+
|
|
127
|
+
### Limits
|
|
128
|
+
|
|
129
|
+
Due to using `Uint8Arrays` for storage, only up to 256 cell states are
|
|
130
|
+
supported. The same limit applies to the number of CA configs given.
|
|
131
|
+
|
|
132
|
+
### Status
|
|
133
|
+
|
|
134
|
+
**STABLE** - used in production
|
|
135
|
+
|
|
136
|
+
[Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Bcellular%5D+in%3Atitle)
|
|
137
|
+
|
|
138
|
+
### Related packages
|
|
139
|
+
|
|
140
|
+
- [@thi.ng/lsys](https://github.com/thi-ng/umbrella/tree/develop/packages/lsys) - Functional, extensible L-System architecture w/ support for probabilistic rules
|
|
141
|
+
- [@thi.ng/pixel](https://github.com/thi-ng/umbrella/tree/develop/packages/pixel) - Typedarray integer & float pixel buffers w/ customizable formats, blitting, drawing, convolution
|
|
142
|
+
|
|
143
|
+
## Installation
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
yarn add @thi.ng/cellular
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
ES module import:
|
|
150
|
+
|
|
151
|
+
```html
|
|
152
|
+
<script type="module" src="https://cdn.skypack.dev/@thi.ng/cellular"></script>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
[Skypack documentation](https://docs.skypack.dev/)
|
|
156
|
+
|
|
157
|
+
For Node.js REPL:
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
# with flag only for < v16
|
|
161
|
+
node --experimental-repl-await
|
|
162
|
+
|
|
163
|
+
> const cellular = await import("@thi.ng/cellular");
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Package sizes (gzipped, pre-treeshake): ESM: 1.24 KB
|
|
167
|
+
|
|
168
|
+
## Dependencies
|
|
169
|
+
|
|
170
|
+
- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
|
|
171
|
+
- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
|
|
172
|
+
- [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
|
|
173
|
+
- [@thi.ng/random](https://github.com/thi-ng/umbrella/tree/develop/packages/random)
|
|
174
|
+
- [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/develop/packages/transducers)
|
|
175
|
+
|
|
176
|
+
## API
|
|
177
|
+
|
|
178
|
+
[Generated API docs](https://docs.thi.ng/umbrella/cellular/)
|
|
179
|
+
|
|
180
|
+
## Code examples
|
|
181
|
+
|
|
182
|
+
### Classic Wolfram
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { MultiCA1D } from "@thi.ng/cellular";
|
|
186
|
+
import { defIndexed, intBuffer } from "@thi.ng/pixel";
|
|
187
|
+
import { asPPM } from "@thi.ng/pixel-io-netpbm";
|
|
188
|
+
import { writeFileSync } from "fs";
|
|
189
|
+
|
|
190
|
+
const WIDTH = 512;
|
|
191
|
+
const HEIGHT = 512;
|
|
192
|
+
|
|
193
|
+
// define standard 1D Wolfram CA (3-neighborhood, 2 states)
|
|
194
|
+
const ca = new MultiCA1D(
|
|
195
|
+
[
|
|
196
|
+
{
|
|
197
|
+
rule: 73,
|
|
198
|
+
// kernel can be imported as `WOLFRAM3`
|
|
199
|
+
kernel: [[-1, 0],[0, 0],[1, 0]],
|
|
200
|
+
states: 2,
|
|
201
|
+
reset: false,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
WIDTH
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// seed a single cell in center
|
|
208
|
+
ca.current[WIDTH/2] = 1;
|
|
209
|
+
|
|
210
|
+
// create image with indexed color model (2 cell states => 2 colors)
|
|
211
|
+
const img = intBuffer(WIDTH, HEIGHT, defIndexed([0xff000000, 0xffffffff]));
|
|
212
|
+
|
|
213
|
+
// compute the CA for entire image
|
|
214
|
+
ca.updateImage(img.data, HEIGHT);
|
|
215
|
+
|
|
216
|
+
// write as PPM file
|
|
217
|
+
writeFileSync("export/out.ppm", asPPM(img));
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Result:
|
|
221
|
+
|
|
222
|
+

|
|
223
|
+
|
|
224
|
+
### Custom kernels & multiple rules
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { multiColorGradient, srgb } from "@thi.ng/color";
|
|
228
|
+
|
|
229
|
+
// create CA with 2 rules/kernels (5-neighborhoods) and 64 states (max age)
|
|
230
|
+
const ca = new MultiCA1D(
|
|
231
|
+
[
|
|
232
|
+
{
|
|
233
|
+
rule: 0x73ed2ac2,
|
|
234
|
+
// kernel can be imported as `WOLFRAM5`
|
|
235
|
+
kernel: [[-2, 0], [-1, 0], [0, 0], [1, 0], [2, 0]],
|
|
236
|
+
states: 64,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
rule: 0xef14e4ca,
|
|
240
|
+
// kernel can be imported as `WOLFRAM5`
|
|
241
|
+
kernel: [[-2, 0], [-1, 0], [0, 0], [1, 0], [2, 0]],
|
|
242
|
+
states: 64,
|
|
243
|
+
}
|
|
244
|
+
],
|
|
245
|
+
WIDTH
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// seed cells with 10% noise
|
|
249
|
+
ca.setNoise("cells", 0.1);
|
|
250
|
+
|
|
251
|
+
// set mask to stripe pattern to select both CAs
|
|
252
|
+
ca.setPattern("mask", WIDTH / 4, WIDTH / 2, 1, 0, WIDTH / 8);
|
|
253
|
+
|
|
254
|
+
// alternatively apply noise to the mask to create
|
|
255
|
+
// more uniformly hybrid/mixed results
|
|
256
|
+
// ca.setNoise("mask", 0.5);
|
|
257
|
+
|
|
258
|
+
// create color gradient to visualize the different cell states
|
|
259
|
+
// and wrap as indexed color model for pixel buffer below...
|
|
260
|
+
// references:
|
|
261
|
+
// https://docs.thi.ng/umbrella/color/modules.html#multiColorGradient
|
|
262
|
+
// https://docs.thi.ng/umbrella/pixel/modules.html#defIndexed
|
|
263
|
+
const fmt = defIndexed(
|
|
264
|
+
multiColorGradient(
|
|
265
|
+
{
|
|
266
|
+
num: ca.numStates,
|
|
267
|
+
stops: [
|
|
268
|
+
[0, srgb(1, 0, 0.5)],
|
|
269
|
+
[0.02, srgb(0.8, 1, 1)],
|
|
270
|
+
[1, srgb(1, 0.5, 0)],
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
false
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// create image / pixel buffer using above indexed color model
|
|
278
|
+
const img = intBuffer(WIDTH, HEIGHT, fmt);
|
|
279
|
+
|
|
280
|
+
// compute CA for full image
|
|
281
|
+
ca.updateImage(img.data, HEIGHT);
|
|
282
|
+
|
|
283
|
+
// export as PPM image
|
|
284
|
+
writeFileSync("export/out.ppm", asPPM(img));
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
| 1st CA only | 2nd CA only |
|
|
288
|
+
|---------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
|
|
289
|
+
|  |  |
|
|
290
|
+
| Hybrid (stripe pattern) | Hybrid (noise) |
|
|
291
|
+
|  |  |
|
|
292
|
+
|
|
293
|
+
## Authors
|
|
294
|
+
|
|
295
|
+
Karsten Schmidt
|
|
296
|
+
|
|
297
|
+
If this project contributes to an academic publication, please cite it as:
|
|
298
|
+
|
|
299
|
+
```bibtex
|
|
300
|
+
@misc{thing-cellular,
|
|
301
|
+
title = "@thi.ng/cellular",
|
|
302
|
+
author = "Karsten Schmidt",
|
|
303
|
+
note = "https://thi.ng/cellular",
|
|
304
|
+
year = 2022
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
© 2022 Karsten Schmidt // Apache Software License 2.0
|
package/api.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { FnN, NumericArray } from "@thi.ng/api";
|
|
2
|
+
export declare type Target = "cells" | "mask";
|
|
3
|
+
export declare type Kernel = NumericArray[];
|
|
4
|
+
export interface CAConfig1D {
|
|
5
|
+
/**
|
|
6
|
+
* Same as {@link CASpec1D.kernel}.
|
|
7
|
+
*/
|
|
8
|
+
kernel: Kernel;
|
|
9
|
+
/**
|
|
10
|
+
* Same as {@link CASpec1D.weights}.
|
|
11
|
+
*/
|
|
12
|
+
weights: bigint[];
|
|
13
|
+
/**
|
|
14
|
+
* Same as {@link CASpec1D.rule}, but always a bigint.
|
|
15
|
+
*/
|
|
16
|
+
rule: bigint;
|
|
17
|
+
/**
|
|
18
|
+
* Same as {@link CASpec1D.states}.
|
|
19
|
+
*/
|
|
20
|
+
states: number;
|
|
21
|
+
/**
|
|
22
|
+
* Cell state update function/behavior. Takes a current cell state, returns
|
|
23
|
+
* new one.
|
|
24
|
+
*/
|
|
25
|
+
fn: FnN;
|
|
26
|
+
}
|
|
27
|
+
export interface CASpec1D {
|
|
28
|
+
/**
|
|
29
|
+
* Array of 2D offset vectors `[x, y]` defining the automata's neighborhood.
|
|
30
|
+
* `x` coordinates are horizontal offsets and positive `y` coordinates are
|
|
31
|
+
* used to refer to previous generations (e.g. 0 = current gen, 1 = T-1, 2 =
|
|
32
|
+
* T-2 etc.) and thereby providing a form of short term memory for that
|
|
33
|
+
* specific automata. Negative `y` coords will lead to cells being ignored.
|
|
34
|
+
*
|
|
35
|
+
* Unless {@link CASpec1D.positional} is false (default: true), the order of
|
|
36
|
+
* offsets _is_ important: Whenever the offset relates to a non-zero cell in the
|
|
37
|
+
* neighborhood, it will contribute a specific bit value to encode the
|
|
38
|
+
* overall state of the neighborhood, i.e. 2^k, where `k` is the array index
|
|
39
|
+
* of the corresponding kernel offset.
|
|
40
|
+
*/
|
|
41
|
+
kernel: Kernel;
|
|
42
|
+
/**
|
|
43
|
+
* If false (default: true), the order of kernel offsets is irrelevant and
|
|
44
|
+
* only the count of non-zero cells in the neighborhood is used to check a
|
|
45
|
+
* relevant bit in the `rule` ID. E.g. if count = 3, then the 3rd LSB will
|
|
46
|
+
* be checked.
|
|
47
|
+
*/
|
|
48
|
+
positional?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* CA replication rules encoded as bigint. The overall magnitude of these
|
|
51
|
+
* rule IDs depends on the size of the neighborhood kernel and will be 2^n
|
|
52
|
+
* bits, where `n` is the kernel size. E.g. A 5-neighborhood will offer 2^32
|
|
53
|
+
* = 4 billion possibilities. A 7-neighborhood corresponds to a 2^7 = 128
|
|
54
|
+
* bit large rule space (~10^38 possibilities!).
|
|
55
|
+
*/
|
|
56
|
+
rule: bigint | number | string;
|
|
57
|
+
/**
|
|
58
|
+
* Max number of cell states (aka cell age). Note: MUST be <= 256. For
|
|
59
|
+
* "standard" Wolfram automata, states = 2.
|
|
60
|
+
*/
|
|
61
|
+
states: number;
|
|
62
|
+
/**
|
|
63
|
+
* If true (default), cells will reset to zero once their max. age has been
|
|
64
|
+
* reached. Should be set to `false` for "standard" 2-state Wolfram
|
|
65
|
+
* automata.
|
|
66
|
+
*/
|
|
67
|
+
reset?: boolean;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=api.d.ts.map
|
package/api.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/index.d.ts
ADDED
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thi.ng/cellular",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Highly customizable 1D cellular automata, shared env, multiple rules, arbitrary sized/shaped neighborhoods, short term memory, cell states etc.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "./index.js",
|
|
7
|
+
"typings": "./index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/thi-ng/umbrella.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/cellular#readme",
|
|
14
|
+
"funding": [
|
|
15
|
+
{
|
|
16
|
+
"type": "github",
|
|
17
|
+
"url": "https://github.com/sponsors/postspectacular"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "patreon",
|
|
21
|
+
"url": "https://patreon.com/thing_umbrella"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"author": "Karsten Schmidt <k+npm@thi.ng>",
|
|
25
|
+
"license": "Apache-2.0",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "yarn clean && tsc --declaration",
|
|
28
|
+
"clean": "rimraf '*.js' '*.d.ts' '*.map' doc",
|
|
29
|
+
"doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
|
|
30
|
+
"doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
|
|
31
|
+
"doc:readme": "yarn doc:stats && tools:readme",
|
|
32
|
+
"doc:stats": "tools:module-stats",
|
|
33
|
+
"pub": "yarn npm publish --access public",
|
|
34
|
+
"test": "testament test"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@thi.ng/api": "^8.3.7",
|
|
38
|
+
"@thi.ng/checks": "^3.2.0",
|
|
39
|
+
"@thi.ng/errors": "^2.1.7",
|
|
40
|
+
"@thi.ng/random": "^3.3.1",
|
|
41
|
+
"@thi.ng/transducers": "^8.3.4"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@microsoft/api-extractor": "^7.25.0",
|
|
45
|
+
"@thi.ng/testament": "^0.2.8",
|
|
46
|
+
"rimraf": "^3.0.2",
|
|
47
|
+
"tools": "^0.0.1",
|
|
48
|
+
"typedoc": "^0.22.17",
|
|
49
|
+
"typescript": "^4.7.3"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"1d",
|
|
53
|
+
"automata",
|
|
54
|
+
"bigint",
|
|
55
|
+
"cellular",
|
|
56
|
+
"generative",
|
|
57
|
+
"neighborhood",
|
|
58
|
+
"pixel",
|
|
59
|
+
"rulebased",
|
|
60
|
+
"simulation",
|
|
61
|
+
"typescript"
|
|
62
|
+
],
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"browser": {
|
|
67
|
+
"process": false,
|
|
68
|
+
"setTimeout": false
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=14"
|
|
72
|
+
},
|
|
73
|
+
"files": [
|
|
74
|
+
"*.js",
|
|
75
|
+
"*.d.ts"
|
|
76
|
+
],
|
|
77
|
+
"exports": {
|
|
78
|
+
".": {
|
|
79
|
+
"default": "./index.js"
|
|
80
|
+
},
|
|
81
|
+
"./1d": {
|
|
82
|
+
"default": "./1d.js"
|
|
83
|
+
},
|
|
84
|
+
"./api": {
|
|
85
|
+
"default": "./api.js"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"thi.ng": {
|
|
89
|
+
"related": [
|
|
90
|
+
"lsys",
|
|
91
|
+
"pixel"
|
|
92
|
+
],
|
|
93
|
+
"year": 2022
|
|
94
|
+
},
|
|
95
|
+
"gitHead": "9e516d30a1a537e027a6b3d78bf9121bc5831d31\n"
|
|
96
|
+
}
|