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

Skip to content

Gates ​

Quantum gates transform the state of properties without measuring them. All gates are called via getModule() on your QuantumPropertyManager subclass. Every gate accepts an optional predicates parameter. Predicated gates create entanglement between properties (see Core Concepts: Predicated Operations).

Gate Reference ​

GateMethodDimFraction
Cyclem.cycle(prop, fraction?, predicates?)AnyOptional
Shiftm.shift(prop, fraction?, predicates?)AnyOptional
Xm.x(prop, fraction?, predicates?)AnyOptional. Alias for shift
Hadamardm.hadamard(prop, fraction?, predicates?)AnyOptional
Inverse Hadamardm.inverse_hadamard(prop, predicates?)AnyNone
Clockm.clock(prop, fraction?, predicates?)AnyOptional
Zm.z(prop, fraction?, predicates?)AnyOptional. Alias for clock
Ym.y(prop, fraction?, predicates?)2 onlyOptional
iSwapm.i_swap(p1, p2, fraction, predicates?)AnyRequired
Swapm.swap(p1, p2, predicates?)AnyNone
Phase Rotatem.phase_rotate(predicates, angle)AnyNone

Where m = this.getModule(). Gate names are snake_case at the WASM level.

x is an alias for shift, not for cycle. z is an alias for clock. Both aliases take exactly the arguments their target takes.

Fraction is not a default, it selects a different code path

"Optional" above means the argument may be omitted, not that omitting it fills in 1. Calling m.cycle(prop) runs the non-fractional gate, a discrete permutation of basis states. Calling m.cycle(prop, 1.0) runs the fractional gate evaluated at 1.0, a continuous rotation. On the shipped build the two land on the same state at exactly 1.0 (they agree to 1e-16 for every single-qudit gate, with and without predicates), but the fractional path is slower. Internally the two are told apart by a NaN sentinel for "no fraction", so omit the argument when you want the permutation and pass a fraction when you want the rotation. Do not write 1 to mean "the normal gate".

Conditional Gates (Predicates) ​

Every gate accepts an optional predicates parameter that conditions the gate on the state of other properties. This is the primary mechanism for creating entanglement. When a gate depends on another property's state, the two become correlated.

typescript
import type { PredicateSpec } from "quantum-forge/quantum";

const predicates: PredicateSpec[] = [
  { property: controlProp, value: 1, isEqual: true },   // control must be |1⟩
  { property: otherProp, value: 0, isEqual: false },     // other must NOT be |0⟩
];

const m = this.getModule();
// To build WASM predicate objects from PredicateSpec:
const wasmPreds = predicates.map(s =>
  s.isEqual ? s.property.is(s.value) : s.property.is_not(s.value)
);

// Gate only applies when ALL predicates are satisfied
m.shift(targetProp, undefined, wasmPreds);      // CNOT (controlled-NOT), creates entanglement
m.hadamard(targetProp, undefined, wasmPreds);   // controlled-Hadamard, conditional superposition
m.i_swap(p1, p2, 0.5, wasmPreds);               // controlled entanglement

Predicates sit in the third argument, so a predicated non-fractional gate has to pass something for the fraction. Pass undefined (or null). The wrapper checks for it explicitly and dispatches to the non-fractional overload. Passing 1 instead would silently give you the fractional gate at 1.0, which is a different operation.

The PredicateSpec type:

typescript
interface PredicateSpec {
  property: QFProperty;  // the property to check
  value: number;         // the basis state to compare against
  isEqual: boolean;      // true = "is this value", false = "is NOT this value"
}

Predicates cannot target the gated property ​

A predicate may not reference the same property the gate is acting on. The simulator rejects it rather than silently doing something sensible, and the error comes back as a TargetControlOverlap whose message reads like Cycle operation has overlapping targets and controls (the gate name in front varies).

typescript
// WRONG: the predicate names the property being gated
m.cycle(prop, undefined, [prop.is(1)]);
// throws: "Cycle operation has overlapping targets and controls"

// RIGHT: the predicate names a different property
m.cycle(target, undefined, [control.is(1)]);

This applies to two-property gates too. Neither p1 nor p2 may appear in the predicate list for swap or i_swap.

Phase Rotate ​

Applies a phase rotation conditioned on predicates. Unlike other gates, phase_rotate takes only predicates, with no target property.

typescript
m.phase_rotate(wasmPreds, Math.PI);       // π phase flip on matching states
m.phase_rotate(wasmPreds, Math.PI / 4);   // π/4 phase shift

Passing an empty predicate list is a silent no-op. The C++ implementation returns immediately without touching the state and without raising anything, so a bug that empties your predicate array shows up as a missing phase, not as an error. Check the array length yourself if that matters.

