Operator Usage Examples

This tutorial demonstrates how to use various operators.

[ ]:
import pysparq as ps
import numpy as np

Arithmetic Operators

[ ]:
ps.System.clear()

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()
ps.Init_Unsafe("a", 7)(state)
ps.Init_Unsafe("b", 10)(state)

Addition

[ ]:
# Out-of-place addition: result ^= a + b
ps.Add_UInt_UInt("a", "b", "result")(state)
print("Add_UInt_UInt:")
ps.pprint(state)
[ ]:
# In-place addition: b += a
ps.System.clear()
ps.System.add_register("a", ps.UnsignedInteger, 4)
ps.System.add_register("b", ps.UnsignedInteger, 4)
state = ps.SparseState()
ps.Init_Unsafe("a", 7)(state)
ps.Init_Unsafe("b", 10)(state)

op = ps.Add_UInt_UInt_InPlace("a", "b")
op(state)
print("Add_UInt_UInt_InPlace:")
ps.pprint(state)
# b = (10 + 7) % 16 = 1
[ ]:
# Undo
op.dag(state)
print("After dagger:")
ps.pprint(state)

Multiplication

[ ]:
ps.System.clear()
ps.System.add_register("x", ps.UnsignedInteger, 4)
ps.System.add_register("triple", ps.UnsignedInteger, 4)

state = ps.SparseState()
ps.Init_Unsafe("x", 5)(state)

# Multiply by an odd constant (guarantees bijectivity)
ps.Mult_UInt_ConstUInt("x", 3, "triple")(state)
print("Mult_UInt_ConstUInt(x, 3):")
ps.pprint(state)

Comparison and Flags

[ ]:
ps.System.clear()
ps.System.add_register("a", ps.UnsignedInteger, 4)
ps.System.add_register("b", ps.UnsignedInteger, 4)
ps.System.add_register("less", ps.Boolean, 1)
ps.System.add_register("equal", ps.Boolean, 1)

state = ps.SparseState()
ps.Init_Unsafe("a", 3)(state)
ps.Init_Unsafe("b", 5)(state)

ps.Compare_UInt_UInt("a", "b", "less", "equal")(state)
print("Compare_UInt_UInt:")
ps.pprint(state)

Quantum Gates

[ ]:
ps.System.clear()
ps.System.add_register("q", ps.Boolean, 1)

state = ps.SparseState()
print("Initial:")
ps.pprint(state)
[ ]:
# X gate (NOT)
ps.X_Bool("q", 0)(state)
print("After X gate:")
ps.pprint(state)
[ ]:
# Hadamard
ps.Hadamard_Bool("q")(state)
print("After Hadamard:")
ps.pprint(state)
[ ]:
# Phase gate
ps.Phase_Bool("q", 0, np.pi/4)(state)
print("After Phase(π/4):")
ps.pprint(state)

QFT

[ ]:
ps.System.clear()
ps.System.add_register("q", ps.UnsignedInteger, 3)

state = ps.SparseState()
ps.Init_Unsafe("q", 1)(state)

print("Initial:")
ps.pprint(state)
[ ]:
# QFT
ps.QFT("q")(state)
print("After QFT:")
ps.pprint(state)
[ ]:
# InverseQFT
ps.InverseQFT("q")(state)
print("After InverseQFT:")
ps.pprint(state)

Conditional Operations

[ ]:
ps.System.clear()
ps.System.add_register("x", ps.UnsignedInteger, 2)
ps.System.add_register("result", ps.UnsignedInteger, 2)
ps.System.add_register("ctrl", ps.Boolean, 1)

state = ps.SparseState()
ps.Init_Unsafe("x", 3)(state)
ps.Init_Unsafe("ctrl", 1)(state)

# Perform the addition when ctrl = 1
ps.Add_UInt_ConstUInt("x", 5, "result").conditioned_by_nonzeros("ctrl")(state)

print("Conditional addition (ctrl=1):")
ps.pprint(state)
[ ]:
# The condition does not trigger when ctrl = 0
ps.System.clear()
ps.System.add_register("x", ps.UnsignedInteger, 2)
ps.System.add_register("result", ps.UnsignedInteger, 2)
ps.System.add_register("ctrl", ps.Boolean, 1)

state = ps.SparseState()
ps.Init_Unsafe("x", 3)(state)
ps.Init_Unsafe("ctrl", 0)(state)  # ctrl = 0

