@thi.ng/cellular 0.1.0 → 0.2.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.
Files changed (5) hide show
  1. package/1d.d.ts +46 -16
  2. package/1d.js +107 -39
  3. package/CHANGELOG.md +12 -1
  4. package/api.d.ts +51 -3
  5. package/package.json +5 -5
package/1d.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { IClear, UIntArray } from "@thi.ng/api";
1
+ import type { IClear, TypedArray, UIntArray } from "@thi.ng/api";
2
2
  import type { IRandom } from "@thi.ng/random";
3
- import type { CAConfig1D, CASpec1D, Kernel, Target } from "./api.js";
3
+ import type { CAConfig1D, CASpec1D, Kernel, Target, UpdateImageOpts1D } from "./api.js";
4
4
  /**
5
5
  * Standard Wolfram automata 3-neighborhood (no history)
6
6
  */
@@ -92,6 +92,13 @@ export declare const WOLFRAM7: Kernel;
92
92
  * {@link CASpec1D.reset} option. Conversely, if the corresponding bit is _not_
93
93
  * set in the rule ID, the cell state will be zeroed too.
94
94
  *
95
+ * ### Update probabilities
96
+ *
97
+ * Each cell has an optional update probability, which is initialized to 1.0 by
98
+ * default (i.e. to always be updated). Use
99
+ * {@link MultiCA1D.updateProbabilistic} or {@link MultiCA1D.updateImage} to
100
+ * take these probabilities into account.
101
+ *
95
102
  * ### Wraparound
96
103
  *
97
104
  * By default the environment is configured to be toroidal, i.e. both left/right
@@ -135,11 +142,12 @@ export declare class MultiCA1D implements IClear {
135
142
  numStates: number;
136
143
  mask: Uint8Array;
137
144
  gens: Uint8Array[];
145
+ prob: Float32Array;
138
146
  constructor(configs: CASpec1D[], width: number, wrap?: boolean);
139
147
  get current(): Uint8Array;
140
148
  get previous(): Uint8Array;
141
149
  clear(): void;
142
- clearCurrent(): void;
150
+ clearTarget(target: Target): void;
143
151
  resize(width: number): void;
144
152
  /**
145
153
  * Sets a parametric pattern in the current generation or mask array.
@@ -162,28 +170,50 @@ export declare class MultiCA1D implements IClear {
162
170
  */
163
171
  setNoise(target: Target, prob?: number, rnd?: IRandom): this;
164
172
  /**
165
- * Computes a single new generation using current cell states and mask. See
173
+ * Computes a single new generation using current cell states and mask only
174
+ * (no consideration for cell update probabilities, use
175
+ * {@link MultiCA1D.updateProbabilistic} for that instead). Als see
166
176
  * {@link MultiCA1D.updateImage} for batch updates.
167
177
  */
168
178
  update(): void;
179
+ /**
180
+ * Same as {@link MultiCA1D.update}, but also considering cell update
181
+ * probabilities stored in the {@link MultiCA1D.prob} array.
182
+ *
183
+ * @param rnd
184
+ */
185
+ updateProbabilistic(rnd?: IRandom): void;
186
+ /**
187
+ * Computes (but doesn't apply) the new state for a single cell.
188
+ *
189
+ * @param config - CA configuration
190
+ * @param x - cell index
191
+ * @param val - current cell value
192
+ */
193
+ computeCell({ rule, kernel, weights, fn }: CAConfig1D, x: number, val: number): number;
169
194
  /**
170
195
  * 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.
196
+ * given `height` (and assumed to be the same width as this CA instance has
197
+ * been configured to). Fills given `pixels` array with consecutive
198
+ * generations.
199
+ *
200
+ * @remarks
201
+ * Via the provided options object, per-generation & per-cell perturbance
202
+ * settings can be provided for cell states, mask and cell update
203
+ * probabilities. The latter are only considered if the
204
+ * {@link UpdateImageOpts1D.probabilistic} option is enabled. This can be
205
+ * helpful to sporadically introduce noise into the sim, break constant
206
+ * patterns and/or produce more varied/complex outputs.
207
+ *
208
+ * See {@link UpdateImageOpts1D} for further options.
177
209
  *
178
210
  * @param pixels
179
211
  * @param height
180
- * @param perturb
181
- * @param density
182
- * @param rnd
212
+ * @param opts
183
213
  */
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];
214
+ updateImage(pixels: UIntArray, height: number, opts?: Partial<UpdateImageOpts1D>): void;
215
+ rotate(target: Target | "all", dir: number): void;
216
+ protected _getTarget(target: Target): [TypedArray, number];
187
217
  }
