You're reading the 2.x docs. quantum-forge 3.0 is out: this page in 3.x / migration guide

Skip to content

Lifecycle & State Management ​

Quantum properties consume resources in the WASM simulator. By default, properties persist in their shared state vector until measured and pooled. This page covers tools for explicit lifecycle control: destroying properties you no longer need, querying state budget, and isolating independent simulations.

Destroying Properties ​

When a property is no longer needed, you can destroy it to immediately remove it from the shared state vector, shrinking the state and freeing resources.

destroy() is a method on the property handle itself, not on the module:

typescript
// Property is done, destroy it
prop.destroy();

destroy() factorizes the qudit out of any shared state vector. This is different from the measure-and-pool pattern:

ApproachWhat happensWhen to use
Measure + releaseCollapses to a value, resets to |0\u27E9, returns to poolNormal gameplay — you need the measurement result
destroy()Factorizes the qudit out of the state vector entirelyCleanup — you don't need the result, you want to shrink the state

Why destroy instead of letting properties go out of scope? ​

When a property handle goes out of scope (or you lose the reference), the qudit remains in the shared state vector. It's a dead qudit — it still consumes state space and slows down every operation on the remaining properties. destroy() removes it cleanly.

typescript
// BAD: property leaked into the state vector
function doSomething(registry: QuantumPropertyManager) {
  const temp = registry.acquireProperty();
  const m = registry.getModule();
  m.hadamard(temp);
  m.i_swap(temp, otherProp, 0.5);
  // temp goes out of scope, but its qudit is still in the shared state
}

// GOOD: explicit cleanup
function doSomething(registry: QuantumPropertyManager) {
  const temp = registry.acquireProperty();
  const m = registry.getModule();
  m.hadamard(temp);
  m.i_swap(temp, otherProp, 0.5);
  // ... use the entanglement ...
  temp.destroy(); // qudit factorized out, state vector shrinks
}

Checking validity after destroy ​

After calling destroy(), the property handle is no longer backed by a quantum state. Ask the handle itself with is_valid():

typescript
prop.destroy();

prop.is_valid();  // false

WARNING

Using a destroyed property for gates, measurement, or queries is undefined behavior in current builds. The wrappers do not guard for validity, so the failure mode ranges from a thrown error to a WASM trap that takes the module down with it. Guard the call site with is_valid() rather than relying on a catch:

typescript
if (prop.is_valid()) {
  m.hadamard(prop);
}

Replay and adapters ​

destroy() is particularly useful in recording/replay scenarios. When replaying a quantum log, the adapter creates temporary properties to rebuild state. Once replay is complete, those temporary properties should be destroyed rather than left in the state vector:

typescript
// After replaying a log, clean up any properties that aren't needed
for (const tempProp of replayTemporaries) {
  tempProp.destroy();
}

State Budget Queries ​

You can query the size of the quantum state at runtime. This enables backpressure: your game can make decisions based on how much state is currently allocated.

Per-property queries ​

Both queries are methods on the property handle, not module functions:

typescript
// How many qudits share this property's state vector?
const numQudits = prop.num_active_qudits();

// How many basis amplitudes are in the sparse state vector?
const sparseSize = prop.state_vector_size();

Both queries are read-only and do not modify state.

QueryReturnsUse case
prop.num_active_qudits()Number of qudits in the property's shared quantum stateCheck entanglement group size
prop.state_vector_size()Current number of basis amplitudes (sparse)Monitor memory pressure

Global limit ​

typescript
import { getMaxStateSize } from "quantum-forge/quantum";

const limit = getMaxStateSize(); // 100,000 basis amplitudes

getMaxStateSize() returns the compile-time maximum number of basis amplitudes any single state vector can hold. The WASM module enforces this limit — operations that would exceed it throw an error instead of crashing.

Implementing backpressure ​

Use state budget queries to implement graceful degradation:

QuantumPropertyManager has no public iteration API, so a subclass that wants to scan its own properties has to keep the ids it owns. Track them in a Set<string> maintained wherever you register and remove properties:

typescript
class QuantumRegistry extends QuantumPropertyManager {
  private readonly STATE_BUDGET_WARN = 10_000;
  private readonly STATE_BUDGET_HARD = 50_000;

  /** Ids this registry owns. Kept in sync by makeQuantum / measure. */
  private readonly liveIds = new Set<string>();

  makeQuantum(id: string): void {
    const prop = this.acquireProperty();
    this.getModule().hadamard(prop);
    this.setProperty(id, prop);
    this.liveIds.add(id);
  }

  measure(id: string): number {
    const prop = this.getProperty(id);
    if (!prop) return 0;
    const [value] = this.getModule().measure_properties([prop]);
    this.deleteProperty(id);
    this.releaseProperty(prop, value);
    this.liveIds.delete(id);
    return value;
  }

  /** Largest state vector across the properties this registry owns. */
  private largestStateSize(): number {
    let largest = 0;
    for (const id of this.liveIds) {
      const prop = this.getProperty(id);
      if (!prop) continue;
      largest = Math.max(largest, prop.state_vector_size());
    }
    return largest;
  }

  canCreateQuantumObject(): boolean {
    return this.largestStateSize() <= this.STATE_BUDGET_HARD;
  }

  shouldPreferClassical(): boolean {
    return this.largestStateSize() > this.STATE_BUDGET_WARN;
  }

  quantumSplit(originalId: string, newId: string): boolean {
    if (!this.canCreateQuantumObject()) {
      this.logger?.warn?.("State budget exceeded, falling back to classical");
      return false;
    }

    const prop1 = this.getProperty(originalId);
    if (!prop1) return false;

    const prop2 = this.acquireProperty();
    this.getModule().i_swap(prop1, prop2, 0.5);
    this.setProperty(newId, prop2);
    this.liveIds.add(newId);
    return true;
  }
}