Single-Property Gates ​

Cycle ​

Cyclic permutation of basis states, incrementing the value mod d: |0⟩→|1⟩→|2⟩→...→|d-1⟩→|0⟩. For dimension 2, this is the NOT gate.

typescript
const m = this.getModule();
m.cycle(prop);        // |0⟩ → |1⟩, |1⟩ → |0⟩ (dim=2)
m.cycle(prop, 0.5);   // fractional cycle (√NOT)

Game use case: Initialize a ball to |1⟩ (exists) after acquiring at |0⟩. Used in Quantum Pong's entangle-split.

Hadamard ​

Creates equal superposition from a definite state. The most common gate for putting objects "into quantum."

typescript
const m = this.getModule();
m.hadamard(prop);        // |0⟩ → (|0⟩+|1⟩)/√2, |1⟩ → (|0⟩−|1⟩)/√2
m.hadamard(prop, 0.5);   // fractional Hadamard (H^0.5)
m.hadamard(prop, 0.1);   // very gentle, barely moves probabilities

Game use case: Fractional Hadamard (H^t) for gradual probability diffusion. Small t = slow spread, large t = fast spread.

Inverse Hadamard ​

The adjoint (reverse) of Hadamard. Collapses a superposition back toward a definite state.

typescript
m.inverse_hadamard(prop);  // (|0⟩+|1⟩)/√2 → |0⟩

Clock / Z ​

Phase rotation (Z-rotation). Changes the phase of a state without changing measurement probabilities, until the property interacts with something else.

typescript
const m = this.getModule();
m.clock(prop);        // the generalized Z gate, non-fractional
m.clock(prop, 0.5);   // fractional clock: |1⟩ picks up a phase of π/2 at dim=2

clock's fraction is optional, like every other single-property gate here. m.clock(prop) is the full generalized Z. m.z(prop, ...) is the same function under another name.

Game use case: Quantum Pong's phase dial. Paddle hits bias subsequent entanglement interactions.

TIP

Phase alone doesn't change what you'll measure. It changes how the property interacts with other properties. Phase + iSwap = probability redistribution. See Phase & Interference.

Shift / X ​

Generalized bit-flip, and the inverse of cycle. shift decrements the basis value mod d: |0⟩→|d-1⟩, |1⟩→|0⟩, |2⟩→|1⟩, and so on.

typescript
// dimension 3
m.shift(prop);   // |0⟩ → |2⟩   (cycle would give |1⟩)

// dimension 2
m.shift(prop);   // |0⟩ → |1⟩   (same as cycle: +1 and -1 coincide mod 2)

At dimension 2 increment and decrement are the same operation, which is why shift and cycle look interchangeable in qubit code. At dimension 3 and above they move the state in opposite directions.

m.x(prop, ...) is an alias for shift, not for cycle. If you reach for x expecting |0⟩ → |1⟩ on a qutrit, you will get |2⟩.

Y Gate ​

Pauli Y gate. Qubit-only, throws an error if dimension is not 2. Composite gate: S-X-S-dagger.

typescript
m.y(prop);          // full Y gate (dim must be 2)
m.y(prop, 0.5);     // fractional Y: rotate around Y-axis on Bloch sphere

DANGER

m.y() throws if the property's dimension is not 2. This is intentional. The Y gate has no standard generalization to higher dimensions.

Two-Property Gates ​

iSwap ​

The primary entanglement gate. Creates quantum correlations between two properties.

typescript
const m = this.getModule();
m.i_swap(prop1, prop2, 0.5);   // half iSwap, maximal entanglement
m.i_swap(prop1, prop2, 1.0);   // full iSwap, complete state exchange with phase
m.i_swap(prop1, prop2, 0.25);  // quarter iSwap, partial entanglement

At fraction=0.5, starting from |10⟩:

(|10⟩ + i|01⟩) / √2

This means: 50% chance property 1 is |1⟩ and property 2 is |0⟩, and 50% chance the reverse, and they are correlated. Measuring one instantly determines the other.

WARNING

i_swap between properties in different shared states triggers a tensor product, growing the combined state space. This is why pooling is critical. Pooled properties are already in the shared state.

Game use case: Quantum Pong's entangle-split. Ball enters quantum zone, splits into correlated pair.

Swap ​

Direct state exchange between two properties. No entanglement, just moves quantum state from one to the other.

typescript
m.swap(prop1, prop2);

Batch Gate Execution ​