ps.Add_UInt_ConstUInt("x", 5, "result").conditioned_by_nonzeros("ctrl")(state)

print("Conditional addition (ctrl=0):")
ps.pprint(state)
# result stays 0

Block Encoding: Tridiagonal Matrix

Block Encoding embeds a classical matrix into a unitary and is central to quantum linear-algebra algorithms. The example below uses BlockEncodingTridiagonal from pysparq.algorithms.block_encoding to block-encode the tridiagonal matrix :math:A = \alpha I + \beta T.

[ ]:
from pysparq.algorithms.block_encoding import (
    get_tridiagonal_matrix,
    BlockEncodingTridiagonal,
)

alpha, beta, dim = 0.5, 0.3, 4
A = get_tridiagonal_matrix(alpha, beta, dim)
print(f"Tridiagonal matrix A:\n{A}")

# Build the Block Encoding circuit
ps.System.clear()
ps.System.add_register("main_reg", ps.UnsignedInteger, 2)  # dim = 2^2
ps.System.add_register("anc_UA", ps.UnsignedInteger, 4)

state = ps.SparseState()
ps.Init_Unsafe("main_reg", 0)(state)
ps.Init_Unsafe("anc_UA", 0)(state)

block_enc = BlockEncodingTridiagonal("main_reg", "anc_UA", alpha, beta)
block_enc(state)
print(f"Block Encoding applied successfully, number of basis states: {state.size()}")

block_enc.dag(state)  # release the ancilla registers
print(f"After inverse Block Encoding, number of basis states: {state.size()}")

Custom Operators on the Python Side

When you need to combine several existing operators, wrap them directly on the Python side:

[ ]:
class MyIncrement:
    """Add 1 to the register value (out-of-place result register)."""

    def __init__(self, src: str, dst: str):
        self.src = src
        self.dst = dst

    def __call__(self, state: ps.SparseState):
        ps.Add_UInt_UInt(self.src, self.dst)(state)

    def dag(self, state: ps.SparseState):
        ps.Add_UInt_UInt(self.src, self.dst)(state)  # XOR semantics: applying it twice restores the value


ps.System.clear()
ps.System.add_register("counter", ps.UnsignedInteger, 4)
ps.System.add_register("result", ps.UnsignedInteger, 4)
state = ps.SparseState()
ps.Init_Unsafe("counter", 5)(state)
ps.Init_Unsafe("result", 0)(state)

my_inc = MyIncrement("counter", "result")
my_inc(state)          # result = 0 + 5 = 5
print(f"After addition, result = {state.basis_states[0].get(ps.System.get_id('result')).value}")
my_inc.dag(state)      # result = 5 + 5 = 0, restored
print(f"After dagger, result = {state.basis_states[0].get(ps.System.get_id('result')).value}")

If you need new primitives (beyond what the existing operators can express), use compile_operator to compile C++ code into a dynamically linked library:

[ ]:
from pysparq.dynamic_operator import compile_operator

cpp_code = '''
class FlipOp : public SelfAdjointOperator {
    size_t reg_id;
public:
    FlipOp(size_t r) : reg_id(r) {}
    void operator()(std::vector<System>& state) const override {
        for (auto& s : state) {
            s.get(reg_id).value ^= 1;
        }
    }
};
'''

FlipOp = compile_operator(
    name="FlipOp",
    cpp_code=cpp_code,
    base_class="SelfAdjointOperator",
    constructor_args=[("size_t", "reg_id")],
)

ps.System.clear()
ps.System.add_register("q", ps.UnsignedInteger, 4)
state = ps.SparseState()
ps.Init_Unsafe("q", 1)(state)
print("Initial:", state.basis_states[0].get(ps.System.get_id("q")).value)
flip = FlipOp(reg_id=0)
flip(state)   # flip bit 0
print("After flip:", state.basis_states[0].get(ps.System.get_id("q")).value)

Summary

This tutorial demonstrated:

  • Arithmetic operators: Add, Mult, Compare

  • Quantum gates: X, Hadamard, Phase

  • QFT / InverseQFT

  • Conditional operations

  • Block Encoding: block-encode a tridiagonal matrix with BlockEncodingTridiagonal

  • Custom operators: compose existing operators on the Python side, or compile C++ code via compile_operator