quantum-forge 2.7.0 → 3.0.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.
@@ -1,7 +1,8 @@
1
1
  // src/quantum/QuantumForgeLoader.ts
2
- var wasmBasePath = "/quantum-forge";
2
+ var explicitBasePath = null;
3
+ var variant = null;
3
4
  function setWasmBasePath(path) {
4
- wasmBasePath = path.endsWith("/") ? path.slice(0, -1) : path;
5
+ explicitBasePath = path.endsWith("/") ? path.slice(0, -1) : path;
5
6
  }
6
7
  function useQuantumForgeBuild(name) {
7
8
  if (isInitialized) {
@@ -11,7 +12,20 @@ function useQuantumForgeBuild(name) {
11
12
  );
12
13
  return;
13
14
  }
14
- setWasmBasePath(`/quantum-forge-${name}`);
15
+ variant = name;
16
+ explicitBasePath = null;
17
+ }
18
+ function runningFromDisk() {
19
+ return typeof process !== "undefined" && typeof process.versions?.node === "string" && import.meta.url.startsWith("file:");
20
+ }
21
+ function getWasmBasePath() {
22
+ if (explicitBasePath !== null) return explicitBasePath;
23
+ if (runningFromDisk()) {
24
+ const dist = import.meta.url.endsWith(".ts") ? "../../dist/" : "../";
25
+ const dir = variant ? `${dist}quantum-forge-${variant}/` : dist;
26
+ return new URL(dir, import.meta.url).href.replace(/\/$/, "");
27
+ }
28
+ return variant ? `/quantum-forge-${variant}` : "/quantum-forge";
15
29
  }
16
30
  var quantumForgeModule = null;
17
31
  var initPromise = null;