188
218
  /**
189
219
  * Creates a random rule ID for given `kernelSize` and using optionally provided
package/1d.js CHANGED
@@ -106,6 +106,13 @@ export const WOLFRAM7 = [[-3, 0], ...WOLFRAM5, [3, 0]];
106
106
  * {@link CASpec1D.reset} option. Conversely, if the corresponding bit is _not_
107
107
  * set in the rule ID, the cell state will be zeroed too.
108
108
  *
109
+ * ### Update probabilities
110
+ *
111
+ * Each cell has an optional update probability, which is initialized to 1.0 by
112
+ * default (i.e. to always be updated). Use
113
+ * {@link MultiCA1D.updateProbabilistic} or {@link MultiCA1D.updateImage} to
114
+ * take these probabilities into account.
115
+ *
109
116
  * ### Wraparound
110
117
  *
111
118
  * By default the environment is configured to be toroidal, i.e. both left/right
@@ -160,14 +167,17 @@ export class MultiCA1D {
160
167
  }
161
168
  clear() {
162
169
  this.gens.forEach((g) => g.fill(0));
170
+ this.mask.fill(0);
171
+ this.prob.fill(1);
163
172
  }
164
- clearCurrent() {
165
- this.current.fill(0);
173
+ clearTarget(target) {
174
+ this._getTarget(target)[0].fill(target === "prob" ? 1 : 0);
166
175
  }
167
176
  resize(width) {
168
177
  this.width = width;
169
178
  this.mask = new Uint8Array(width);
170
179
  this.gens = [...repeatedly(() => new Uint8Array(width), this.rows + 1)];
180
+ this.prob = new Float32Array(width).fill(1);
171
181
  }
172
182
  /**
173
183
  * Sets a parametric pattern in the current generation or mask array.
@@ -198,71 +208,129 @@ export class MultiCA1D {
198
208
  */
199
209
  setNoise(target, prob = 0.5, rnd = SYSTEM) {
200
210
  const [dest, num] = this._getTarget(target);
211
+ const fn = target === "prob" ? () => rnd.float() : () => rnd.int() % num;
201
212
  for (let x = 0, width = this.width; x < width; x++) {
202
213
  if (rnd.float() < prob)
203
- dest[x] = rnd.int() % num;
214
+ dest[x] = fn();
204
215
  }
205
216
  return this;
206
217
  }
207
218
  /**
208
- * Computes a single new generation using current cell states and mask. See
219
+ * Computes a single new generation using current cell states and mask only
220
+ * (no consideration for cell update probabilities, use
221
+ * {@link MultiCA1D.updateProbabilistic} for that instead). Als see
209
222
  * {@link MultiCA1D.updateImage} for batch updates.
210
223
  */
211
224
  update() {
212
- const { width, gens, configs, mask, wrap } = this;
225
+ const { width, gens, configs, mask } = this;
213
226
  const [next, curr] = gens;
214
227
  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;
228
+ next[x] = this.computeCell(configs[mask[x]], x, curr[x]);
229
+ }
230
+ gens.unshift(gens.pop());
231
+ }
232
+ /**
233
+ * Same as {@link MultiCA1D.update}, but also considering cell update
234
+ * probabilities stored in the {@link MultiCA1D.prob} array.
235
+ *
236
+ * @param rnd
237
+ */
238
+ updateProbabilistic(rnd = SYSTEM) {
239
+ const { width, prob, gens, configs, mask } = this;
240
+ const [next, curr] = gens;
241
+ for (let x = 0; x < width; x++) {
242
+ next[x] =
243
+ rnd.float() < prob[x]
244
+ ? this.computeCell(configs[mask[x]], x, curr[x])
245
+ : curr[x];
233
246
  }
234
247
  gens.unshift(gens.pop());
235
248
  }