When applying many gates in a tight loop (e.g. an AI sweep applying 50-100 gates per tick), each m.cycle() / m.shift() / etc. crosses the JS-WASM boundary separately. The overhead of those boundary crossings can dominate the actual gate math.

executeBatch() sends an entire array of gate operations in one WASM call, eliminating ~99% of boundary crossings.

typescript
import type { BatchOp, BatchResult } from "quantum-forge/quantum";

const m = this.getModule();
const ops: BatchOp[] = [
  { op: "hadamard", target: prop1 },
  { op: "cycle", target: prop2, predicates: [prop1.is(1)] },
  { op: "shift", target: prop3, fraction: 0.5 },
  { op: "swap", target: prop1, target2: prop2 },
  { op: "phase_rotate", angle: Math.PI / 4, predicates: [prop1.is(1)] },
];

const result: BatchResult = m.executeBatch(ops);
// result.success      — true if all operations completed
// result.opsExecuted  — number of operations that ran before stopping
// result.errorMessage — non-empty on failure

BatchOp fields ​

FieldTypeRequiredDescription
opOpCodeYesGate name: "cycle", "shift", "clock", "x", "z", "y", "hadamard", "inverse_hadamard", "swap", "i_swap", "phase_rotate"
targetQFPropertyMost gatesPrimary target property (omit for phase_rotate)
target2QFPropertyswap, i_swapSecond target
fractionnumberNoGate fraction. Omit for the non-fractional (discrete permutation) variant.
anglenumberphase_rotateRotation angle in radians
predicatesPredicate[]NoWASM predicate objects (same as individual gate calls)

Fractional vs. non-fractional

Omitting fraction calls the non-fractional gate (a discrete permutation, e.g. cycle increments the basis state by 1). Setting fraction: 1.0 calls the fractional gate at fraction 1.0, a continuous rotation that reaches the same state by a slower path. This matches individual gate calls, where m.cycle(prop) and m.cycle(prop, 1.0) take different code paths.

Error handling ​

Operations execute sequentially. On the first error, execution stops — already-executed gates are not rolled back (quantum gates are side-effecting). Check result.opsExecuted to know how many completed.

typescript
const result = m.executeBatch(ops);
if (!result.success) {
  console.warn(`Batch failed after ${result.opsExecuted}/${ops.length} ops: ${result.errorMessage}`);
}

Tape-based batch (maximum throughput) ​

For 50+ operations, executeBatchTape() bypasses per-op marshaling entirely by accepting a pre-encoded Float64Array. The tape is bulk-copied to WASM in one memcpy.

OP is importable from the package. executeBatchTape is not: it lives on the loaded WASM module, so reach it through getModule().

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

const m = manager.getModule();

const properties = [prop1, prop2, prop3];
const tape = new Float64Array([
  OP.HADAMARD, 0, -1, NaN, 0, 0,         // hadamard(prop1)
  OP.CYCLE, 1, -1, NaN, 0, 1, 0, 1, 1,   // cycle(prop2) if prop1.is(1)
  OP.SWAP, 0, 2, NaN, 0, 0,              // swap(prop1, prop3)
]);

const result = m.executeBatchTape(properties, tape);

The NaN in the fraction slot is the "no fraction" sentinel, the tape's way of asking for the non-fractional gate. Writing 1 there gives you the fractional gate at 1.0 instead.

See the API Reference for the full tape format and OP constants including ROTATE_BASIS_PAIR for fused basis-state rotations.

When to use batch vs. individual calls ​

ApproachBest forBoundary cost
Individual callsInteractive gates, measurement-dependent logicOne JS-WASM crossing per gate
m.executeBatch(ops)Moderate batches, readable codeOne crossing, but each op is still marshaled through embind
m.executeBatchTape(props, tape)Pre-built sequences, 50+ ops, real-time animationOne crossing, one memcpy, no per-op marshaling

Measurement is not included in batch operations. Use measure_properties() after the batch completes.

Fractional Gates ​

Most gates accept a fraction parameter for partial application:

  • fraction=0.5: square root of the gate
  • fraction=0.1: very gentle, barely moves the state
  • fraction=2: applies the fractional gate twice over
  • fraction=1: the fractional gate at full strength, which is not the same thing as the discrete gate you get by omitting the argument

Fractional gates are key to creating gradual quantum effects. For example, hadamard(prop, 0.05) for slow probability diffusion, or y(prop, 0.3) for precise Bloch sphere navigation.

There is no "default fraction". Omitting the argument selects the non-fractional gate, a discrete permutation of basis states. Supplying a fraction selects a continuous rotation. Those are two separate operations sharing one name, and no fraction value gets you back to the discrete one. Decide which you want at each call site.

Powered by Quantum Forge