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.

typescript
const m = manager.getModule();

// Property is done — destroy it
m.destroy(prop);

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 — 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 ...
  m.destroy(temp); // qudit factorized out, state vector shrinks
}

Checking validity after destroy

After calling destroy(), the property handle is invalid. Any operation on it will throw. Use is_valid() to check:

typescript
m.destroy(prop);

prop.is_valid();  // false
// m.hadamard(prop);  // would throw — property is destroyed

WARNING

Do not use a destroyed property for gates, measurement, or queries. Check is_valid() if you're unsure whether a property has been destroyed.

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) {
  m.destroy(tempProp);
}

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

typescript
const m = manager.getModule();

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

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

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

QueryReturnsUse case
num_active_qudits(prop)Number of qudits in the property's shared quantum stateCheck entanglement group size
state_vector_size(prop)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:

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

  canCreateQuantumObject(): boolean {
    // Check if any existing property is already near the limit
    for (const [, prop] of this.entries()) {
      const size = this.getModule().state_vector_size(prop);
      if (size > this.STATE_BUDGET_HARD) return false;
    }
    return true;
  }

  shouldPreferClassical(): boolean {
    // When state is getting large, prefer classical behavior
    for (const [, prop] of this.entries()) {
      const size = this.getModule().state_vector_size(prop);
      if (size > this.STATE_BUDGET_WARN) return true;
    }
    return false;
  }

  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);
    return true;
  }
}

TIP

State budget queries are cheap read-only operations. You can call them every frame without performance concern.

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 { QuantumForge, ensureLoaded } from "quantum-forge/quantum";

await ensureLoaded();

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

const m = sim.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();

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 = QuantumForge.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 = QuantumForge.createSimulation();
const simB = QuantumForge.createSimulation();

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

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

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 QuantumForge.createQuantumProperty() continues to work. Properties in the global context can entangle freely with each other, as before.

typescript
// These are equivalent — both use the global context
const prop = manager.acquireProperty();
const prop2 = QuantumForge.createQuantumProperty(2);

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

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

Simulation lifecycle

MethodDescription
QuantumForge.createSimulation()Create a new isolated simulation
new QuantumSimulation()Direct constructor (same result)
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.getModule()Access the WASM module for gate and query calls
sim.destroy()Release all properties and state vectors at once

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 = QuantumForge.createSimulation();
const board = sim.createProperty(2);
const ancilla = sim.createProperty(2);

const m = sim.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