Operator Reference

Operators are the building blocks of quantum operations in PySparQ. All operations are implemented as operator objects that take a SparseState and transform it.

What Is an Operator?

An operator is a callable object that transforms a SparseState, implementing quantum operations while guaranteeing unitarity.

Basic Usage

import pysparq as ps

ps.System.clear()

# Create registers
ps.System.add_register("a", ps.UnsignedInteger, 4)
ps.System.add_register("b", ps.UnsignedInteger, 4)
ps.System.add_register("result", ps.UnsignedInteger, 4)

state = ps.SparseState()

# Initialize the inputs
ps.Init_Unsafe("a", 3)(state)
ps.Init_Unsafe("b", 5)(state)

# Create and apply an operator
add_op = ps.Add_UInt_UInt("a", "b", "result")
add_op(state)  # apply the operator

# For a non-self-adjoint operator, use dag() to undo the operation
add_op.dag(state)  # undo (restore the original state)

Operator Properties

Unitarity

All quantum operators must satisfy the unitarity condition:

\[U^\dagger U = I\]

PySparQ guarantees unitarity through two mechanisms:

Unitarity mechanisms

Type

Mechanism

Example

Out-of-place

XOR write: result ^= f(inputs)

Add_UInt_UInt, Mult_UInt_ConstUInt

In-place

Explicit dagger implementation

Add_UInt_UInt_InPlace, ShiftLeft_InPlace

SelfAdjointOperator vs BaseOperator

Operator base classes

Base class

Characteristics

dag() behavior

Typical operators

SelfAdjointOperator

\(U^\dagger = U\)

dag() is equivalent to operator()

Add_UInt_UInt, X_Bool

BaseOperator

General unitary operator

Requires an explicit dag() implementation

Add_UInt_UInt_InPlace, ShiftLeft_InPlace

# SelfAdjointOperator: applying twice restores the original state
op = ps.Add_UInt_UInt("a", "b", "result")
op(state)  # apply
op(state)  # apply again = undo (because XOR is self-inverse)

# BaseOperator: dag() is required to undo
op = ps.ShiftLeft_InPlace("reg", 2)
op(state)      # shift left by 2 bits
op.dag(state)  # shift right by 2 bits (undo)

Type Constraints

Operators impose strict requirements on register types:

Register types

Type

Description

Valid range

UnsignedInteger

Unsigned integer

\([0, 2^n-1]\)

SignedInteger

Signed integer (two’s complement)

\([-2^{n-1}, 2^{n-1}-1]\)

Boolean

Single qubit

{0, 1}

Rational

Fixed-point fraction

\([0, 1)\)

General

Raw bit storage

Arbitrary bit patterns

# Correct: Boolean for single-qubit gates
ps.System.add_register("qubit", ps.Boolean, 1)
ps.X_Bool("qubit", 0)(state)

# Wrong: type mismatch
# ps.System.add_register("counter", ps.UnsignedInteger, 4)
# ps.X_Bool("counter", 0)(state)  # raises an exception!

Bit Constraints

Many operators verify:

  • Register sizes match the expected dimensions

  • Bit indices are within the register range

  • Output registers have sufficient capacity

Conditional Operations

All operators support conditional execution to implement controlled operations.

Condition Methods

Condition methods

Method

Condition

Example

conditioned_by_nonzeros(reg)

Register value ≠ 0

Controlled by any nonzero state

conditioned_by_all_ones(reg)

All bits are 1

Multi-qubit control

conditioned_by_bit(reg, pos)

The specified bit is 1

Single-qubit control

conditioned_by_value(reg, val)

The register equals a specific value

Classical control

op = ps.Add_UInt_UInt("a", "b", "result")

# Apply when the control register is nonzero
op.conditioned_by_nonzeros("control")(state)

# Apply when bit 0 of flag is 1
op.conditioned_by_bit("flag", 0)(state)

# Apply when mode equals 1
op.conditioned_by_value("mode", 1)(state)

# Multiple conditions
op.conditioned_by_nonzeros(["ctrl1", "ctrl2"])(state)

Clearing Control Conditions

op.clear_control_nonzeros()
op.clear_control_by_bit()
op.clear_control_by_value()
op.clear_control_all_ones()

Inspecting Control Variables

# Get the current control variables
print(op.condition_variable_nonzeros)
print(op.condition_variable_by_bit)  # list[tuple[int, int]]
print(op.condition_variable_by_value)  # list[tuple[int, int]]

API Reference

Operator Categories in Detail