@qe-libs/rena-wasm 0.1.14 → 0.1.16

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/src/pipeline.js CHANGED
@@ -319,17 +319,29 @@ export function project(centered, nUnits, nConnections, rotation, rotRows, rotCo
319
319
  /**
320
320
  * Compute code node positions via least-squares (LWS).
321
321
  *
322
- * @param {boolean} [directed=false] Use libqe's directed_node_positions (for
322
+ * @param {boolean} [directed=false] Use libena's directed_node_positions (for
323
323
  * ordered n² networks), as rENA's optimize() does for ordered sets.
324
+ * @param {number} [selfWeight=2] Weight of a self-connection in its code's
325
+ * node weight (2 = row + column sum, the default; 1 = once).
326
+ * @param {{from, to}|null} [key=null] Unordered: libena's column key
327
+ * (undirected_connection_key); the nodes come from node_positions_by_key --
328
+ * the same as node_positions for choose(n, 2) columns, and it also places
329
+ * self-connection columns. null keeps node_positions.
330
+ * @param {number|null} [nCodes=null] Number of codes (with `key`).
324
331
  * @returns {{ nodes: Float64Array, nodeRows: number, nodeCols: number,
325
332
  * centroids: Float64Array|null }}
326
333
  * nodes — code positions in ENA space (= R's rotation$nodes)
327
334
  * centroids — LWS unit centroid positions (= R's model$centroids), or null
328
335
  * if libqe does not expose them
329
336
  */
330
- export function nodePositions(qe, networks, nUnits, nConnections, points, nDims, directed = false) {
331
- const solve = directed ? qe.directed_node_positions : qe.node_positions;
332
- const r = solve(networks, nUnits, nConnections, points, nUnits, nDims, nDims);
337
+ export function nodePositions(qe, networks, nUnits, nConnections, points, nDims,
338
+ directed = false, selfWeight = 2, key = null, nCodes = null) {
339
+ const r = directed
340
+ ? qe.directed_node_positions(networks, nUnits, nConnections, points, nUnits, nDims, nDims, selfWeight)
341
+ : key
342
+ ? qe.node_positions_by_key(networks, nUnits, nConnections, points, nUnits, nDims, nDims,
343
+ key.from, key.to, nCodes, selfWeight)
344
+ : qe.node_positions(networks, nUnits, nConnections, points, nUnits, nDims, nDims);
333
345
  return {
334
346
  nodes: new Float64Array(r.nodes.data),
335
347
  nodeRows: r.nodes.rows,
package/src/tensor.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * tensor.js — Context-tensor accumulation for rENA WASM.
3
3
  *
4
4
  * Maps tma's apply_tensor() semantics to the JS/WASM layer. Each unit's
5
- * network is accumulated by calling qe.accumulate_tensor_unit() with a
5
+ * network is accumulated by calling qe.accumulate_unit_networks() with a
6
6
  * multi-dimensional tensor that encodes per-factor-combination window sizes
7
7
  * and weights.
8
8
  *
@@ -353,8 +353,8 @@ export function buildHorizonContexts(rows, unitOf, nUnits, horizons) {
353
353
  * Accumulate tensor networks for all units across all conversations.
354
354
  *
355
355
  * Every accumulation — including plain windowed ENA, via defaultTensor(window)
356
- * — runs through here, so the per-line finalisation (fold / binarize / weight)
357
- * lives in exactly one place.
356
+ * — runs through here, and the per-line finalisation (fold / binarize /
357
+ * weight / unit sum / self diagonal) happens in libtma.
358
358
  *
359
359
  * @param {object} qe libqe WASM module
360
360
  * @param {Object[]} rows Full dataset
@@ -374,16 +374,28 @@ export function buildHorizonContexts(rows, unitOf, nUnits, horizons) {
374
374
  * @param {object|null} [horizons=null] flexible horizons (see
375
375
  * buildHorizonContexts); replaces
376
376
  * convoGroups as the windowing contexts
377
+ * @param {boolean} [misses=true] also accumulate missed co-occurrences
378
+ * (ground i present, response j absent)
379
+ * @param {boolean} [diagonal=false] unordered only: self-connections are
380
+ * also connection columns (A & A, A & B,
381
+ * B & B, ...; n(n+1)/2 columns)
377
382
  *
378
- * @returns {{ networks: Float64Array, rowConnectionCounts: Float64Array }}
383
+ * @returns {{ networks: Float64Array, rowConnectionCounts: Float64Array,
384
+ * selfNetworks: Float64Array, missNetworks: Float64Array|null,
385
+ * selfMissNetworks: Float64Array|null }}
379
386
  * networks nUnits × nConnections, row-major
380
387
  * rowConnectionCounts nRows × nConnections, row-major — each row's finalised
381
388
  * (folded / binarized / weighted) connection vector;
382
389
  * rows sum by unit to `networks`
390
+ * selfNetworks nUnits × nCodes — self-connections (ground i ×
391
+ * response i, the ordered diagonal), weighted per line
392
+ * missNetworks nUnits × nConnections — missed co-occurrences (misses)
393
+ * selfMissNetworks nUnits × nCodes — self-misses (misses)
383
394
  */
384
395
  export function accumulateTensor(qe, rows, codeMatrix, nRows, nCodes, nUnits,
385
396
  unitOf, convoGroups, tensorDef, ordered = false,
386
- binary = true, weight = null, horizons = null) {
397
+ binary = true, weight = null, horizons = null,
398
+ misses = true, diagonal = false) {
387
399
  const {
388
400
  dims,
389
401
  dimsSender = [],
@@ -400,12 +412,16 @@ export function accumulateTensor(qe, rows, codeMatrix, nRows, nCodes, nUnits,
400
412
  validateTensor(dims, tensorData, factors, factorLevels);
401
413
  const allTimes = timesCol ? parseTimes(rows, timesCol, timesEndCol || null) : null;
402
414
 
415
+ const withDiagonal = diagonal && !ordered;
403
416
  const nConnections = ordered
404
417
  ? nCodes * nCodes
405
- : qe.choose_two(nCodes);
418
+ : withDiagonal ? (nCodes * (nCodes + 1)) / 2 : qe.choose_two(nCodes);
406
419
 
407
420
  const networks = new Float64Array(nUnits * nConnections);
408
421
  const rowConnectionCounts = new Float64Array(nRows * nConnections);
422
+ const selfNetworks = new Float64Array(nUnits * nCodes);
423
+ const missNetworks = misses ? new Float64Array(nUnits * nConnections) : null;
424
+ const selfMissNetworks = misses ? new Float64Array(nUnits * nCodes) : null;
409
425
 
410
426
  // libqe weight argument: a weight-model name, or the legacy binary flag.
411
427
  const kernelWeight = weight ?? binary;
@@ -435,40 +451,42 @@ export function accumulateTensor(qe, rows, codeMatrix, nRows, nCodes, nUnits,
435
451
  ? buildContextLookupWithTimes(rows, rowIndices, factors, factorLevels, allTimes)
436
452
  : buildContextLookup(rows, rowIndices, factors, factorLevels);
437
453
 
438
- // Always accumulate the DIRECTED per-response-row counts (ordered
439
- // kernel), exactly as tma does — it calls apply_tensor with the
440
- // default ordered=TRUE and defers the ordered-vs-unordered decision
441
- // to aggregation. Passing the unordered flag here would make the
442
- // kernel emit an already-symmetric matrix that the fold below would
443
- // then double-count.
444
- const result = qe.accumulate_tensor_unit(
454
+ // One libtma call (accumulate_unit_networks), as tma's accumulate()
455
+ // in R: fold (unordered), per-line weight model, unit sum and the
456
+ // self-connection diagonal, for hits and misses. Unit vectors are
457
+ // additive over lines, so a unit's contexts simply add up.
458
+ const args = [
445
459
  tensorData, dimsArr,
446
460
  senderArr, receiverArr, modeArr,
447
461
  contextLookup, clRows, clCols,
448
462
  new Int32Array(localRows),
449
463
  ctxCodes, nCtx, nCodes,
450
464
  times,
451
- true
452
- );
453
-
454
- // Finalise each raw per-response-row connection vector with libqe's
455
- // shared kernel, exactly as tma does in R: fold to the upper
456
- // triangle (unordered) or keep the directed row (ordered), then
457
- // apply the weight model per line (binary clamp, product, sqrt,
458
- // log1p) before the per-unit sum. Row i is local row localRows[i].
465
+ ordered, kernelWeight, misses,
466
+ ];
467
+ // The trailing `diagonal` is an optional overload in libtma's binding.
468
+ const result = withDiagonal
469
+ ? qe.accumulate_unit_networks(...args, true)
470
+ : qe.accumulate_unit_networks(...args);
471
+ const addUnit = (target, vec, width) => {
472
+ const off = unit * width;
473
+ for (let c = 0; c < width; c++) target[off + c] += vec[c];
474
+ };
475
+ addUnit(networks, result.connection_counts, nConnections);
476
+ addUnit(selfNetworks, result.self_connection_counts, nCodes);
477
+ if (misses) {
478
+ addUnit(missNetworks, result.miss_counts, nConnections);
479
+ addUnit(selfMissNetworks, result.self_miss_counts, nCodes);
480
+ }
481
+
482
+ // Per-line weighted rows (rowConnectionCounts): libtma's weight_rows
483
+ // on the raw, already-folded rows. Row i is local row localRows[i].
459
484
  const rcc = result.row_connection_counts;
460
- const fin = qe.finalize_row_connections(
461
- rcc.data, rcc.rows, rcc.cols, nCodes, ordered, kernelWeight
462
- );
463
- const unitOff = unit * nConnections;
485
+ const fin = qe.weight_rows(rcc.data, rcc.rows, rcc.cols, ordered, kernelWeight);
464
486
  for (let i = 0; i < fin.rows; i++) {
465
487
  const rowOff = rowIndices[localRows[i]] * nConnections;
466
- const finOff = i * fin.cols;
467
- for (let c = 0; c < nConnections; c++) {
468
- const v = fin.data[finOff + c];
469
- rowConnectionCounts[rowOff + c] = v;
470
- networks[unitOff + c] += v;
471
- }
488
+ rowConnectionCounts.set(
489
+ fin.data.slice(i * fin.cols, (i + 1) * fin.cols), rowOff);
472
490
  }
473
491
  };
474
492
 
@@ -484,7 +502,7 @@ export function accumulateTensor(qe, rows, codeMatrix, nRows, nCodes, nUnits,
484
502
  if (localRows.length) accumulateContext(rowIndices, unit, localRows);
485
503
  }
486
504
  });
487
- return { networks, rowConnectionCounts };
505
+ return { networks, rowConnectionCounts, selfNetworks, missNetworks, selfMissNetworks };
488
506
  }
489
507
 
490
508
  for (const [, rowIndices] of convoGroups) {
@@ -503,7 +521,7 @@ export function accumulateTensor(qe, rows, codeMatrix, nRows, nCodes, nUnits,
503
521
  }
504
522
  }
505
523
 
506
- return { networks, rowConnectionCounts };
524
+ return { networks, rowConnectionCounts, selfNetworks, missNetworks, selfMissNetworks };
507
525
  }
508
526
 
509
527
  /**