249
+ /**
250
+ * Computes (but doesn't apply) the new state for a single cell.
251
+ *
252
+ * @param config - CA configuration
253
+ * @param x - cell index
254
+ * @param val - current cell value
255
+ */
256
+ computeCell({ rule, kernel, weights, fn }, x, val) {
257
+ const { width, gens, wrap } = this;
258
+ let sum = $0;
259
+ for (let i = 0, n = kernel.length; i < n; i++) {
260
+ const k = kernel[i];
261
+ let xx = x + k[0];
262
+ if (wrap) {
263
+ if (xx < 0)
264
+ xx += width;
265
+ else if (xx >= width)
266
+ xx -= width;
267
+ }
268
+ else if (xx < 0 || xx >= width)
269
+ continue;
270
+ const y = k[1];
271
+ if (y >= 0 && gens[1 + y][xx] !== 0)
272
+ sum += weights[i];
273
+ }
274
+ return rule & ($1 << sum) ? fn(val) : 0;
275
+ }
236
276
  /**
237
277
  * 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.
278
+ * given `height` (and assumed to be the same width as this CA instance has
279
+ * been configured to). Fills given `pixels` array with consecutive
280
+ * generations.
281
+ *
282
+ * @remarks
283
+ * Via the provided options object, per-generation & per-cell perturbance
284
+ * settings can be provided for cell states, mask and cell update
285
+ * probabilities. The latter are only considered if the
286
+ * {@link UpdateImageOpts1D.probabilistic} option is enabled. This can be
287
+ * helpful to sporadically introduce noise into the sim, break constant
288
+ * patterns and/or produce more varied/complex outputs.
289
+ *
290
+ * See {@link UpdateImageOpts1D} for further options.
244
291
  *
245
292
  * @param pixels
246
293
  * @param height
247
- * @param perturb
248
- * @param density
249
- * @param rnd
294
+ * @param opts
250
295
  */
