Transpilation
Contains functions that operate on blocks and circuits to transpile
them to new blocks/circuits.
transpile(*fs)
AbstractBlock
or QuantumCircuit
transpilation. Compose functions that
accept a circuit/block and returns a circuit/block.
PARAMETER | DESCRIPTION |
---|---|
*fs |
composable functions that either map blocks to blocks
(
TYPE:
|
RETURNS | DESCRIPTION |
---|---|
Callable
|
Composed function. |
Examples:
Flatten a block of nested chains and krons:
from qadence import *
from qadence.transpile import transpile, flatten, scale_primitive_blocks_only
b = chain(2 * chain(chain(X(0), Y(0))), kron(kron(X(0), X(1))))
print(b)
# both flatten and scale_primitive_blocks_only are functions that accept and
# return a block
t = transpile(flatten, scale_primitive_blocks_only)(b)
print(t)
We also proved a decorator to easily turn a function Callable[[AbstractBlock], AbstractBlock]
into a Callable[[QuantumCircuit], QuantumCircuit]
to be used in circuit transpilation.
from qadence import *
from qadence.transpile import transpile, blockfn_to_circfn, flatten
# We want to pass this circuit to `transpile` instead of a block,
# so we need functions that map from a circuit to a circuit.
circ = QuantumCircuit(2, chain(chain(X(0), chain(X(1)))))
@blockfn_to_circfn
def fn(block):
# un-decorated function accepts a block and returns a block
return block * block
transp = transpile(
# the decorated function accepts a circuit and returns a circuit
fn,
# already existing functions can also be decorated
blockfn_to_circfn(flatten)
)
print(transp(circ))
Source code in qadence/transpile/transpile.py
chain_single_qubit_ops(block)
Transpile a chain of krons into a kron of chains of single qubit operations.
Examples:
from qadence import hea
from qadence.transpile.block import chain_single_qubit_ops
# Consider a single HEA layer
block = hea(2,1)
print(block)
# After applying chain_single_qubit_ops, we get:
print(chain_single_qubit_ops(block))
ChainBlock(0,1) [tag: HEA]
├── ChainBlock(0,1)
│ ├── KronBlock(0,1)
│ │ ├── RX(0) [params: ['theta_0']]
│ │ └── RX(1) [params: ['theta_1']]
│ ├── KronBlock(0,1)
│ │ ├── RY(0) [params: ['theta_2']]
│ │ └── RY(1) [params: ['theta_3']]
│ └── KronBlock(0,1)
│ ├── RX(0) [params: ['theta_4']]
│ └── RX(1) [params: ['theta_5']]
└── ChainBlock(0,1)
└── KronBlock(0,1)
└── CNOT(0,1)
ChainBlock(0,1)
├── KronBlock(0,1)
│ ├── ChainBlock(0)
│ │ ├── RX(0) [params: ['theta_0']]
│ │ ├── RY(0) [params: ['theta_2']]
│ │ └── RX(0) [params: ['theta_4']]
│ └── ChainBlock(1)
│ ├── RX(1) [params: ['theta_1']]
│ ├── RY(1) [params: ['theta_3']]
│ └── RX(1) [params: ['theta_5']]
└── ChainBlock(0,1)
└── KronBlock(0,1)
└── CNOT(0,1)
Source code in qadence/transpile/block.py
flatten(block, types=[ChainBlock, KronBlock, AddBlock])
Flattens the given types of CompositeBlock
s if possible.
Example:
from qadence import chain, kron, X
from qadence.transpile import flatten
from qadence.blocks import ChainBlock, KronBlock, AddBlock
x = chain(chain(chain(X(0))), kron(kron(X(0))))
# flatten only `ChainBlock`s
assert flatten(x, [ChainBlock]) == chain(X(0), kron(kron(X(0))))
# flatten `ChainBlock`s and `KronBlock`s
assert flatten(x, [ChainBlock, KronBlock]) == chain(X(0), kron(X(0)))
# flatten `AddBlock`s (does nothing in this case)
assert flatten(x, [AddBlock]) == x
Source code in qadence/transpile/block.py
scale_primitive_blocks_only(block, scale=None)
When given a scaled CompositeBlock consisting of several PrimitiveBlocks, move the scale all the way down into the leaves of the block tree.
PARAMETER | DESCRIPTION |
---|---|
block |
The block to be transpiled.
TYPE:
|
scale |
An optional scale parameter. Only to be used for recursive calls internally.
TYPE:
|
RETURNS | DESCRIPTION |
---|---|
AbstractBlock
|
A block of the same type where the scales have been moved into the subblocks.
TYPE:
|
Examples:
There are two different cases:
ChainBlock
s/KronBlock
s: Only the first subblock needs to be scaled because chains/krons
represent multiplications.
from qadence import chain, X, RX
from qadence.transpile import scale_primitive_blocks_only
b = 2 * chain(X(0), RX(0, "theta"))
print(b)
# After applying scale_primitive_blocks_only
print(scale_primitive_blocks_only(b))
[mul: 2]
└── ChainBlock(0)
├── X(0)
└── RX(0) [params: ['theta']]
ChainBlock(0)
├── [mul: 2.00000000000000]
│ └── X(0)
└── RX(0) [params: ['theta']]
AddBlock
s: Consider 2 * add(X(0), RX(0, "theta")). The scale needs to be added to all
subblocks. We get add(2 * X(0), 2 * RX(0, "theta")).
from qadence import add, X, RX
from qadence.transpile import scale_primitive_blocks_only
b = 2 * add(X(0), RX(0, "theta"))
print(b)
# After applying scale_primitive_blocks_only
print(scale_primitive_blocks_only(b))
[mul: 2]
└── AddBlock(0)
├── X(0)
└── RX(0) [params: ['theta']]
AddBlock(0)
├── [mul: 2.00000000000000]
│ └── X(0)
└── [mul: 2.00000000000000]
└── RX(0) [params: ['theta']]
Source code in qadence/transpile/block.py
set_trainable(blocks, value=True, inplace=True)
Set the trainability of all parameters in a block to a given value
PARAMETER | DESCRIPTION |
---|---|
blocks |
Block or list of blocks for which to set the trainable attribute
TYPE:
|
value |
The value of the trainable attribute to assign to the input blocks
TYPE:
|
inplace |
Whether to modify the block(s) in place or not. Currently, only
TYPE:
|
RAISES | DESCRIPTION |
---|---|
NotImplementedError
|
if the |
RETURNS | DESCRIPTION |
---|---|
AbstractBlock | list[AbstractBlock]
|
AbstractBlock | list[AbstractBlock]: the input block or list of blocks with the trainable attribute set to the given value |
Source code in qadence/transpile/block.py
validate(block)
Moves a block from global to local qubit numbers by adding PutBlocks and reassigning qubit locations approriately.
Example
from qadence.blocks import chain
from qadence.operations import X
from qadence.transpile import validate
x = chain(chain(X(0)), chain(X(1)))
print(x)
print(validate(x))
ChainBlock(0,1)
├── ChainBlock(0)
│ └── X(0)
└── ChainBlock(1)
└── X(1)
ChainBlock(0,1)
├── put on (0)
│ └── ChainBlock(0)
│ └── put on (0)
│ └── X(0)
└── put on (1)
└── ChainBlock(0)
└── put on (0)
└── X(0)
Source code in qadence/transpile/block.py
add_interaction(x, *args, interaction=Interaction.NN, spacing=1.0)
Turns blocks or circuits into (a chain of) HamEvo
blocks including a
chosen interaction term.
This is a @singledipatch
ed function which can be called in three ways:
- With a
QuantumCircuit
which contains all necessary information:add_interaction(circuit)
- With a
Register
and anAbstractBlock
:add_interaction(reg, block)
- With an
AbstractBlock
only:add_interaction(block)
See the section about analog blocks for detailed information about how which types of blocks are translated.
PARAMETER | DESCRIPTION |
---|---|
x |
Circuit or block to be emulated. See the examples on which argument combinations are accepted.
TYPE:
|
interaction |
Type of interaction that is added. Can also be a function that accepts a register and a list of edges that define which qubits interact (see the examples).
TYPE:
|
spacing |
All qubit coordinates are multiplied by
TYPE:
|
Examples:
from qadence import QuantumCircuit, AnalogRX, add_interaction
c = QuantumCircuit(2, AnalogRX(2.0))
e = add_interaction(c)
[mul: 0.0]
└── AddBlock(0,1)
├── AddBlock(0,1)
│ └── AddBlock(0,1)
│ ├── [mul: 1.571]
│ │ └── AddBlock(0,1)
│ │ ├── AddBlock(0)
│ │ │ ├── [mul: 1.00000000000000]
│ │ │ │ └── X(0)
│ │ │ └── [mul: 0.0]
│ │ │ └── Y(0)
│ │ └── AddBlock(1)
│ │ ├── [mul: 1.00000000000000]
│ │ │ └── X(1)
│ │ └── [mul: 0.0]
│ │ └── Y(1)
│ └── [mul: 0.0]
│ └── AddBlock(0,1)
│ ├── N(0)
│ └── N(1)
└── AddBlock(0,1)
└── [mul: 865723.020]
└── KronBlock(0,1)
├── N(0)
└── N(1)
add_interaction
directly on a block, but you have to provide either
the Register
or define a non-global qubit support.
from qadence import AnalogRX, Register, add_interaction
b = AnalogRX(2.0)
r = Register(1)
e = add_interaction(r, b)
# or provide only the block with local qubit support
# in this case the register is created via `Register(b.n_qubits)`
e = add_interaction(AnalogRX(2.0, qubit_support=(0,)))
print(e.generator)
[mul: 0.450]
└── AddBlock(0)
└── AddBlock(0)
├── [mul: 1.571]
│ └── AddBlock(0)
│ └── AddBlock(0)
│ ├── [mul: 1.00000000000000]
│ │ └── X(0)
│ └── [mul: 0.0]
│ └── Y(0)
└── [mul: 0.0]
└── AddBlock(0)
└── N(0)
[mul: 0.450]
└── AddBlock(0)
└── AddBlock(0)
├── [mul: 1.571]
│ └── AddBlock(0)
│ └── AddBlock(0)
│ ├── [mul: 1.00000000000000]
│ │ └── X(0)
│ └── [mul: 0.0]
│ └── Y(0)
└── [mul: 0.0]
└── AddBlock(0)
└── N(0)
interaction
function which has to accept a Register
and a list
of edges: list[tuple[int, int]]
:
from qadence import AnalogRX, Register, add_interaction
from qadence.transpile.emulate import ising_interaction
def int_fn(r: Register, pairs: list[tuple[int, int]]) -> AbstractBlock:
# do either something completely custom
# ...
# or e.g. change the default kwargs to `ising_interaction`
return ising_interaction(r, pairs, rydberg_level=70)
b = AnalogRX(2.0)
r = Register(1)
e = add_interaction(r, b, interaction=int_fn)