TIP

State budget queries are cheap read-only operations. You can call them every frame without performance concern. The Set scan above is over your own ids, so it costs whatever a few map lookups cost.

QuantumSimulation ​

QuantumSimulation creates an isolated simulation context. Properties created within a simulation are sandboxed: they can entangle with each other but not with properties from other simulations (or from the global context).

typescript
import { getQuantumForge, getModule, ensureLoaded } from "quantum-forge/quantum";

await ensureLoaded();

const sim = getQuantumForge().createSimulation();
const prop1 = sim.createProperty(2); // dimension 2
const prop2 = sim.createProperty(2);

// Gates come from the module, not from the simulation
const m = getModule();
m.hadamard(prop1);
m.i_swap(prop1, prop2, 0.5); // works, same simulation

// Clean up. Releases all properties and state vectors at once
sim.destroy();

A simulation owns properties, not gates. There is no sim.getModule(). Its whole surface is createProperty, destroyProperty, factorizeAllSeparable, destroy, and isDestroyed. Gates and queries always come from getModule() (or manager.getModule()), and they work on properties from any simulation as long as a single call does not mix two of them.

Why isolated simulations? ​

The primary use case is search tree exploration in games like quantum chess. When exploring possible moves, each branch of the search tree needs its own quantum state. Without isolation, all branches share (and grow) the same state vector:

typescript
// WITHOUT QuantumSimulation — all replays share state, vector grows unbounded
for (const move of candidateMoves) {
  const props = replayUpTo(move);    // creates properties in global context
  const score = evaluate(props);      // state vector includes ALL replays
  cleanupReplay(props);              // even with cleanup, tensor products already happened
}

// WITH QuantumSimulation — each replay is isolated
for (const move of candidateMoves) {
  const sim = getQuantumForge().createSimulation();
  const props = replayUpTo(move, sim); // properties are sandboxed
  const score = evaluate(props);        // state vector is small (just this replay)
  sim.destroy();                        // everything released at once
}

Cross-simulation entanglement is an error ​

Attempting to entangle properties from different simulations throws an error:

typescript
const simA = getQuantumForge().createSimulation();
const simB = getQuantumForge().createSimulation();

const propA = simA.createProperty(2);
const propB = simB.createProperty(2);

const m = getModule();
// This throws. propA and propB are in different simulations
m.i_swap(propA, propB, 0.5);
// Error: [QuantumForgeError] Cannot entangle properties from different QuantumSimulation contexts

If you match on the message, match on that exact text. It is the string the C++ layer throws.

This is a feature, not a limitation. It prevents accidental state vector growth from cross-contamination between independent quantum contexts.

Global context still works ​

QuantumSimulation is opt-in. The existing pattern of creating properties via QuantumPropertyManager.acquireProperty() or getQuantumForge().createQuantumProperty() continues to work. Properties in the global context can entangle freely with each other, as before.

typescript
// Both of these live in the global context
const prop = manager.acquireProperty();
const prop2 = getQuantumForge().createQuantumProperty(2);

// Global properties can still entangle with each other
getModule().i_swap(prop, prop2, 0.5); // works fine

Use QuantumSimulation when you need isolation. Use the global context for simple cases.

Simulation lifecycle ​

MethodDescription
getQuantumForge().createSimulation()Create a new isolated simulation
new (getModule().QuantumSimulation)()Direct constructor, same result. The class is only reachable through the loaded module, not as a top-level import from quantum-forge/quantum
sim.createProperty(dimension)Create a property bound to this simulation
sim.destroyProperty(prop)Factorize one property out and free its resources
sim.factorizeAllSeparable()Split out any separable qudits across all shared states
sim.destroy()Release all properties and state vectors at once
sim.isDestroyed()Whether destroy() has already been called

Gate and query calls are not on the simulation. Use getModule() for those.

Incremental property destruction ​

For search algorithms that create many ancilla properties, use sim.destroyProperty(prop) to free resources incrementally instead of waiting for sim.destroy():

typescript
const sim = getQuantumForge().createSimulation();
const board = sim.createProperty(2);
const ancilla = sim.createProperty(2);

const m = getModule();
m.i_swap(board, ancilla, 0.5);
// ... use the entanglement ...
m.i_swap(board, ancilla, -0.5); // undo

// Ancilla is now separable — destroy it to free resources
sim.destroyProperty(ancilla);
// ancilla.is_valid() === false
// board keeps its quantum state (superposition preserved)

destroyProperty automatically detects separable qudits after removing the destroyed property and splits them into independent states. This prevents qudit accumulation across many do/undo search cycles.

Destroying entangled properties

If the property is entangled with others (not separable), destroying it measures the qudit, which collapses the entangled partners' state. A console warning is emitted in this case. To avoid unintended collapse, uncompute the entanglement before destroying.

Explicit separability scanning ​

After undo operations that may have restored separability (e.g. i_swap(a,b,f) followed by i_swap(a,b,-f)), call sim.factorizeAllSeparable() to split separable qudits into independent states without destroying any properties:

typescript
// After undoing a sequence of operations
sim.factorizeAllSeparable();
// Separable qudits now have independent state vectors

WARNING

After calling sim.destroy(), all properties created within the simulation become invalid. Check prop.is_valid() if you hold references to them.

Lifecycle Summary ​

ToolPurposeWhen to use
Measure + poolCollapse and recycle a propertyNormal gameplay
destroy()Remove a qudit from the state vectorCleanup without measurement
State budget queriesMonitor state vector sizeBackpressure, debug overlays
QuantumSimulationIsolate independent quantum contextsSearch trees, replay branches, testing

Powered by Quantum Forge