251
- updateImage(pixels, height, perturb = 0, density = 0.05, rnd = SYSTEM) {
296
+ updateImage(pixels, height, opts = {}) {
297
+ assert(pixels.length >= this.width * height, "target pixel buffer too small");
298
+ const { cells, mask, prob, probabilistic, rnd, onupdate } = {
299
+ probabilistic: false,
300
+ rnd: SYSTEM,
301
+ ...opts,
302
+ };
303
+ const $ = (id, conf) => {
304
+ conf &&
305
+ conf.perturb &&
306
+ rnd.float() < conf.perturb &&
307
+ this.setNoise(id, conf.density || 0.05, rnd);
308
+ };
252
309
  for (let y = 0; y < height; y++) {
253
- rnd.float() < perturb && this.setNoise("cells", density, rnd);
254
- this.update();
310
+ $("cells", cells);
311
+ $("mask", mask);
312
+ $("prob", prob);
313
+ probabilistic ? this.updateProbabilistic(rnd) : this.update();
314
+ onupdate && onupdate(this, y);
255
315
  pixels.set(this.current, y * this.width);
256
316
  }
257
317
  }
258
- rotate(dir) {
259
- __rotate(this.current, dir);
260
- __rotate(this.mask, dir);
318
+ rotate(target, dir) {
319
+ if (target === "all") {
320
+ __rotate(this.current, dir);
321
+ __rotate(this.mask, dir);
322
+ __rotate(this.prob, dir);
323
+ }
324
+ else {
325
+ __rotate(this._getTarget(target)[0], dir);
326
+ }
261
327
  }
262
328
  _getTarget(target) {
263
329
  return target === "cells"
264
330
  ? [this.current, this.numStates]
265
- : [this.mask, this.configs.length];
331
+ : target === "mask"
332
+ ? [this.mask, this.configs.length]
333
+ : [this.prob, 1];
266
334
  }
267
335
  }
268
336
  const __compileSpec = ({ rule, kernel, positional, states, reset, }) => {
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2022-06-09T16:14:01Z
3
+ - **Last updated**: 2022-06-11T14:24:35Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,17 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/cellular@0.2.0) (2022-06-11)
13
+
14
+ #### 🚀 Features
15
+
16
+ - add probabilities, update options ([97e6b4d](https://github.com/thi-ng/umbrella/commit/97e6b4d))
17
+ - add cell update probabilities
18
+ - add `updateProbabilistic()`
19
+ - extract `computeCell()`
20
+ - add `UpdateImageOpts1D`, update `updateImage()`
21
+ - replace `clearCurrent()` => `clearTarget()`
22
+
12
23
  ## [0.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/cellular@0.1.0) (2022-06-09)
13
24
 
14
25
  #### 🚀 Features
package/api.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import type { FnN, NumericArray } from "@thi.ng/api";
2
- export declare type Target = "cells" | "mask";
1
+ import type { Fn2, FnN, NumericArray } from "@thi.ng/api";
2
+ import type { IRandom } from "@thi.ng/random";
3
+ import type { MultiCA1D } from "./1d";
4
+ export declare type Target = "cells" | "mask" | "prob";
3
5
  export declare type Kernel = NumericArray[];
4
6
  export interface CAConfig1D {
5
7
  /**
@@ -7,7 +9,10 @@ export interface CAConfig1D {
7
9
  */
8
10
  kernel: Kernel;
9
11
  /**
10
- * Same as {@link CASpec1D.weights}.
12
+ * Weight factors for each kernel offset. If {@link CASpec1D.positional} is
13
+ * true, these weights will all be `1 << i` where `i` is the index of each
14
+ * kernel offset vector. If `positional` is false, all weights will be set
15
+ * to 1.
11
16
  */
12
17
  weights: bigint[];
13
18
  /**
@@ -66,4 +71,47 @@ export interface CASpec1D {
66
71
  */
67
72
  reset?: boolean;
68
73
  }
74
+ export interface UpdateBufferOpts {
75
+ /**
76
+ * Per-generation perturbance probability. Default: 0%
77
+ */
78
+ perturb: number;
79
+ /**
80
+ * Per-cell perturbance probability. Default: 5% (only used if `perturb >
81
+ * 0`)
82
+ */
83
+ density: number;
84
+ }
85
+ export interface UpdateImageOpts1D {
86
+ /**
87
+ * Per-generation perturbance options for cell states array
88
+ */
89
+ cells: Partial<UpdateBufferOpts>;
90
+ /**
91
+ * Per-generation perturbance options for cell mask array
92
+ */
93
+ mask: Partial<UpdateBufferOpts>;
94
+ /**
95
+ * Per-generation perturbance options for cell update probability array.
96
+ * Only used if {@link UpdateImageOpts1D.probabilistic} is true.
97
+ */
98
+ prob: Partial<UpdateBufferOpts>;
99
+ /**
100
+ * If true, each new generation will be updated via
101
+ * {@link MultiCA1D.updateProbabilistic} instead of
102
+ * {@link MultiCA1D.update}.
103
+ */
104
+ probabilistic: boolean;
105
+ /**
106
+ * PRNG instance to use for perturbance. Default:
107
+ * {@link @thi.ng/random#SYSTEM} aka `Math.random`.
108
+ */
109
+ rnd: IRandom;
110
+ /**
111
+ * User handler function called immediatedly after each update (computation
112
+ * of a new generation). The arguments passed are the {@link MultiCA1D}
113
+ * instance and pixel row index.
114
+ */
115
+ onupdate: Fn2<MultiCA1D, number, void>;
116
+ }
69
117
  //# sourceMappingURL=api.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/cellular",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Highly customizable 1D cellular automata, shared env, multiple rules, arbitrary sized/shaped neighborhoods, short term memory, cell states etc.",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -35,10 +35,10 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@thi.ng/api": "^8.3.7",
38
- "@thi.ng/checks": "^3.2.0",
38
+ "@thi.ng/checks": "^3.2.1",
39
39
  "@thi.ng/errors": "^2.1.7",
40
- "@thi.ng/random": "^3.3.1",
41
- "@thi.ng/transducers": "^8.3.4"
40
+ "@thi.ng/random": "^3.3.2",
41
+ "@thi.ng/transducers": "^8.3.5"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@microsoft/api-extractor": "^7.25.0",
@@ -92,5 +92,5 @@
92
92
  ],
93
93
  "year": 2022
94
94
  },
95
- "gitHead": "9e516d30a1a537e027a6b3d78bf9121bc5831d31\n"
95
+ "gitHead": "ab0188234419f2d9f471de80871df930e5555bd6\n"
96
96
  }