@@ -48,7 +62,7 @@ async function ensureLoaded() {
48
62
  initPromise = (async () => {
49
63
  const startTime = performance.now();
50
64
  logger?.info?.("Loading Quantum Forge WASM module...", "QuantumForgeLoader");
51
- const modulePath = `${wasmBasePath}/quantum-forge-web-api.mjs`;
65
+ const modulePath = `${getWasmBasePath()}/quantum-forge-web-api.mjs`;
52
66
  const mod = await import(
53
67
  /* @vite-ignore */
54
68
  modulePath
@@ -128,6 +142,683 @@ async function registerServiceWorker(swPath = "/quantum-forge-sw.js") {
128
142
  }
129
143
  }
130
144
 
145
+ // src/quantum/Quantum.ts
146
+ Symbol.dispose ??= /* @__PURE__ */ Symbol.for("Symbol.dispose");
147
+ var QUANTUM_HANDLE = /* @__PURE__ */ Symbol.for("quantum-forge.quantum-handle");
148
+ var nextId = 1;
149
+ var cache = /* @__PURE__ */ new Map();
150
+ var observers = /* @__PURE__ */ new Set();
151
+ var pending = [];
152
+ var delivering = false;
153
+ function reportObserverError(err) {
154
+ try {
155
+ const report = globalThis.reportError;
156
+ if (typeof report === "function") report(err);
157
+ else console.error("A quantum observer threw:", err);
158
+ } catch {
159
+ }
160
+ }
161
+ function notify(deliver) {
162
+ if (observers.size === 0) return;
163
+ pending.push({ deliver, to: [...observers] });
164
+ if (delivering) return;
165
+ delivering = true;
166
+ try {
167
+ for (let next = pending.shift(); next; next = pending.shift()) {
168
+ for (const o of next.to) {
169
+ try {
170
+ next.deliver(o);
171
+ } catch (err) {
172
+ reportObserverError(err);
173
+ }
174
+ }
175
+ }
176
+ } finally {
177
+ delivering = false;
178
+ }
179
+ }
180
+ function deepFreeze(value) {
181
+ if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
182
+ Object.freeze(value);
183
+ for (const child of Object.values(value)) deepFreeze(child);
184
+ }
185
+ return value;
186
+ }
187
+ function notifyGate(event) {
188
+ if (observers.size === 0) return;
189
+ deepFreeze(event);
190
+ notify((o) => o.onGate?.(event));
191
+ }
192
+ function notifyMeasure(event) {
193
+ if (observers.size === 0) return;
194
+ deepFreeze(event);
195
+ notify((o) => o.onMeasure?.(event));
196
+ }
197
+ var IMPOSSIBLE = 1e-10;
198
+ function wasm(method, call) {
199
+ try {
200
+ return call();
201
+ } catch (err) {
202
+ const message = err instanceof Error ? err.message : String(err);
203
+ const limit = /num_qudits (\d+) exceeds compile-time MAX_NUM_QUDITS \((\d+)\)/.exec(message);
204
+ if (limit) {
205
+ throw new Error(
206
+ `${method}(): this would put ${limit[1]} qudits in one entangled state, and the loaded build holds at most ${limit[2]}. Dispose handles you no longer need to free qudits.`,
207
+ { cause: err }
208
+ );
209
+ }
210
+ throw err;
211
+ }
212
+ }
213
+ function describeValue(v) {
214
+ return typeof v === "string" ? JSON.stringify(v) : String(v);
215
+ }
216
+ function describeValues(values) {
217
+ return `[${values.map(describeValue).join(", ")}]`;
218
+ }
219
+ function assertLive(prop) {
220
+ if (prop.disposed) {
221
+ throw new Error(`Quantum property #${prop.id} was disposed and can no longer be used.`);
222
+ }
223
+ }
224
+ function checkPredicates(preds, gate) {
225
+ if (!preds) return [];
226
+ return preds.map((p) => {
227
+ if (p.property.disposed) {
228
+ throw new Error(
229
+ `Predicate on quantum property #${p.property.id} cannot be used: the property was disposed.`
230
+ );
231
+ }
232
+ if (gate?.targets.includes(p.property)) {
233
+ throw new Error(
234
+ `${gate.method}(): a gate on quantum property #${p.property.id} cannot be conditioned on that same property. A when predicate must read a different property.`
235
+ );
236
+ }
237
+ return p.raw;
238
+ });
239
+ }
240
+ function assertPossible(method, terms, holds, what) {
241
+ const m = getModule();
242
+ const probability = (preds) => wasm(method, () => m.predicate_probability(preds));
243
+ const impossible = () => new Error(
244
+ `${method}(): ${what} has zero probability in the current state, so it cannot be forced. Force only an outcome a measurement could give.`
245
+ );
246
+ const own = terms.map((t) => probability([t.raw]));
247
+ if (holds && own.some((p) => p <= IMPOSSIBLE)) throw impossible();
248
+ if (!holds && own.some((p) => 1 - p > IMPOSSIBLE)) return;
249
+ const uses = /* @__PURE__ */ new Map();
250
+ for (const t of terms) uses.set(t.prop, (uses.get(t.prop) ?? 0) + 1);
251
+ let all = 1;
252
+ const correlated = [];
253
+ terms.forEach((t, i) => {
254
+ if (uses.get(t.prop) === 1 && t.prop.raw.num_active_qudits() === 1) all *= own[i];
255
+ else correlated.push(i);
256
+ });
257
+ if (correlated.length === 1) all *= own[correlated[0]];
258
+ else if (correlated.length > 1) all *= probability(correlated.map((i) => terms[i].raw));
259
+ if ((holds ? all : 1 - all) <= IMPOSSIBLE) throw impossible();
260
+ }
261
+ function serialize(preds) {
262
+ return (preds ?? []).map((p) => ({ id: p.property.id, index: p.index, isEqual: p.isEqual }));
263
+ }
264
+ function indexIn(prop, value) {
265
+ const declared = prop.values.indexOf(value);
266
+ if (declared !== -1) return declared;
267
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value < prop.dimension) {
268
+ return value;
269
+ }
270
+ throw new RangeError(
271
+ `${describeValue(value)} is not a value of quantum property #${prop.id}; its values are ${describeValues(prop.values)} (or an index 0..${prop.dimension - 1}).`
272
+ );
273
+ }
274
+ function finite(n, what) {
275
+ if (typeof n !== "number" || !Number.isFinite(n)) {
276
+ throw new RangeError(`${what} must be a finite number, got ${String(n)}.`);
277
+ }
278
+ return n;
279
+ }
280
+ var CONSTRUCT = /* @__PURE__ */ Symbol("Quantum.construct");
281
+ var construct;
282
+ var Quantum = class _Quantum {
283
+ static {
284
+ construct = (raw, values) => new _Quantum(CONSTRUCT, raw, values);
285
+ }
286
+ /** Unique per `quantum()` call, increasing, never reused. */
287
+ id;
288
+ /** Declared values in basis order. Index 0 is the starting value. */
289
+ values;
290
+ /** Number of declared values. */
291
+ dimension;
292
+ _raw;
293
+ // An ES private field, not a property: `Object.freeze()` on the handle
294
+ // (deep-freeze helpers on game state do this) cannot stop dispose() from
295
+ // setting it.
296
+ #disposed = false;
297
+ /**
298
+ * The WASM property behind this handle, for the batch API (`executeBatch`,
299
+ * `executeBatchTape`), which has no handle-level form yet.
300
+ *
301
+ * Caveats:
302
+ * - Operations run through `.raw` are invisible to observers, so a
303
+ * `QuantumRecorder` log will not contain them and a replay will diverge.
304
+ * - Never call `destroy()` on it. The handle still owns it, and its
305
+ * `dispose()` would then fail.
306
+ * - It is valid only while the handle is live. After `dispose()` it may back
307
+ * a different handle, so this getter throws.
308
+ * @throws Error once the handle is disposed.
309
+ */
310
+ get raw() {
311
+ assertLive(this);
312
+ return this._raw;
313
+ }
314
+ constructor(token, raw, values) {
315
+ if (token !== CONSTRUCT) {
316
+ throw new TypeError("Quantum handles are created with quantum(); the constructor is private.");
317
+ }
318
+ Object.defineProperty(this, QUANTUM_HANDLE, { value: true });
319
+ this.id = nextId++;
320
+ this._raw = raw;
321
+ this.values = Object.freeze([...values]);
322
+ this.dimension = values.length;
323
+ }
324
+ /** True once `dispose()` has run. Every other call then throws. */
325
+ get disposed() {
326
+ return this.#disposed;
327
+ }
328
+ // -- Evolution --
329
+ /**
330
+ * Hadamard gate: spread the property evenly across every value.
331
+ * @param fraction Omit (or pass exactly 1) for the discrete gate; any other number runs the fractional gate.
332
+ */
333
+ hadamard(fraction, opts) {
334
+ return this._gate("hadamard", "hadamard", fraction, opts);
335
+ }
336
+ /** Inverse Hadamard gate. Undoes `hadamard()`. */
337
+ inverseHadamard(opts) {
338
+ assertLive(this);
339
+ const preds = checkPredicates(opts?.when, { method: "inverseHadamard", targets: [this] });
340
+ const m = getModule();
341
+ wasm("inverseHadamard", () => {
342
+ if (preds.length) m.inverse_hadamard(this.raw, preds);
343
+ else m.inverse_hadamard(this.raw);
344
+ });
345
+ const event = {
346
+ op: "inverse_hadamard",
347
+ target: this.id,
348
+ predicates: serialize(opts?.when)
349
+ };
350
+ notifyGate(event);
351
+ return this;
352
+ }
353
+ /**
354
+ * Cycle gate: move to the next value, wrapping (index + 1 mod dimension).
355
+ * @param fraction Omit (or pass exactly 1) for the discrete gate; any other number runs the fractional gate.
356
+ */
357
+ cycle(fraction, opts) {
358
+ return this._gate("cycle", "cycle", fraction, opts);
359
+ }
360
+ /**
361
+ * Shift gate: move to the previous value, wrapping (index - 1 mod dimension).
362
+ * @param fraction Omit (or pass exactly 1) for the discrete gate; any other number runs the fractional gate.
363
+ */
364
+ shift(fraction, opts) {
365
+ return this._gate("shift", "shift", fraction, opts);
366
+ }
367
+ /**
368
+ * Clock gate: rotate the phase of each value by its index.
369
+ * @param fraction Omit (or pass exactly 1) for the discrete gate; any other number runs the fractional gate.
370
+ */
371
+ clock(fraction, opts) {
372
+ return this._gate("clock", "clock", fraction, opts);
373
+ }
374
+ /**
375
+ * Pauli X gate. Same as `shift()`; at dimension 2 also the same as `cycle()`.
376
+ * @param fraction Omit (or pass exactly 1) for the discrete gate; any other number runs the fractional gate.
377
+ */
378
+ x(fraction, opts) {
379
+ return this._gate("x", "x", fraction, opts);
380
+ }
381
+ /**
382
+ * Pauli Y gate. Dimension 2 only; throws on any other dimension.
383
+ * @param fraction Omit (or pass exactly 1) for the discrete gate; any other number runs the fractional gate.
384
+ */
385
+ y(fraction, opts) {
386
+ return this._gate("y", "y", fraction, opts);
387
+ }
388
+ /**
389
+ * Pauli Z gate. Same as `clock()`.
390
+ * @param fraction Omit (or pass exactly 1) for the discrete gate; any other number runs the fractional gate.
391
+ */
392
+ z(fraction, opts) {
393
+ return this._gate("z", "z", fraction, opts);
394
+ }
395
+ // -- Game-word aliases --
396
+ /**
397
+ * Alias for `hadamard()`.
398
+ * Spread the property evenly across every value.
399
+ */
400
+ superpose(fraction, opts) {
401
+ return this._gate("hadamard", "superpose", fraction, opts);
402
+ }
403
+ /**
404
+ * Alias for `cycle()`.
405
+ * Move to the next value, wrapping. `next(0.5)` is half a step.
406
+ */
407
+ next(fraction, opts) {
408
+ return this._gate("cycle", "next", fraction, opts);
409
+ }
410
+ /**
411
+ * Alias for `shift()`.
412
+ * Move to the previous value, wrapping.
413
+ */
414
+ previous(fraction, opts) {
415
+ return this._gate("shift", "previous", fraction, opts);
416
+ }
417
+ /**
418
+ * Alias for `clock()`.
419
+ * Turn the phase dial.
420
+ */
421
+ phase(fraction, opts) {
422
+ return this._gate("clock", "phase", fraction, opts);
423
+ }
424
+ /**
425
+ * Alias for `cycle()`, restricted to dimension 2.
426
+ * Swap the two values. `flip(0.5)` is the square root of NOT. Throws on any
427
+ * other dimension.
428
+ */
429
+ flip(fraction, opts) {
430
+ assertLive(this);
431
+ if (this.dimension !== 2) {
432
+ throw new Error(
433
+ `flip() needs a property with 2 values; quantum property #${this.id} has ${this.dimension}. Use next() or cycle() instead.`
434
+ );
435
+ }
436
+ return this._gate("cycle", "flip", fraction, opts);
437
+ }
438
+ // -- Interaction --
439
+ /**
440
+ * Swap the states of this property and `other`.
441
+ * @throws Error when `other` is this property, or has a different number of values.
442
+ */
443
+ swap(other, opts) {
444
+ const preds = this._pair("swap", other, opts);
445
+ const m = getModule();
446
+ wasm("swap", () => {
447
+ if (preds.length) m.swap(this.raw, other.raw, preds);
448
+ else m.swap(this.raw, other.raw);
449
+ });
450
+ const event = {
451
+ op: "swap",
452
+ targets: [this.id, other.id],
453
+ predicates: serialize(opts?.when)
454
+ };
455
+ notifyGate(event);
456
+ return this;
457
+ }
458
+ /**
459
+ * iSwap gate between this property and `other`. `iSwap(other, 0.5)` on
460
+ * a pair where one is set leaves them entangled.
461
+ * @param fraction Required; 1 is a full iSwap.
462
+ * @throws Error when `other` is this property, or has a different number of values.
463
+ */
464
+ iSwap(other, fraction, opts) {
465
+ const preds = this._pair("iSwap", other, opts);
466
+ finite(fraction, "iSwap fraction");
467
+ const m = getModule();
468
+ wasm("iSwap", () => {
469
+ if (preds.length) m.i_swap(this.raw, other.raw, fraction, preds);
470
+ else m.i_swap(this.raw, other.raw, fraction);
471
+ });
472
+ const event = {
473
+ op: "i_swap",
474
+ targets: [this.id, other.id],
475
+ fraction,
476
+ predicates: serialize(opts?.when)
477
+ };
478
+ notifyGate(event);
479
+ return this;
480
+ }
481
+ // -- Predicates --
482
+ /**
483
+ * A predicate that holds when this property equals `value`.
484
+ * @param value A declared value or its basis index.
485
+ * @throws RangeError when `value` is neither.
486
+ */
487
+ is(value) {
488
+ return this._predicate(value, true);
489
+ }
490
+ /**
491
+ * A predicate that holds when this property does not equal `value`.
492
+ * @param value A declared value or its basis index.
493
+ * @throws RangeError when `value` is neither.
494
+ */
495
+ isNot(value) {
496
+ return this._predicate(value, false);
497
+ }
498
+ // -- Measurement and inspection --
499
+ /** Measure this property, collapsing it and every entangled partner. Returns the declared value. */
500
+ measure() {
501
+ assertLive(this);
502
+ const [outcome] = wasm("measure", () => getModule().measure_properties([this.raw]));
503
+ const event = { op: "measure", targets: [this.id], outcomes: [outcome] };
504
+ notifyMeasure(event);
505
+ return this.values[outcome];
506
+ }
507
+ /**
508
+ * Measure this property with the outcome forced to `value`. For replays and tests.
509
+ * @param value A declared value or its basis index.
510
+ * @throws Error when `value` has zero probability in the current state. The
511
+ * state is left unchanged.
512
+ */
513
+ forcedMeasure(value) {
514
+ assertLive(this);
515
+ const index = indexIn(this, value);
516
+ assertPossible(
517
+ "forcedMeasure",
518
+ [{ prop: this, raw: this.raw.is(index) }],
519
+ true,
520
+ `value ${describeValue(this.values[index])} of quantum property #${this.id}`
521
+ );
522
+ const [outcome] = wasm(
523
+ "forcedMeasure",
524
+ () => getModule().forced_measure_properties([this.raw], [index])
525
+ );
526
+ const event = {
527
+ op: "forced_measure",
528
+ targets: [this.id],
529
+ forced: [index],
530
+ outcomes: [outcome]
531
+ };
532
+ notifyMeasure(event);
533
+ return this.values[outcome];
534
+ }
535
+ /**
536
+ * Probability that a measurement would give `value`. Does not collapse the state.
537
+ * @param value A declared value or its basis index.
538
+ */
539
+ probability(value) {
540
+ assertLive(this);
541
+ const index = indexIn(this, value);
542
+ return wasm("probability", () => getModule().predicate_probability([this.raw.is(index)]));
543
+ }
544
+ /** Probability of every declared value, in basis order. Does not collapse the state. */
545
+ probabilities() {
546
+ assertLive(this);
547
+ const probs = new Array(this.dimension).fill(0);
548
+ for (const entry of wasm("probabilities", () => getModule().probabilities([this.raw]))) {
549
+ probs[entry.qudit_values[0]] += entry.probability;
550
+ }
551
+ return this.values.map((value, i) => ({ value, probability: probs[i] }));
552
+ }
553
+ // -- Lifecycle --
554
+ /**
555
+ * End this property's life. Measures it first, which collapses any
556
+ * entangled partners. Then:
557
+ *
558
+ * - If it is alone in its state, it is reset to its first value and its
559
+ * WASM property goes to a private cache for the next `quantum()` at this
560
+ * dimension.
561
+ * - If it still shares a state with other qudits, its WASM property is
562
+ * destroyed, which factors it out of that state, and it is not cached.
563
+ * A partner left on its own shrinks back to one qudit.
564
+ *
565
+ * Calling it again does nothing. Works on a frozen handle.
566
+ */
567
+ dispose() {
568
+ if (this.#disposed) return;
569
+ const m = getModule();
570
+ const raw = this._raw;
571
+ const [outcome] = wasm("dispose", () => m.measure_properties([raw]));
572
+ this.#disposed = true;
573
+ if (raw.num_active_qudits() === 1) {
574
+ m.reset(raw, outcome);
575
+ let bucket = cache.get(this.dimension);
576
+ if (!bucket) {
577
+ bucket = [];
578
+ cache.set(this.dimension, bucket);
579
+ }
580
+ bucket.push(raw);
581
+ } else {
582
+ raw.destroy();
583
+ }
584
+ const value = this.values[outcome];
585
+ notify((o) => o.onDispose?.(this, value));
586
+ }
587
+ /** Same as `dispose()`, so a `using` declaration disposes the handle at scope exit. */
588
+ [Symbol.dispose]() {
589
+ this.dispose();
590
+ }
591
+ // -- Diagnostics --
592
+ /** Number of qudits in the shared state this property belongs to. */
593
+ numActiveQudits() {
594
+ assertLive(this);
595
+ return this.raw.num_active_qudits();
596
+ }
597
+ /** Number of basis amplitudes in the shared state vector. */
598
+ stateVectorSize() {
599
+ assertLive(this);
600
+ return this.raw.state_vector_size();
601
+ }
602
+ // -- Internals --
603
+ _predicate(value, isEqual) {
604
+ assertLive(this);
605
+ const index = indexIn(this, value);
606
+ const raw = isEqual ? this.raw.is(index) : this.raw.is_not(index);
607
+ return Object.freeze({ property: this, value: this.values[index], index, isEqual, raw });
608
+ }
609
+ /** Checks shared by swap() and iSwap(). Returns the WASM predicates. */
610
+ _pair(method, other, opts) {
611
+ assertLive(this);
612
+ assertLive(other);
613
+ if (other === this) {
614
+ throw new Error(`${method}(): quantum property #${this.id} cannot ${method} with itself.`);
615
+ }
616
+ if (other.dimension !== this.dimension) {
617
+ throw new Error(
618
+ `${method}(): quantum property #${this.id} has ${this.dimension} values and #${other.id} has ${other.dimension}. Both need the same number of values.`
619
+ );
620
+ }
621
+ return checkPredicates(opts?.when, { method, targets: [this, other] });
622
+ }
623
+ _gate(op, method, fractionOrOpts, maybeOpts) {
624
+ assertLive(this);
625
+ const [fraction, opts] = typeof fractionOrOpts === "object" ? [void 0, fractionOrOpts] : [fractionOrOpts, maybeOpts];
626
+ const preds = checkPredicates(opts?.when, { method, targets: [this] });
627
+ const sent = fraction === void 0 || fraction === 1 ? void 0 : finite(fraction, `${method} fraction`);
628
+ const fn = getModule()[op];
629
+ wasm(method, () => {
630
+ if (preds.length) fn(this.raw, sent, preds);
631
+ else if (sent !== void 0) fn(this.raw, sent);
632
+ else fn(this.raw);
633
+ });
634
+ const event = {
635
+ op,
636
+ target: this.id,
637
+ fraction: sent,
638
+ predicates: serialize(opts?.when)
639
+ };
640
+ notifyGate(event);
641
+ return this;
642
+ }
643
+ };
644
+ function declaredValuesProblem(values) {
645
+ if (values.length < 2) {
646
+ return { reason: "a quantum property needs at least 2 values", ErrorType: RangeError };
647
+ }
648
+ const seen = /* @__PURE__ */ new Set();
649
+ for (const v of values) {
650
+ if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean") {
651
+ return {
652
+ reason: `${String(v)} is not a string, number or boolean`,
653
+ ErrorType: TypeError
654
+ };
655
+ }
656
+ if (typeof v === "number" && !Number.isFinite(v)) {
657
+ return { reason: `${describeValue(v)} is not a finite number`, ErrorType: RangeError };
658
+ }
659
+ if (seen.has(v)) {
660
+ return { reason: `value ${describeValue(v)} is declared twice`, ErrorType: Error };
661
+ }
662
+ seen.add(v);
663
+ }
664
+ return void 0;
665
+ }
666
+ function quantum(arg) {
667
+ let values;
668
+ if (typeof arg === "number") {
669
+ if (!Number.isInteger(arg)) {
670
+ throw new RangeError(`quantum(${arg}): dimension must be an integer.`);
671
+ }
672
+ if (arg < 2) {
673
+ throw new RangeError(`quantum(${arg}): a quantum property needs at least 2 values.`);
674
+ }
675
+ values = Array.from({ length: arg }, (_, i) => i);
676
+ } else {
677
+ const problem = declaredValuesProblem(arg);
678
+ if (problem) {
679
+ throw new problem.ErrorType(`quantum(${describeValues(arg)}): ${problem.reason}.`);
680
+ }
681
+ values = arg;
682
+ }
683
+ const dimension = values.length;
684
+ const max = getMaxDimension();
685
+ if (dimension > max) {
686
+ throw new Error(
687
+ `quantum(): a property with ${dimension} values exceeds the loaded build's maximum of ${max} values per property.`
688
+ );
689
+ }
690
+ const raw = cache.get(dimension)?.pop() ?? getModule().QuantumForge.createQuantumProperty(dimension);
691
+ const prop = construct(raw, values);
692
+ notify((o) => o.onCreate?.(prop));
693
+ return prop;
694
+ }
695
+ function isQuantum(x) {
696
+ if (typeof x !== "object" || x === null) return false;
697
+ const marker = Object.getOwnPropertyDescriptor(x, QUANTUM_HANDLE);
698
+ return marker !== void 0 && "value" in marker && marker.value === true;
699
+ }
700
+ function clearQuantumCache() {
701
+ for (const bucket of cache.values()) {
702
+ for (const raw of bucket) raw.destroy();
703
+ }
704
+ cache.clear();
705
+ }
706
+ function observeQuantum(observer) {
707
+ observers.add(observer);
708
+ return () => {
709
+ observers.delete(observer);
710
+ };
711
+ }
712
+ function requireProps(fn, props) {
713
+ if (props.length === 0) throw new Error(`${fn}() needs at least one quantum property.`);
714
+ props.forEach(assertLive);
715
+ }
716
+ function measure(...props) {
717
+ requireProps("measure", props);
718
+ const outcomes = wasm("measure", () => getModule().measure_properties(props.map((p) => p.raw)));
719
+ const event = {
720
+ op: "measure",
721
+ targets: props.map((p) => p.id),
722
+ outcomes: [...outcomes]
723
+ };
724
+ notifyMeasure(event);
725
+ return outcomes.map((index, i) => props[i].values[index]);
726
+ }
727
+ function forcedMeasure(props, values) {
728
+ requireProps("forcedMeasure", props);
729
+ if (values.length !== props.length) {
730
+ throw new Error(
731
+ `forcedMeasure(): got ${props.length} properties but ${values.length} values.`
732
+ );
733
+ }
734
+ const forced = props.map((p, i) => indexIn(p, values[i]));
735
+ assertPossible(
736
+ "forcedMeasure",
737
+ props.map((p, i) => ({ prop: p, raw: p.raw.is(forced[i]) })),
738
+ true,
739
+ `the outcome ${describeValues(props.map((p, i) => p.values[forced[i]]))} for quantum properties ${props.map((p) => `#${p.id}`).join(", ")}`
740
+ );
741
+ const outcomes = wasm(
742
+ "forcedMeasure",
743
+ () => getModule().forced_measure_properties(
744
+ props.map((p) => p.raw),
745
+ forced
746
+ )
747
+ );
748
+ const event = {
749
+ op: "forced_measure",
750
+ targets: props.map((p) => p.id),
751
+ forced,
752
+ outcomes: [...outcomes]
753
+ };
754
+ notifyMeasure(event);
755
+ return outcomes.map((index, i) => props[i].values[index]);
756
+ }
757
+ function probabilities(...props) {
758
+ requireProps("probabilities", props);
759
+ return wasm("probabilities", () => getModule().probabilities(props.map((p) => p.raw))).map((entry) => ({
760
+ values: entry.qudit_values.map((index, i) => props[i].values[index]),
761
+ probability: entry.probability
762
+ }));
763
+ }
764
+ function densityMatrix(...props) {
765
+ requireProps("densityMatrix", props);
766
+ return wasm(
767
+ "densityMatrix",
768
+ () => getModule().reduced_density_matrix(props.map((p) => p.raw))
769
+ ).map((entry) => ({
770
+ row: entry.row_values.map((index, i) => props[i].values[index]),
771
+ col: entry.col_values.map((index, i) => props[i].values[index]),
772
+ real: entry.value.real,
773
+ imag: entry.value.imag
774
+ }));
775
+ }
776
+ function measureWhen(preds) {
777
+ const raw = checkPredicates(preds);
778
+ const outcome = wasm("measureWhen", () => getModule().measure_predicate(raw));
779
+ const event = {
780
+ op: "measure_predicate",
781
+ predicates: serialize(preds),
782
+ outcome
783
+ };
784
+ notifyMeasure(event);
785
+ return outcome !== 0;
786
+ }
787
+ function forcedMeasureWhen(preds, outcome) {
788
+ const raw = checkPredicates(preds);
789
+ const forced = outcome ? 1 : 0;
790
+ const what = `${outcome ? "every" : "not every"} predicate holding`;
791
+ assertPossible(
792
+ "forcedMeasureWhen",
793
+ preds.map((p, i) => ({ prop: p.property, raw: raw[i] })),
794
+ outcome,
795
+ what
796
+ );
797
+ const actual = wasm("forcedMeasureWhen", () => getModule().forced_measure_predicate(raw, forced));
798
+ const event = {
799
+ op: "forced_measure_predicate",
800
+ predicates: serialize(preds),
801
+ forced,
802
+ outcome: actual
803
+ };
804
+ notifyMeasure(event);
805
+ if (actual !== forced) {
806
+ throw new Error(`forcedMeasureWhen(): ${what} has zero probability in the current state, so it cannot be forced.`);
807
+ }
808
+ return actual !== 0;
809
+ }
810
+ function probabilityWhen(preds) {
811
+ const raw = checkPredicates(preds);
812
+ return wasm("probabilityWhen", () => getModule().predicate_probability(raw));
813
+ }
814
+ function phaseRotate(angle, opts) {
815
+ finite(angle, "phaseRotate angle");
816
+ const raw = checkPredicates(opts.when);
817
+ wasm("phaseRotate", () => getModule().phase_rotate(raw, angle));
818
+ const event = { op: "phase_rotate", angle, predicates: serialize(opts.when) };
819
+ notifyGate(event);
820
+ }
821
+
131
822
  // src/quantum/QuantumPropertyManager.ts
132
823
  var QuantumPropertyManager = class {
133
824
  dimension;
@@ -215,23 +906,381 @@ var QuantumPropertyManager = class {
215
906
  getModule() {
216
907
  return getModule();
217
908
  }
218
- // -- Internal access for QuantumRecorder replay --
219
- /** @internal — used by QuantumRecorder.replayLog() to restore pool state. */
909
+ // -- Internal access for LegacyQuantumRecorder replay --
910
+ /** @internal — used by LegacyQuantumRecorder.replayLog() to restore pool state. */
220
911
  _setPool(pool) {
221
912
  this.pool = pool;
222
913
  }
223
- /** @internal — used by QuantumRecorder to enumerate live handles. */
914
+ /** @internal — used by LegacyQuantumRecorder to enumerate live handles. */
224
915
  _getProperties() {
225
916
  return this.properties;
226
917
  }
227
- /** @internal — used by QuantumRecorder to enumerate pool handles. */
918
+ /** @internal — used by LegacyQuantumRecorder to enumerate pool handles. */
228
919
  _getPool() {
229
920
  return this.pool;
230
921
  }
231
922
  };
232
923
 
233
924
  // src/quantum/QuantumRecorder.ts
925
+ var LOG_VERSION = 1;
926
+ function copyPredicates(preds) {
927
+ return preds.map((p) => ({ id: p.id, index: p.index, isEqual: p.isEqual }));
928
+ }
929
+ function gateEntry(e) {
930
+ const predicates = copyPredicates(e.predicates);
931
+ switch (e.op) {
932
+ case "inverse_hadamard":
933
+ return { op: e.op, target: e.target, predicates };
934
+ case "swap":
935
+ return { op: e.op, targets: [e.targets[0], e.targets[1]], predicates };
936
+ case "i_swap":
937
+ return { op: e.op, targets: [e.targets[0], e.targets[1]], fraction: e.fraction, predicates };
938
+ case "phase_rotate":
939
+ return { op: e.op, angle: e.angle, predicates };
940
+ default:
941
+ return e.fraction === void 0 ? { op: e.op, target: e.target, predicates } : { op: e.op, target: e.target, fraction: e.fraction, predicates };
942
+ }
943
+ }
944
+ function measureEntry(e) {
945
+ switch (e.op) {
946
+ case "measure":
947
+ return { op: e.op, targets: [...e.targets], outcomes: [...e.outcomes] };
948
+ case "forced_measure":
949
+ return {
950
+ op: e.op,
951
+ targets: [...e.targets],
952
+ forced: [...e.forced],
953
+ outcomes: [...e.outcomes]
954
+ };
955
+ case "measure_predicate":
956
+ return { op: e.op, predicates: copyPredicates(e.predicates), outcome: e.outcome };
957
+ case "forced_measure_predicate":
958
+ return {
959
+ op: e.op,
960
+ predicates: copyPredicates(e.predicates),
961
+ forced: e.forced,
962
+ outcome: e.outcome
963
+ };
964
+ }
965
+ }
966
+ function referencedIds(e) {
967
+ if (e.op === "create") return [];
968
+ const ids = [];
969
+ if ("id" in e) ids.push(e.id);
970
+ if ("target" in e) ids.push(e.target);
971
+ if ("targets" in e) ids.push(...e.targets);
972
+ if ("predicates" in e) ids.push(...e.predicates.map((p) => p.id));
973
+ return ids;
974
+ }
975
+ var isObj = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
976
+ var isInt = (x) => typeof x === "number" && Number.isInteger(x);
977
+ var isNum = (x) => typeof x === "number" && Number.isFinite(x);
978
+ var isIntArray = (x) => Array.isArray(x) && x.every(isInt);
979
+ var isPair = (x) => isIntArray(x) && x.length === 2;
980
+ var isBit = (x) => x === 0 || x === 1;
981
+ var isPredicates = (x) => Array.isArray(x) && x.every((p) => isObj(p) && isInt(p.id) && isInt(p.index) && typeof p.isEqual === "boolean");
982
+ function indexReason(dims, id, index, what) {
983
+ if (index < 0) return `${what} ${index} is negative`;
984
+ const dim = dims.get(id);
985
+ if (dim !== void 0 && index >= dim) {
986
+ return `${what} ${index} is out of range for quantum property #${id}, which has ${dim} values`;
987
+ }
988
+ return void 0;
989
+ }
990
+ function predicatesReason(dims, preds) {
991
+ if (!isPredicates(preds)) return "predicates are malformed";
992
+ for (const p of preds) {
993
+ const reason = indexReason(dims, p.id, p.index, "predicate index");
994
+ if (reason) return reason;
995
+ }
996
+ return void 0;
997
+ }
998
+ function indicesReason(dims, targets, indices, what) {
999
+ for (let i = 0; i < targets.length; i++) {
1000
+ const reason = indexReason(dims, targets[i], indices[i], what);
1001
+ if (reason) return reason;
1002
+ }
1003
+ return void 0;
1004
+ }
1005
+ function invalidReason(e, dims) {
1006
+ switch (e.op) {
1007
+ case "create":
1008
+ if (!isInt(e.id)) return "id must be an integer";
1009
+ if (!Array.isArray(e.values)) return "values must be an array";
1010
+ return declaredValuesProblem(e.values)?.reason;
1011
+ case "dispose":
1012
+ if (!isInt(e.id) || !isInt(e.outcome)) return "id and outcome must be integers";
1013
+ return indexReason(dims, e.id, e.outcome, "outcome");
1014
+ case "hadamard":
1015
+ case "cycle":
1016
+ case "shift":
1017
+ case "clock":
1018
+ case "x":
1019
+ case "y":
1020
+ case "z":
1021
+ if (!isInt(e.target)) return "target must be an integer";
1022
+ if (e.fraction !== void 0 && !isNum(e.fraction)) return "fraction must be a number";
1023
+ return predicatesReason(dims, e.predicates);
1024
+ case "inverse_hadamard":
1025
+ if (!isInt(e.target)) return "target must be an integer";
1026
+ return predicatesReason(dims, e.predicates);
1027
+ case "swap":
1028
+ if (!isPair(e.targets)) return "targets must be two integers";
1029
+ return predicatesReason(dims, e.predicates);
1030
+ case "i_swap":
1031
+ if (!isPair(e.targets)) return "targets must be two integers";
1032
+ if (!isNum(e.fraction)) return "fraction must be a number";
1033
+ return predicatesReason(dims, e.predicates);
1034
+ case "phase_rotate":
1035
+ if (!isNum(e.angle)) return "angle must be a number";
1036
+ return predicatesReason(dims, e.predicates);
1037
+ case "measure":
1038
+ if (!isIntArray(e.targets) || !isIntArray(e.outcomes)) {
1039
+ return "targets and outcomes must be integer arrays";
1040
+ }
1041
+ if (e.targets.length !== e.outcomes.length) return "targets and outcomes differ in length";
1042
+ return indicesReason(dims, e.targets, e.outcomes, "outcome");
1043
+ case "forced_measure":
1044
+ if (!isIntArray(e.targets) || !isIntArray(e.forced) || !isIntArray(e.outcomes)) {
1045
+ return "targets, forced and outcomes must be integer arrays";
1046
+ }
1047
+ if (e.targets.length !== e.outcomes.length) return "targets and outcomes differ in length";
1048
+ if (e.targets.length !== e.forced.length) return "targets and forced differ in length";
1049
+ return indicesReason(dims, e.targets, e.forced, "forced outcome") ?? indicesReason(dims, e.targets, e.outcomes, "outcome");
1050
+ case "measure_predicate":
1051
+ if (!isBit(e.outcome)) return "outcome must be 0 or 1";
1052
+ return predicatesReason(dims, e.predicates);
1053
+ case "forced_measure_predicate":
1054
+ if (!isBit(e.outcome) || !isBit(e.forced)) return "forced and outcome must be 0 or 1";
1055
+ return predicatesReason(dims, e.predicates);
1056
+ default:
1057
+ return typeof e.op === "string" ? `unknown op ${JSON.stringify(e.op)}` : "missing op";
1058
+ }
1059
+ }
1060
+ function validateLog(input, where) {
1061
+ if (!isObj(input)) {
1062
+ throw new Error(
1063
+ `${where}: expected a log object { "version": ${LOG_VERSION}, "entries": [...] }.`
1064
+ );
1065
+ }
1066
+ if (input.version !== LOG_VERSION) {
1067
+ const got = input.version === void 0 ? "no version" : `version ${JSON.stringify(input.version)}`;
1068
+ throw new Error(
1069
+ `${where}: unsupported log format (${got}). This build reads version ${LOG_VERSION}.`
1070
+ );
1071
+ }
1072
+ if (!Array.isArray(input.entries)) {
1073
+ throw new Error(`${where}: the log's "entries" must be an array.`);
1074
+ }
1075
+ if (input.untrackedIds !== void 0 && !isIntArray(input.untrackedIds)) {
1076
+ throw new Error(`${where}: the log's "untrackedIds" must be an array of integers.`);
1077
+ }
1078
+ const dims = /* @__PURE__ */ new Map();
1079
+ input.entries.forEach((entry, position) => {
1080
+ const reason = isObj(entry) ? invalidReason(entry, dims) : "not an object";
1081
+ if (reason) {
1082
+ throw new Error(`${where}: entry ${position} is invalid: ${reason}.`);
1083
+ }
1084
+ const valid = entry;
1085
+ if (valid.op === "create") dims.set(valid.id, valid.values.length);
1086
+ });
1087
+ const entries = [...input.entries];
1088
+ const log = { version: LOG_VERSION, entries };
1089
+ if (input.untrackedIds !== void 0) log.untrackedIds = [...input.untrackedIds];
1090
+ return log;
1091
+ }
234
1092
  var QuantumRecorder = class {
1093
+ _entries = [];
1094
+ _seen = /* @__PURE__ */ new Set();
1095
+ _untracked = /* @__PURE__ */ new Set();
1096
+ _detach;
1097
+ /**
1098
+ * @throws TypeError when given any argument. The 2.x recorder took a
1099
+ * `QuantumPropertyManager`; that one is now `LegacyQuantumRecorder`.
1100
+ */
1101
+ constructor(...args) {
1102
+ if (args.length > 0) {
1103
+ throw new TypeError(
1104
+ "new QuantumRecorder() takes no arguments: it records quantum() handles through observeQuantum(). To record a QuantumPropertyManager, use new LegacyQuantumRecorder(manager)."
1105
+ );
1106
+ }
1107
+ }
1108
+ /** Begin recording. Clears the log. Calling it while recording restarts with an empty log. */
1109
+ startRecording() {
1110
+ this._detach?.();
1111
+ this._entries = [];
1112
+ this._seen = /* @__PURE__ */ new Set();
1113
+ this._untracked = /* @__PURE__ */ new Set();
1114
+ const push = (entry) => {
1115
+ this._track(entry);
1116
+ this._entries.push(entry);
1117
+ };
1118
+ this._detach = observeQuantum({
1119
+ onCreate: (prop) => push({ op: "create", id: prop.id, values: [...prop.values] }),
1120
+ onGate: (event) => push(gateEntry(event)),
1121
+ onMeasure: (event) => push(measureEntry(event)),
1122
+ onDispose: (prop, value) => push({ op: "dispose", id: prop.id, outcome: prop.values.indexOf(value) })
1123
+ });
1124
+ }
1125
+ /** Note created ids, and warn the first time an entry touches a handle this recording never saw created. */
1126
+ _track(entry) {
1127
+ if (entry.op === "create") {
1128
+ this._seen.add(entry.id);
1129
+ return;
1130
+ }
1131
+ for (const id of referencedIds(entry)) {
1132
+ if (this._seen.has(id) || this._untracked.has(id)) continue;
1133
+ this._untracked.add(id);
1134
+ console.warn(
1135
+ `QuantumRecorder: ${entry.op} touched quantum property #${id}, which was created before startRecording(). The log cannot be replayed. Start recording before creating the handles you want replayed.`
1136
+ );
1137
+ }
1138
+ }
1139
+ /**
1140
+ * Stop recording and return the log, `{ version: 1, entries }`. The log
1141
+ * carries `untrackedIds` when it touched handles created before recording.
1142
+ */
1143
+ stopRecording() {
1144
+ this._detach?.();
1145
+ this._detach = void 0;
1146
+ return this.getLog();
1147
+ }
1148
+ /** True between `startRecording()` and `stopRecording()`. */
1149
+ isRecording() {
1150
+ return this._detach !== void 0;
1151
+ }
1152
+ /** A copy of the log so far, `{ version: 1, entries }`. Works while recording. */
1153
+ getLog() {
1154
+ const log = { version: LOG_VERSION, entries: structuredClone(this._entries) };
1155
+ if (this._untracked.size > 0) log.untrackedIds = [...this._untracked].sort((a, b) => a - b);
1156
+ return log;
1157
+ }
1158
+ /**
1159
+ * Replay a log into new handles. Each `create` makes a fresh handle with a
1160
+ * new id; the returned map is keyed by the id in the log. Handles the log
1161
+ * disposes are removed from the map, so it holds the handles still live at
1162
+ * the end of the log.
1163
+ *
1164
+ * The log is validated first, as `deserialize()` does. A recorder running
1165
+ * during the replay records it (see the class docs); the log passed in is
1166
+ * never modified.
1167
+ *
1168
+ * @throws Error when the log is malformed, lists `untrackedIds`, or an entry
1169
+ * references an id with no earlier `create` entry. No handle survives a throw.
1170
+ */
1171
+ static replay(log) {
1172
+ const { entries, untrackedIds } = validateLog(log, "QuantumRecorder.replay");
1173
+ if (untrackedIds && untrackedIds.length > 0) {
1174
+ throw new Error(
1175
+ `QuantumRecorder.replay: the log touches ${untrackedIds.map((id) => `#${id}`).join(", ")}, created before recording started, so it has no state to rebuild them from. Start recording before creating the handles you want replayed.`
1176
+ );
1177
+ }
1178
+ const handles = /* @__PURE__ */ new Map();
1179
+ const created = [];
1180
+ try {
1181
+ entries.forEach((entry, position) => replayEntry(entry, position, handles, created));
1182
+ } catch (error) {
1183
+ for (const h of created) {
1184
+ try {
1185
+ h.dispose();
1186
+ } catch {
1187
+ }
1188
+ }
1189
+ throw error;
1190
+ }
1191
+ return handles;
1192
+ }
1193
+ /** Serialize a log to JSON: `{ "version": 1, "entries": [...] }`. */
1194
+ static serialize(log) {
1195
+ return JSON.stringify(log);
1196
+ }
1197
+ /**
1198
+ * Parse a log serialized by `serialize()`.
1199
+ * @throws Error when the text is not JSON, the version is missing or
1200
+ * unsupported, or any entry is malformed or names a basis index outside its
1201
+ * handle's declared values.
1202
+ */
1203
+ static deserialize(text) {
1204
+ return validateLog(JSON.parse(text), "QuantumRecorder.deserialize");
1205
+ }
1206
+ };
1207
+ function replayEntry(entry, position, handles, created) {
1208
+ const get = (id) => {
1209
+ const h = handles.get(id);
1210
+ if (!h) {
1211
+ throw new Error(
1212
+ `QuantumRecorder.replay: entry ${position} (${entry.op}) references quantum property #${id}, which no earlier create entry in the log made. Start recording before creating the handles you want replayed.`
1213
+ );
1214
+ }
1215
+ return h;
1216
+ };
1217
+ const preds = (list) => list.map((p) => {
1218
+ const h = get(p.id);
1219
+ const value = h.values[p.index];
1220
+ return p.isEqual ? h.is(value) : h.isNot(value);
1221
+ });
1222
+ const force = (ids, outcomes) => {
1223
+ const props = ids.map(get);
1224
+ forcedMeasure(
1225
+ props,
1226
+ props.map((h, i) => h.values[outcomes[i]])
1227
+ );
1228
+ };
1229
+ switch (entry.op) {
1230
+ case "create": {
1231
+ if (handles.has(entry.id)) {
1232
+ throw new Error(
1233
+ `QuantumRecorder.replay: entry ${position} creates quantum property #${entry.id}, which an earlier create entry already made and the log has not disposed.`
1234
+ );
1235
+ }
1236
+ const h = quantum(entry.values);
1237
+ handles.set(entry.id, h);
1238
+ created.push(h);
1239
+ return;
1240
+ }
1241
+ case "dispose": {
1242
+ const h = get(entry.id);
1243
+ force([entry.id], [entry.outcome]);
1244
+ h.dispose();
1245
+ handles.delete(entry.id);
1246
+ return;
1247
+ }
1248
+ case "hadamard":
1249
+ case "cycle":
1250
+ case "shift":
1251
+ case "clock":
1252
+ case "x":
1253
+ case "y":
1254
+ case "z":
1255
+ get(entry.target)[entry.op](entry.fraction, { when: preds(entry.predicates) });
1256
+ return;
1257
+ case "inverse_hadamard":
1258
+ get(entry.target).inverseHadamard({ when: preds(entry.predicates) });
1259
+ return;
1260
+ case "swap":
1261
+ get(entry.targets[0]).swap(get(entry.targets[1]), { when: preds(entry.predicates) });
1262
+ return;
1263
+ case "i_swap":
1264
+ get(entry.targets[0]).iSwap(get(entry.targets[1]), entry.fraction, {
1265
+ when: preds(entry.predicates)
1266
+ });
1267
+ return;
1268
+ case "phase_rotate":
1269
+ phaseRotate(entry.angle, { when: preds(entry.predicates) });
1270
+ return;
1271
+ case "measure":
1272
+ case "forced_measure":
1273
+ force(entry.targets, entry.outcomes);
1274
+ return;
1275
+ case "measure_predicate":
1276
+ case "forced_measure_predicate":
1277
+ forcedMeasureWhen(preds(entry.predicates), entry.outcome !== 0);
1278
+ return;
1279
+ }
1280
+ }
1281
+
1282
+ // src/quantum/LegacyQuantumRecorder.ts
1283
+ var LegacyQuantumRecorder = class {
235
1284
  _recording = false;
236
1285
  _log = [];
237
1286
  _handleToIndex = /* @__PURE__ */ new Map();
@@ -537,10 +1586,17 @@ var OP = {
537
1586
  ROTATE_BASIS_PAIR: 11
538
1587
  };
539
1588
  export {
1589
+ LegacyQuantumRecorder,
540
1590
  OP,
1591
+ QUANTUM_HANDLE,
1592
+ Quantum,
541
1593
  QuantumPropertyManager,
542
1594
  QuantumRecorder,
1595
+ clearQuantumCache,
1596
+ densityMatrix,
543
1597
  ensureLoaded,
1598
+ forcedMeasure,
1599
+ forcedMeasureWhen,
544
1600
  getAttribution,
545
1601
  getMaxDimension,
546
1602
  getMaxQudits,
@@ -548,8 +1604,17 @@ export {
548
1604
  getModule,
549
1605
  getQuantumForge,
550
1606
  getVersion,
1607
+ getWasmBasePath,
551
1608
  getWasmMemoryBytes,
1609
+ isQuantum,
552
1610
  isReady,
1611
+ measure,
1612
+ measureWhen,
1613
+ observeQuantum,
1614
+ phaseRotate,
1615
+ probabilities,
1616
+ probabilityWhen,
1617
+ quantum,
553
1618
  registerServiceWorker,
554
1619
  setWasmBasePath,
555
1620
  startBackgroundLoad,