@qe-libs/rena-wasm 0.1.1 → 0.1.2

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 CHANGED
@@ -32,12 +32,14 @@ const model = ena.fit(rows, {
32
32
  dims: 2,
33
33
  });
34
34
 
35
- model.centroids // Float64Array nUnits × dims
36
- model.networks // Float64Array nUnits × nConnections (normed)
37
- model.positions // Float64Array nCodes × dims
35
+ model.model.centroids // Float64Array nUnits × dims
36
+ model.lineWeights // Float64Array nUnits × nConnections (normed)
37
+ model.connectionCounts // Float64Array nUnits × nConnections (raw unit counts)
38
+ model.rowConnectionCounts // Float64Array nRows × nConnections (raw row counts)
39
+ model.rotation.nodes // Float64Array nCodes × dims
38
40
  model.connectionNames // ['Data & Technical.Constraints', ...]
39
- model.unitLabels // ['UserName1_ConditionA', ...]
40
- model.columnNames // ['SVD1', 'SVD2']
41
+ model.model.unitLabels // ['UserName1_ConditionA', ...]
42
+ model.rotation.columnNames // ['SVD1', 'SVD2']
41
43
 
42
44
  // Per-unit helpers
43
45
  model.centroid('Alice_A') // number[] length = dims
@@ -70,7 +72,7 @@ const model = ena.fit(rows, {
70
72
  Returns raw (un-normalised) network vectors without running the full pipeline.
71
73
 
72
74
  ```js
73
- const { networks, unitLabels, connectionNames, nUnits, nConnections } =
75
+ const { connectionCounts, rowConnectionCounts, unitLabels, connectionNames, nUnits, nConnections } =
74
76
  ena.accumulate(rows, { codes, units, conversations, window: 4 });
75
77
  ```
76
78
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qe-libs/rena-wasm",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "JavaScript/WebAssembly ENA pipeline — thin orchestration layer over @qe-libs/libqe-wasm",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -12,6 +12,7 @@
12
12
  *
13
13
  * Top-level fields (= R's set$...):
14
14
  * connectionCounts Float64Array (nUnits × nConnections) — raw accumulation
15
+ * rowConnectionCounts Float64Array (nRows × nConnections) — per-row raw accumulation
15
16
  * lineWeights Float64Array (nUnits × nConnections) — sphere-normed
16
17
  * points Float64Array (nUnits × dims) — projected positions
17
18
  * rotationMatrix Float64Array (nConnections × dims) — rotation vectors
@@ -23,6 +24,7 @@
23
24
  * dims number
24
25
  *
25
26
  * model sub-object (= R's set$model$...):
27
+ * model.rowConnectionCounts Float64Array (nRows × nConnections) — per-row raw accumulation
26
28
  * model.centroids Float64Array (nUnits × dims) — LWS centroids
27
29
  * model.variance number[] variance explained per dim
28
30
  * model.unitLabels string[]
@@ -41,7 +43,7 @@
41
43
  import loadLibQE from '@qe-libs/libqe-wasm';
42
44
  import { parseData } from './data.js';
43
45
  import {
44
- accumulate, sphereNorm, center,
46
+ accumulate, accumulateWithRows, sphereNorm, center,
45
47
  rotateSVD, rotateMeans, rotateGeneralized,
46
48
  project, nodePositions, spaceDistCorr,
47
49
  } from './pipeline.js';
@@ -107,6 +109,7 @@ class ENAModel {
107
109
  constructor(opts) {
108
110
  // ── top-level fields (= R's set$...) ────────────────────────────────
109
111
  this.connectionCounts = opts.connectionCounts; // raw networks
112
+ this.rowConnectionCounts = opts.rowConnectionCounts;
110
113
  this.lineWeights = opts.lineWeights; // sphere-normed networks
111
114
  this.points = opts.points; // projected unit positions
112
115
  this.rotationMatrix = opts.rotationMatrix; // n_connections × dims
@@ -118,6 +121,7 @@ class ENAModel {
118
121
 
119
122
  // ── model sub-object (= R's set$model$...) ───────────────────────────
120
123
  this.model = {
124
+ rowConnectionCounts: opts.rowConnectionCounts,
121
125
  centroids: opts.centroids, // LWS positions
122
126
  variance: opts.variance, // variance explained
123
127
  unitLabels: opts.unitLabels,
@@ -186,7 +190,8 @@ class ENAModel {
186
190
  // ── shared pipeline (post-accumulation) ──────────────────────────────────────
187
191
 
188
192
  function runPipeline(qe, rawNetworks, nUnits, nConnections, codes, unitLabels,
189
- metaData, rotMethod, groupA, groupB, dims, gParams) {
193
+ metaData, rotMethod, groupA, groupB, dims, gParams,
194
+ rowConnectionCounts = null) {
190
195
  const connectionNames = qe.connection_names(codes);
191
196
 
192
197
  // Sphere norm → lineWeights (= R's set$line.weights)
@@ -243,6 +248,7 @@ function runPipeline(qe, rawNetworks, nUnits, nConnections, codes, unitLabels,
243
248
  return new ENAModel({
244
249
  // top-level
245
250
  connectionCounts: rawNetworks,
251
+ rowConnectionCounts,
246
252
  lineWeights,
247
253
  points,
248
254
  rotationMatrix,
@@ -322,7 +328,7 @@ export default async function loadENA() {
322
328
  unitOf, convoGroups, metaData } =
323
329
  parseData(rows, codes, units, conversations);
324
330
 
325
- let rawNetworks, nConnections;
331
+ let rawNetworks, rowConnectionCounts = null, nConnections;
326
332
 
327
333
  if (tensorDef) {
328
334
  rawNetworks = accumulateTensor(
@@ -331,10 +337,12 @@ export default async function loadENA() {
331
337
  );
332
338
  nConnections = ordered ? nCodes * nCodes : qe.choose_two(nCodes);
333
339
  } else {
334
- rawNetworks = accumulate(
340
+ const accumulated = accumulateWithRows(
335
341
  qe, codeMatrix, nRows, nCodes, nUnits,
336
342
  unitOf, convoGroups, windowSize, binary
337
343
  );
344
+ rawNetworks = accumulated.networks;
345
+ rowConnectionCounts = accumulated.rowConnectionCounts;
338
346
  nConnections = qe.choose_two(nCodes);
339
347
  }
340
348
 
@@ -368,7 +376,8 @@ export default async function loadENA() {
368
376
  }
369
377
 
370
378
  return runPipeline(qe, rawNetworks, nUnits, nConnections, codes,
371
- unitLabels, metaData, rotMethod, groupA, groupB, dims, gParams);
379
+ unitLabels, metaData, rotMethod, groupA, groupB,
380
+ dims, gParams, rowConnectionCounts);
372
381
  },
373
382
 
374
383
  /**
@@ -379,6 +388,7 @@ export default async function loadENA() {
379
388
  * @param {object} opts - codes, units, conversations, window, binary, ordered, tensor
380
389
  * @returns {{
381
390
  * connectionCounts: Float64Array,
391
+ * rowConnectionCounts: Float64Array | null,
382
392
  * unitLabels: string[],
383
393
  * connectionNames: string[],
384
394
  * metaData: Object[],
@@ -401,7 +411,7 @@ export default async function loadENA() {
401
411
  unitOf, convoGroups, metaData } =
402
412
  parseData(rows, codes, units, conversations);
403
413
 
404
- let networks, nConnections;
414
+ let networks, rowConnectionCounts = null, nConnections;
405
415
 
406
416
  if (tensorDef) {
407
417
  networks = accumulateTensor(
@@ -410,10 +420,12 @@ export default async function loadENA() {
410
420
  );
411
421
  nConnections = ordered ? nCodes * nCodes : qe.choose_two(nCodes);
412
422
  } else {
413
- networks = accumulate(
423
+ const accumulated = accumulateWithRows(
414
424
  qe, codeMatrix, nRows, nCodes, nUnits,
415
425
  unitOf, convoGroups, windowSize, binary
416
426
  );
427
+ networks = accumulated.networks;
428
+ rowConnectionCounts = accumulated.rowConnectionCounts;
417
429
  nConnections = qe.choose_two(nCodes);
418
430
  }
419
431
 
@@ -448,7 +460,7 @@ export default async function loadENA() {
448
460
 
449
461
  const connectionNames = qe.connection_names(codes);
450
462
  return {
451
- connectionCounts: networks, unitLabels, connectionNames,
463
+ connectionCounts: networks, rowConnectionCounts, unitLabels, connectionNames,
452
464
  metaData, nUnits, nConnections,
453
465
  // Retained so tuneWindowSize() can rebuild at other window sizes
454
466
  // (= R's ENAAccumulation$`_function.call`).
package/src/pipeline.js CHANGED
@@ -174,6 +174,51 @@ export function accumulate(qe, codeMatrix, nRows, nCodes, nUnits,
174
174
  return networks;
175
175
  }
176
176
 
177
+ /**
178
+ * Accumulate windowed co-occurrences and retain both per-unit and per-row
179
+ * connection vectors.
180
+ *
181
+ * @returns {{ networks: Float64Array, rowConnectionCounts: Float64Array }}
182
+ */
183
+ export function accumulateWithRows(qe, codeMatrix, nRows, nCodes, nUnits,
184
+ unitOf, convoGroups, windowSize = 4, binary = true) {
185
+ const nConnections = qe.choose_two(nCodes);
186
+ const networks = new Float64Array(nUnits * nConnections);
187
+ const rowConnectionCounts = new Float64Array(nRows * nConnections);
188
+
189
+ for (const [, rowIndices] of convoGroups) {
190
+ const nConvo = rowIndices.length;
191
+
192
+ const convoCodes = new Float64Array(nConvo * nCodes);
193
+ for (let r = 0; r < nConvo; r++) {
194
+ const src = rowIndices[r];
195
+ convoCodes.set(
196
+ codeMatrix.subarray(src * nCodes, src * nCodes + nCodes),
197
+ r * nCodes
198
+ );
199
+ }
200
+
201
+ const stanza = qe.accumulate_stanza(
202
+ convoCodes, nConvo, nCodes, windowSize, 0, binary
203
+ );
204
+
205
+ for (let r = 0; r < nConvo; r++) {
206
+ const src = rowIndices[r];
207
+ const unit = unitOf[src];
208
+ const offset = r * nConnections;
209
+ const rowOut = src * nConnections;
210
+
211
+ for (let c = 0; c < nConnections; c++) {
212
+ const value = stanza.data[offset + c];
213
+ rowConnectionCounts[rowOut + c] = value;
214
+ networks[unit * nConnections + c] += value;
215
+ }
216
+ }
217
+ }
218
+
219
+ return { networks, rowConnectionCounts };
220
+ }
221
+
177
222
  // ── normalization (sphere norm) ───────────────────────────────────────────────
178
223
 
179
224
  /**