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
| Gate | Method | Dim | Fraction |
|---|---|---|---|
| Cycle | m.cycle(prop, fraction?, predicates?) | Any | Optional |
| Shift | m.shift(prop, fraction?, predicates?) | Any | Optional |
| X | m.x(prop, fraction?, predicates?) | Any | Optional. Alias for shift |
| Hadamard | m.hadamard(prop, fraction?, predicates?) | Any | Optional |
| Inverse Hadamard | m.inverse_hadamard(prop, predicates?) | Any | None |
| Clock | m.clock(prop, fraction?, predicates?) | Any | Optional |
| Z | m.z(prop, fraction?, predicates?) | Any | Optional. Alias for clock |
| Y | m.y(prop, fraction?, predicates?) | 2 only | Optional |
| iSwap | m.i_swap(p1, p2, fraction, predicates?) | Any | Required |
| Swap | m.swap(p1, p2, predicates?) | Any | None |
| Phase Rotate | m.phase_rotate(predicates, angle) | Any | None |
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.
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 entanglementPredicates 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:
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).
// 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.
m.phase_rotate(wasmPreds, Math.PI); // π phase flip on matching states
m.phase_rotate(wasmPreds, Math.PI / 4); // π/4 phase shiftPassing 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.
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."
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 probabilitiesGame 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.
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.
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=2clock'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.
// 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.
m.y(prop); // full Y gate (dim must be 2)
m.y(prop, 0.5); // fractional Y: rotate around Y-axis on Bloch sphereDANGER
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.
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 entanglementAt fraction=0.5, starting from |10⟩:
(|10⟩ + i|01⟩) / √2This 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.
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.
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 failureBatchOp fields
| Field | Type | Required | Description |
|---|---|---|---|
op | OpCode | Yes | Gate name: "cycle", "shift", "clock", "x", "z", "y", "hadamard", "inverse_hadamard", "swap", "i_swap", "phase_rotate" |
target | QFProperty | Most gates | Primary target property (omit for phase_rotate) |
target2 | QFProperty | swap, i_swap | Second target |
fraction | number | No | Gate fraction. Omit for the non-fractional (discrete permutation) variant. |
angle | number | phase_rotate | Rotation angle in radians |
predicates | Predicate[] | No | WASM 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.
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().
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
| Approach | Best for | Boundary cost |
|---|---|---|
| Individual calls | Interactive gates, measurement-dependent logic | One JS-WASM crossing per gate |
m.executeBatch(ops) | Moderate batches, readable code | One crossing, but each op is still marshaled through embind |
m.executeBatchTape(props, tape) | Pre-built sequences, 50+ ops, real-time animation | One 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 gatefraction=0.1: very gentle, barely moves the statefraction=2: applies the fractional gate twice overfraction=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.