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.
- package/QUANTUM_FORGE.md +213 -205
- package/README.md +74 -38
- package/dist/lib/quantum.d.ts +683 -64
- package/dist/lib/quantum.js +1073 -8
- package/dist/lib/quantum.js.map +1 -1
- package/dist/lib/vite-plugin.d.ts +1 -1
- package/dist/lib/vite-plugin.js.map +1 -1
- package/dist/quantum-forge-qubit/quantum-forge-web-esm.wasm +0 -0
- package/dist/quantum-forge-web-esm.wasm +0 -0
- package/package.json +6 -5
- package/quantum-forge-sw.js +3 -3
- package/scripts/prepare.mjs +16 -0
package/dist/lib/quantum.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// src/quantum/QuantumForgeLoader.ts
|
|
2
|
-
var
|
|
2
|
+
var explicitBasePath = null;
|
|
3
|
+
var variant = null;
|
|
3
4
|
function setWasmBasePath(path) {
|
|
4
|
-
|
|
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
|
-
|
|
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 = `${
|
|
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
|
|
219
|
-
/** @internal — used by
|
|
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
|
|
914
|
+
/** @internal — used by LegacyQuantumRecorder to enumerate live handles. */
|
|
224
915
|
_getProperties() {
|
|
225
916
|
return this.properties;
|
|
226
917
|
}
|
|
227
|
-
/** @internal — used by
|
|
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,
|