Unity API Reference
The package uses three namespaces:
| Namespace | Contents |
|---|---|
QRG.QuantumForge.Runtime | QuantumProperty, Basis, BasisValue, Predicate, the action components, the trackers, the trigger components |
QRG.QuantumForge.Core | QuantumForge, the static class that wraps the native plugin, plus NativeQuantumProperty |
QRG.QuantumForge.Utility | Optional helper components (ScaleProbability, RotateOnPhase, ButtonPressOnKey, Trigger) |
Dimension and property limits
The shipped native plugin is compiled with MAX_DIMENSION=3, MAX_NUM_QUDITS=12 and dynamic fallback off. A Basis with more than 3 values fails when the QuantumProperty creates its native state, and at most 12 properties can interact. Ask the loaded plugin with QuantumForge.GetMaxDimension() and QuantumForge.GetMaxQudits().
QuantumProperty
MonoBehaviour that gives a GameObject quantum state. On Awake it creates the native property at the initial value you picked in the Inspector. If the Basis is unset, or the initial value is not in the Basis, or the dimension exceeds the plugin limit, Awake logs a warning and the component stays inert.
Inspector fields
| Field | Type | Description |
|---|---|---|
basis | Basis | The basis (ScriptableObject) defining possible states. Public, so it is also settable from code before Awake |
Initial | BasisValue | The starting value. Private and serialized, shown as a dropdown of the Basis values. Inspector only |
Properties
| Property | Type | Description |
|---|---|---|
Dimension | int | Number of basis values, read from basis |
Predicate creation
Predicate is_value(BasisValue value)
Predicate is_value(string valueName)
Predicate is_value(int valueIndex)
Predicate is_not_value(BasisValue value)
Predicate is_not_value(string valueName)
Predicate is_not_value(int valueIndex)Single-property gates
All accept an optional params Predicate[] for conditional application. Fractions are float at this layer, and the value is passed straight through to the double parameter on the Core layer.
void Cycle(params Predicate[] predicates)
void Cycle(float fraction, params Predicate[] predicates)
void Shift(params Predicate[] predicates)
void Shift(float fraction, params Predicate[] predicates)
void Clock(params Predicate[] predicates)
void Clock(float fraction, params Predicate[] predicates)
void Hadamard(params Predicate[] predicates)
void Hadamard(float fraction, params Predicate[] predicates)
void InverseHadamard(params Predicate[] predicates) // no fractional form
void X(params Predicate[] predicates) // alias for Shift
void X(float fraction, params Predicate[] predicates)
void Y(params Predicate[] predicates) // qubit-only, errors when Dimension != 2
void Y(float fraction, params Predicate[] predicates)
void Z(params Predicate[] predicates) // alias for Clock
void Z(float fraction, params Predicate[] predicates)
void Reset(int currentValue) // reset to |0⟩ from a known valueEvery one of these also exists as a static that takes the property first, for example QuantumProperty.Cycle(prop, 0.5f, predicates).
Reset is composed from cycles in C#, not a native operation. It is only correct when the property really is in the definite basis state you name, such as right after measuring it. Applied to a superposition it permutes the state instead of resetting it. currentValue must be in [0, Dimension).
Two-property gates (static)
static void Swap(QuantumProperty prop1, QuantumProperty prop2, params Predicate[] predicates)
static void ISwap(QuantumProperty prop1, QuantumProperty prop2)
static void ISwap(QuantumProperty prop1, QuantumProperty prop2, params Predicate[] predicates)
static void ISwap(QuantumProperty prop1, QuantumProperty prop2, float fraction)
static void ISwap(QuantumProperty prop1, QuantumProperty prop2, float fraction, params Predicate[] predicates)
static void NCycle(QuantumProperty prop1, QuantumProperty prop2)The ISwap overloads without a fraction use fraction 1.0. NCycle at this layer takes no fraction. Use QuantumForge.NCycle in the Core layer if you need one.
Phase rotation (static)
static void PhaseRotate(float angle, params Predicate[] predicates)The angle is in radians. There is no target property. The predicates decide which basis states pick up the phase.
Measurement (static)
static int[] Measure(params QuantumProperty[] properties)
static int[] MeasureProperties(params QuantumProperty[] properties) // alias
static int Measure(params Predicate[] predicates) // stochastic projection
static int MeasurePredicate(params Predicate[] predicates) // aliasThe property form returns one measured basis value index per property. Entangled partners update automatically. The predicate form returns 1 when the predicates were satisfied, 0 when they were not.
Analysis (static)
static BasisProbability[] Probabilities(params QuantumProperty[] properties)
static Complex[,] ReducedDensityMatrix(params QuantumProperty[] properties)
static float[] MutualInformation(params QuantumProperty[] properties)
static float[,] CorrelationMatrix(params QuantumProperty[] properties)Complex is System.Numerics.Complex.
QuantumProperty.BasisProbability
[Serializable]
public struct BasisProbability
{
public float Probability;
public BasisValue[] BasisValues; // one entry per property, in the order you passed them
}Two types named BasisProbability
QuantumProperty.BasisProbability carries BasisValue[] BasisValues, the named values from your Basis assets. QuantumForge.BasisProbability in the Core layer carries int[] QuditValues, raw indices with no Basis attached. QuantumProperty.Probabilities converts the second into the first by looking each index up in the property's Basis. They are unrelated types, so do not expect one to assign to the other.
Predicate
A condition for a controlled quantum operation. It is a class in QRG.QuantumForge.Runtime, not a nested type on QuantumProperty and not a struct, so an unassigned element of a Predicate[] is null and the gate call rejects it.
[Serializable]
public class Predicate
{
public QuantumProperty property;
public BasisValue value; // shown in the Inspector as a dropdown of the property's Basis
public bool is_equal; // true = "is this value", false = "is NOT this value"
}Build one with the convenience methods on QuantumProperty instead of filling the fields by hand:
var pred = myProp.is_value("rock"); // true when property is "rock"
var pred = myProp.is_not_value(0); // true when property is NOT index 0Basis
ScriptableObject that defines the possible states of a quantum property.
Create via: Assets > Create > Quantum > Basis
| Field | Type | Description |
|---|---|---|
values | List<BasisValue> | The list of basis values. At most 3 with the shipped plugin |
Dimension | int | Number of values (read-only) |
BasisValue
A class, compared by Name:
[Serializable]
public class BasisValue : IEquatable<BasisValue>
{
public string Name;
}BasisValueDropdownAttribute
public class BasisValueDropdownAttribute : PropertyAttributePut [BasisValueDropdown] on a serialized BasisValue field to get a dropdown of the relevant Basis values instead of a raw name box. QuantumProperty.Initial and Predicate.value use it, and custom actions with their own BasisValue fields should too.
Actions
All actions implement IQuantumAction and are MonoBehaviours you attach to GameObjects and wire to Unity Events. The method is apply(), lowercase.
IQuantumAction
public interface IQuantumAction
{
Predicate[] Predicates { get; set; }
QuantumProperty[] TargetProperties { get; set; }
void apply();
}Action components
| Component | Targets | Parameters | Description |
|---|---|---|---|
Hadamard | 1+ | fraction | Creates equal superposition |
Cycle | 1+ | fraction | Cyclic permutation. Works at any dimension. At dimension 2 it is a bit flip, the same thing Shift does |
Shift | 1+ | fraction | Generalized bit-flip |
Clock | 1+ | fraction | Phase rotation (Z-rotation) |
InverseHadamard | 1+ | none | Adjoint of Hadamard |
X | 1+ | fraction | Pauli X (alias for Shift) |
Y | 1+ | fraction | Pauli Y, dimension 2 only |
Z | 1+ | fraction | Pauli Z (alias for Clock) |
ISwap | exactly 2 | fraction | Entanglement gate |
Swap | exactly 2 | none | State exchange |
NCycle | exactly 2 | none | Entangling operation |
PhaseRotate | via predicates | Radians (0 to 2π) | Phase rotation on predicate-matched states |
MeasureProperties | 1+ | none | Collapse and fire Unity Events |
MeasurePredicates | via predicates | none | Projective predicate measurement |
Components that take exactly 2 targets log an error and do nothing when the count is wrong. PhaseRotate derives its target properties from its predicates, and assigning TargetProperties on it logs an error.
MeasureProperties events
UnityEvent OnMeasure // fires after measurement
QuantumPropertyEvent OnMeasureQuantumProperty // fires once per measured property
int[] LastResult { get; } // most recent outcomesMeasurePredicates events
UnityEvent OnMeasure // fires after measurement
MeasurePredicateEvent OnMeasurePredicates // fires with bool, true when the predicates held
int LastResult { get; } // 0 or 1MeasurePredicateEvent is UnityEvent<bool>.
Trackers
MonoBehaviours that monitor quantum state.
| Tracker | Read it with | Update it with | Use case |
|---|---|---|---|
ProbabilityTracker | Probabilities | GetBasisProbabilities() | Show probability distribution |
EntanglementTracker | LastUpdatedMutualInformation | UpdateMutualInformation() | Quantify entanglement (mutual information) |
CorrelationTracker | return of the update call | UpdateCorrelationMatrix() | Measurement correlations (Pearson coefficient) |
PhaseTracker | PhaseMatrix | UpdatePhaseMatrix() | Relative phases between states |
ReducedDensityMatrixTracker | ReducedDensityMatrix | GetReducedDensityMatrix() | Full density matrix |
Reading ProbabilityTracker.Probabilities recomputes the distribution, it does not hand back a cached array.
Inspector configuration
| Field | Type | Default | Description |
|---|---|---|---|
| Quantum Properties | QuantumProperty[] | empty | Properties to track |
| Continuous | bool | true | Update every frame |
| Radians (PhaseTracker only) | bool | true | Report phases in radians instead of degrees |
These fields are private and serialized, so you set them in the Inspector, not from code. When Continuous is off, call the tracker's update method yourself.
ProbabilityTracker, CorrelationTracker, PhaseTracker and ReducedDensityMatrixTracker fall back to the QuantumProperty on their own GameObject when the list is left empty, and log an error when there is none. EntanglementTracker has no such fallback: mutual information needs at least two properties, so assign them yourself. With fewer than two it logs an error and UpdateMutualInformation() returns null.
Trigger components
Components that turn Unity collider events into quantum activity.
QuantumPropertyTrigger
public class QuantumPropertyTrigger : MonoBehaviourPut it on a trigger collider. When a collider carrying a QuantumProperty enters, in 2D or 3D, it invokes its serialized On Trigger Enter event with that property. Wire it to anything that takes a QuantumProperty, such as a MeasureProperties action.
[Serializable]
public class QuantumPropertyEvent : UnityEvent<QuantumProperty> { }TriggerActionOnQuantumProperty
public class TriggerActionOnQuantumProperty : MonoBehaviourPut it on the same GameObject as an IQuantumAction. On trigger enter it looks up a QuantumProperty on the other collider, points the action's TargetProperties at it and calls apply(). No wiring needed, and nothing happens when the other object has no QuantumProperty.
Utility components
Optional components in QRG.QuantumForge.Utility, used by the samples. None of them are required to run quantum operations.
| Component | Inspector fields | What it does |
|---|---|---|
ScaleProbability | probability tracker, basis value, per-axis toggles | Scales the transform on the chosen axes by the probability of that basis value, every frame |
RotateOnPhase | phase tracker, phase matrix index, per-axis toggles | Rotates the transform on the chosen axes by the phase at that matrix entry, relative to the rotation it started with |
ButtonPressOnKey | button, key, optional label | Invokes a UI Button's onClick when the key is pressed, and flashes the pressed color. Fills the label with the key name |
Trigger | enter event | A plain collider-to-UnityEvent bridge, with no QuantumProperty involved |
Trigger and QuantumPropertyTrigger both show an exit event in the Inspector that nothing invokes. Only the enter event fires today.
Core layer
Direct C# access to the native plugin, without MonoBehaviours. Everything below is on the static class QRG.QuantumForge.Core.QuantumForge.
Fractions and angles are double here. The MonoBehaviour layer takes float and widens.
NativeQuantumProperty
var prop = new NativeQuantumProperty(dimension: 2); // starts in |0⟩
var prop = new NativeQuantumProperty(dimension: 2, initial: 1); // starts in |1⟩
int d = prop.Dimension;
prop.Dispose(); // releases the native handleBoth constructors start the property in |0⟩. The two-argument form then cycles and measures until the value reads back as initial, so it also ends up in a definite classical state, just not |0⟩. It throws when initial is outside [0, dimension).
Predicates are native handles, and they are IDisposable:
using (var pred = prop.is_value(1))
{
QuantumForge.Cycle(other, 0.5, pred);
}Failing to dispose leaks native memory on every gate call. is_not_value(int) is the negated form.
Gate methods
static void Cycle(NativeQuantumProperty prop, double fraction, params Predicate[] preds)
static void Cycle(NativeQuantumProperty prop, params Predicate[] preds)
static void Shift(NativeQuantumProperty prop, double fraction, params Predicate[] preds)
static void Shift(NativeQuantumProperty prop, params Predicate[] preds)
static void Clock(NativeQuantumProperty prop, double fraction, params Predicate[] preds)
static void Clock(NativeQuantumProperty prop, params Predicate[] preds)
static void X(NativeQuantumProperty prop, double fraction, params Predicate[] preds)
static void X(NativeQuantumProperty prop, params Predicate[] preds)
static void Y(NativeQuantumProperty prop, double fraction, params Predicate[] preds)
static void Y(NativeQuantumProperty prop, params Predicate[] preds)
static void Z(NativeQuantumProperty prop, double fraction, params Predicate[] preds)
static void Z(NativeQuantumProperty prop, params Predicate[] preds)
static void Hadamard(NativeQuantumProperty prop, params Predicate[] preds)
static void Hadamard(NativeQuantumProperty prop, double fraction, params Predicate[] preds)
static void InverseHadamard(NativeQuantumProperty prop, params Predicate[] preds)
static void Reset(NativeQuantumProperty prop, int currentValue)
static void PhaseRotate(double angle, params Predicate[] preds)
static void Swap(NativeQuantumProperty p1, NativeQuantumProperty p2, params Predicate[] preds)
static void ISwap(NativeQuantumProperty p1, NativeQuantumProperty p2, double fraction, params Predicate[] preds)
static void ISwap(NativeQuantumProperty p1, NativeQuantumProperty p2, params Predicate[] preds)Two entangling helpers build a controlled sequence in C# rather than in one native call, and both take an optional fraction:
static void NCycle(NativeQuantumProperty prop1, NativeQuantumProperty prop2, double fraction = 1.0)
static void NShift(NativeQuantumProperty prop1, NativeQuantumProperty prop2, double fraction = 1.0)NCycle applies cycles to prop2 controlled on the value of prop1, NShift applies shifts. Each one issues several predicated gate calls, so batching pays off when you use many of them.
Measurement
static int[] Measure(params NativeQuantumProperty[] props)
static int Measure(Predicate[] preds)
static int[] ForcedMeasure(int[] forcedValues, params NativeQuantumProperty[] props)ForcedMeasure collapses the state onto the outcomes you name instead of sampling, and returns the resulting values. forcedValues must be the same length as props, otherwise it throws ArgumentException. Use it for deterministic replays and tests, not for normal gameplay.
Analysis
static BasisProbability[] Probabilities(params NativeQuantumProperty[] props)
static Complex[,] ReducedDensityMatrix(params NativeQuantumProperty[] props)
static float[] MutualInformation(params NativeQuantumProperty[] props)
static float[,] CorrelationMatrix(params NativeQuantumProperty[] props)public readonly struct BasisProbability
{
public readonly float Probability;
public readonly int[] QuditValues; // raw indices, one per property
}This is the Core-layer BasisProbability, distinct from QuantumProperty.BasisProbability.
Batch operations
Each gate call crosses the managed to native boundary once. When you apply many gates back to back, ExecuteBatch sends the whole sequence in a single native call and that overhead mostly disappears.
static QForgeBatchResult ExecuteBatch(
params (QForgeOpCode op,
NativeQuantumProperty target,
NativeQuantumProperty target2,
double fraction,
double angle,
Predicate[] predicates)[] ops)
static QForgeBatchResult ExecuteBatch(params (QForgeOpCode op, NativeQuantumProperty target)[] ops)using static QRG.QuantumForge.Core.QuantumForge;
using (var pred = prop1.is_value(1))
{
var result = ExecuteBatch(
(QForgeOpCode.Hadamard, prop1, null, double.NaN, 0.0, null),
(QForgeOpCode.Cycle, prop2, null, 0.5, 0.0, new[] { pred }),
(QForgeOpCode.ISwap, prop1, prop2, 1.0, 0.0, null));
if (result.errorCode != QForgeResult.QFORGE_SUCCESS)
{
Debug.LogError($"batch stopped after {(int)result.opsExecuted} ops: {result.errorMessage}");
}
}The second overload is the shorthand for gates with no predicates and no fraction. It passes double.NaN for every fraction.
| Tuple field | Meaning |
|---|---|
op | Which gate, from QForgeOpCode |
target | Primary property. Pass null for PhaseRotate |
target2 | Second property, for Swap and ISwap. null otherwise |
fraction | Gate fraction. double.NaN selects the non-fractional variant |
angle | Radians, PhaseRotate only |
predicates | Predicate handles, or null |
public enum QForgeOpCode
{
Cycle, Shift, Clock, X, Z, Y, Hadamard, InverseHadamard, Swap, ISwap, PhaseRotate
}
public struct QForgeBatchResult
{
public UIntPtr opsExecuted; // how many ops ran before stopping
public QForgeResult errorCode; // QFORGE_SUCCESS when the whole batch ran
public string errorMessage;
}Fractional versus non-fractional
double.NaN calls the discrete permutation, for example Cycle advancing the basis state by one. 1.0 calls the continuous rotation at fraction 1.0, which is a different operation. This matches the individual gate calls, where Cycle(prop) and Cycle(prop, 1.0) differ.
Operations run in order and stop at the first error. Gates that already ran are not rolled back, so read opsExecuted to find out how far the batch got. Measurement is not a batch op. Call Measure after the batch finishes.
QForgeBatchOp is the sequential struct that ExecuteBatch marshals into. It holds raw handles and pointers, and you should not need to build one by hand.
ExecuteBatch is not callable with the 1.4.0 plugins
The native plugins committed for 1.4.0 predate qforge_execute_batch, so calling ExecuteBatch throws EntryPointNotFoundException on every platform. It works once the plugins are rebuilt. Everything else on this page resolves against the shipped macOS plugin.
Library lifecycle and diagnostics
static void Initialize() // throws when the native library fails to initialize
static void Cleanup() // logs an error on failure, does not throw
static string GetVersion() // native library version string
static (int major, int minor, int patch) GetVersionInfo()
static int GetMaxDimension() // compiled-in dimension cap, 3 in shipped builds
static int GetMaxQudits() // compiled-in property cap, 12 in shipped builds
static bool IsValidDimension(int dimension)
static string GetErrorString(QForgeResult result)
public delegate void ErrorCallback(QForgeErrorInfo errorInfo, IntPtr userData);
static void SetErrorCallback(ErrorCallback callback, IntPtr userData)
static void SetErrorCallback(ErrorCallback callback)Initialize is a no-op in the current C API. It returns success as soon as the plugin loads, which makes it a cheap startup probe: call it once and you find out the native library resolves before the first gate does. Cleanup clears the registered error callback. Neither is required for ordinary use.
Keep a managed reference to any delegate you hand to SetErrorCallback for as long as the native side may call it. If the garbage collector takes it, the native callback crashes.
Error codes
public enum QForgeResult
{
QFORGE_SUCCESS = 0,
// Parameter errors
QFORGE_ERROR_NULL_POINTER = 1,
QFORGE_ERROR_INVALID_ARGUMENT = 2,
QFORGE_ERROR_BUFFER_TOO_SMALL = 3,
QFORGE_ERROR_INVALID_DIMENSION = 4,
QFORGE_ERROR_INVALID_QUDIT_NUMBER = 5,
// Operation errors
QFORGE_ERROR_TARGET_CONTROL_OVERLAP = 100,
QFORGE_ERROR_INCOMPATIBLE_DIMENSIONS = 101,
QFORGE_ERROR_STATE_SIZE_EXCEEDED = 102,
// Memory errors
QFORGE_ERROR_OUT_OF_MEMORY = 200,
// Internal errors
QFORGE_ERROR_INTERNAL = 900
}Most Core methods do not hand you a QForgeResult. They throw InvalidOperationException with the code and the native message in the text. ExecuteBatch is the exception: it reports the code in QForgeBatchResult.errorCode.
Failures also arrive as a QForgeErrorInfo, the struct passed to an ErrorCallback:
public struct QForgeErrorInfo
{
public QForgeResult code;
public string message;
public IntPtr function; // const char*, marshal with Marshal.PtrToStringAnsi
public int line;
}