- Demos/
- Quantum Computing/
How to use Qualtran with PennyLane
How to use Qualtran with PennyLane
Published: August 17, 2025. Last updated: August 17, 2025.
Get ready to expand your quantum programming toolkit! PennyLane and Qualtran integrate their best features, enabling you to build quantum circuits with a mix of Qualtran Bloqs and PennyLane operations. You can then analyze or simulate the circuits with tools from both libraries. For those unfamiliar, Qualtran provides a set of abstractions for representing quantum programs, a library of quantum algorithms called Bloqs, and a set of tools for visualizing and analyzing these programs.

In this demo, we show you how easy it is to use PennyLane and Qualtran together. This integration allows you to:
- Simulate Qualtran Bloqs: Convert Qualtran programs to PennyLane operations and simulate to confirm
their outputs or try new algorithms in PennyLane circuits with the many components in the Qualtran Bloqs library.
- Analyze PennyLane circuits with Qualtran: Leverage Qualtran’s advanced analysis tools like
We’ll start by focusing on the first key capability: simulating Qualtran Bloqs
using
PennyLane QNodes
.
Simulating Qualtran Bloqs
With barely any work, you can drop Qualtran Bloqs into executable PennyLane circuits!
You only need one class: FromBloq
which wraps a Qualtran Bloq
as a
PennyLane operation. It faithfully converts any Bloq into an
Operation
containing the usual methods and attributes such as
decomposition()
and
matrix()
.
Let’s see how it works! In the following example, we wrap a Qualtran XGate
instance using
FromBloq
. Qualtran Bloqs don’t always apply to specific wires and
may not have wires defined. But PennyLane operations do apply to specific wires, so we need to
provide qubit information to FromBloq
via the wires
argument.
import pennylane as qml
from qualtran.bloqs.basic_gates import XGate
bloq_as_op = qml.FromBloq(XGate(), wires=0)
print(bloq_as_op)
FromBloq(XGate, wires=Wires([0]))
We can see that the output is a FromBloq
instance, whose properties are
the same as PennyLane’s PauliX
operator.
When wire requirements are complicated, you can use the bloq_registers()
helper function to generate the input values for wires
:
print(qml.bloq_registers(XGate()))
{'q': Wires([0])}
This will create register names in accordance to the Bloq’s signature. Here, the function created
one 'q'
register with a single qubit as required by XGate
. You will see an additional
example in the next section.
Expanding PennyLane circuits with Qualtran Bloqs
Let’s look at a more complicated example! Qualtran has a special type of addition operation, known
as Galois Field Addition or GF2Addition,
that is not implemented in PennyLane. You can think of it
as a binary addition that doesn’t allow carrying. Let’s use the combined force of PennyLane and
Qualtran to bring GF2Addition
to life!
We will use it below to calculate the result of \(5 + 10\) in binary.
from qualtran.bloqs.gf_arithmetic import GF2Addition
arithmetic_bloq = GF2Addition(4)
wires = qml.bloq_registers(arithmetic_bloq) # This gives us 2 registers, 'x' and 'y'
print(wires)
{'x': Wires([0, 1, 2, 3]), 'y': Wires([4, 5, 6, 7])}
five = [0, 1, 0, 1] # 5 in binary
ten = [1, 0, 1, 0] # 10 in binary
@qml.qnode(qml.device('default.qubit', shots=1))
def circuit():
# Prepare the input registers for 5 and 10
qml.BasisState(five + ten, wires=wires['x']+wires['y'])
# Sum the two registers
qml.FromBloq(arithmetic_bloq, wires=wires['x']+wires['y'])
# Measure the output binary string
a = [qml.measure(i) for i in range(len(wires['x']+wires['y']))]
return qml.sample(a)
# Simulate the circuit and process binary output to integer
binary_string = "".join([str(bit) for bit in circuit()])
print("GF2Addition of 5 + 10 =", int(binary_string[len(wires['x']):],2))
GF2Addition of 5 + 10 = 15
Wow! We used a Qualtran Bloq as a PennyLane template without any additional work. With Qualtran’s expansive library of quantum algorithms, you can now build a greater variety of circuits using a combination of PennyLane templates and Qualtran Bloqs.
Analyzing PennyLane Circuits with Qualtran
Now, we’ll show you how to convert PennyLane objects to Qualtran Bloqs using the
to_bloq()
function
and how to analyze them with Qualtran tools
like drawing call graphs,
counting qubits,
and counting gates.
For brevity, we’ll
mainly cover how operators get converted to Bloqs in our examples, but functions of operators and
QNodes
work just the same.
There are three main options for the conversion. We’ll briefly introduce them here but go into greater detail in subsequent sections:
- Smart defaults: In this option, PennyLane chooses what Qualtran Bloq to translate to. If an option exists,
we’ll give you a Qualtran Bloq that is highly similar to the PennyLane operator.
- Custom mapping: Want something different from the smart default? Don’t worry, you can customize
what Bloq you want your operator to map to. This makes it easy to refine the finer details of your algorithm.
These options are all accessible through the to_bloq()
function. In the
following sections, we’ll explore how we can wield this powerful function to get all the
functionality introduced above.
Smart defaults
By default, qml.to_bloq
tries its best to translate PennyLane objects to Qualtran-native
objects. This makes certain Qualtran functionalities, such as gate counting and
generalizers,
work more seamlessly. In the following example, PennyLane’s PauliX
operator is
mapped directly to Qualtran’s XGate
.
op_as_bloq = qml.to_bloq(qml.X(0))
print(op_as_bloq)
XGate
Note that not all PennyLane operators are as straightforward to map as the
PauliX
operator. For example, PennyLane’s
QuantumPhaseEstimation
could be mapped to a variety of Qualtran quantum phase
estimation (QPE) Bloqs, such as
TextbookQPE or
QubitizationQPE.
In cases where the mapping is ambiguous, we get the smart default:
unitary = qml.RY(phi=0.3, wires=[0])
estimation_wires = [1, 2, 3]
op = qml.QuantumPhaseEstimation(unitary=unitary, estimation_wires=estimation_wires)
qpe_bloq = qml.to_bloq(op)
print(qpe_bloq)
TextbookQPE
We can use Qualtran’s
show_call_graph
to investigate how QuantumPhaseEstimation
is mapped. This tool lets you visualize the
full stack of a quantum circuit and analyze what causes specific algorithms and gates to be called
and how often.
from qualtran.drawing import show_call_graph
show_call_graph(qpe_bloq, max_depth=1)
Here, the smart default is to call a Qualtran TextbookQPE that uses RectangularWindowState to prepare a state. But what if we wanted to use a different Bloq for our state preparation? In this case, we turn to custom mappings.
Custom mapping
To use another state preparation routine in our QPE workflow, e.g., LPResourceState rather than RectangularWindowState, we can override the smart default with a custom map. As shown below, custom maps are defined with a dictionary where keys are the original operations and values are the new operations we want to map those to.
from qualtran.bloqs.phase_estimation import LPResourceState
from qualtran.bloqs.phase_estimation.text_book_qpe import TextbookQPE
custom_map = {
op: TextbookQPE(
unitary=qml.to_bloq(qml.RY(phi=0.3, wires=[0])),
ctrl_state_prep=LPResourceState(3)
)
}
qpe_bloq = qml.to_bloq(op, custom_mapping=custom_map)
print(qpe_bloq)
TextbookQPE
Below, we see that LPResourceState
has replaced RectangularWindowState
as defined in the
custom map:
show_call_graph(qpe_bloq, max_depth=1)
Wrapping
When a PennyLane object does not have a mapping – a direct Qualtran equivalent
or smart default – the circuit is wrapped as a ToBloq
object.
def circ():
qml.X(0)
qml.X(1)
qfunc_as_bloq = qml.to_bloq(circ)
print(type(qfunc_as_bloq))
<class 'pennylane.io.qualtran_io.ToBloq'>
Wrapping a PennyLane quantum function or operator as a ToBloq
is similar to wrapping a
Qualtran Bloq as a FromBloq
. A wrapped PennyLane object acts like a Bloq: it can be analyzed
using the language of Qualtran to simulate algorithms, estimate resource requirements, draw
diagrams, and more.
We can choose to wrap our PennyLane QuantumPhaseEstimation
simply by setting
map_ops
to False
. This wraps the operators as a ToBloq
object and keeps the original
PennyLane object information.
unitary = qml.RY(phi=0.3, wires=[0])
estimation_wires = [1, 2, 3]
op = qml.QuantumPhaseEstimation(unitary=unitary, estimation_wires=estimation_wires)
wrapped_qpe_bloq = qml.to_bloq(op, map_ops=False)
print(wrapped_qpe_bloq)
PLQuantumPhaseEstimation
The call graph below shows how the wrapped version of QuantumPhaseEstimation
preserves the PennyLane definition of the operator:
show_call_graph(wrapped_qpe_bloq, max_depth=1)
Let’s see how mapping and wrapping affect our resource count estimates.
_, mapped_sigma = qpe_bloq.call_graph()
_, wrapped_sigma = wrapped_qpe_bloq.call_graph()
print("--- Mapped counts ---")
for gate, count in mapped_sigma.items():
print(f"{gate}: {count}")
print("\n--- Wrapped counts ---")
for gate, count in wrapped_sigma.items():
print(f"{gate}: {count}")
--- Mapped counts ---
Rz(-0.6981317007977318): 2
H: 18
CNOT: 18
Z**-0.1111111111111111: 3
Z**0.1111111111111111: 3
Z**-0.2222222222222222: 3
Z**0.2222222222222222: 3
Z**0.4444444444444444: 3
Z**-0.4444444444444444: 3
Rz(0.6981317007977318): 1
Ry(1.4594553124539327): 2
Ry(-1.4594553124539327): 1
I: 2
I: 1
CZ: 2
And: 7
XGate: 24
And†: 7
GPhase((6.123233995736766e-17+1j)): 1
C[Ry(0.3)]: 7
Z**-0.5: 1
Z**-0.5: 1
Z**-0.25: 1
TwoBitSwap: 1
--- Wrapped counts ---
H: 6
C[Ry(0.3)]: 7
Z**0.15915494309189535: 3
CNOT: 6
Z**-0.15915494309189535: 3
Z**0.15915494309189535: 3
TwoBitSwap: 1
Here, we can clearly see that the resource counts for the two methods are distinctly different. This is because the underlying implementations for the two QPE operators differ.
When Qualtran computes the resource counts for a ToBloq
, it first checks if there is a call
graph defined. If it is defined, Qualtran uses that call graph to compute the resource count
estimates. If it is not defined, Qualtran uses the PennyLane decomposition to compute the resource
count estimates.
Since computing the PennyLane decompositions is expensive, many PennyLane templates such as QPE
have call graphs defined when wrapped as a ToBloq
object. By defining these call graphs,
you can now efficiently compute resource count estimates for circuits that may require thousands
of qubits and trillions of gates.
There is one caveat to note: for performance reasons, the call graphs sometimes differ from the actual decomposition. This means you may get more optimal, but still accurate, counts than what the original PennyLane decomposition might have prescribed. As we continue to develop both the PennyLane-Qualtran integration and PennyLane itself, these call graphs will evolve as well.
Conclusion
In this demo, we took a look at how to use PennyLane with Qualtran. We encourage you to play
around with the integration and try swapping out PennyLane operators with Qualtran Bloqs and
running it on a real quantum device! Or convert an existing PennyLane workflow into Qualtran and
use Qualtran tools to analyze the circuit. If you’d like to see more examples of Qualtran and
PennyLane or would like to know further technical details, be sure to check out the docs for
to_bloq()
and FromBloq
!
About the author
Austin Huang
Pennylane Algorithms Team
Total running time of the script: (0 minutes 1.378 seconds)