Skip to content

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 (Callable[[AbstractBlock], AbstractBlock]) or circuits to circuits (Callable[[QuantumCircuit], QuantumCircuit]).

TYPE: Callable DEFAULT: ()

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)
ChainBlock(0,1)
├── [mul: 2] 
   └── ChainBlock(0)
       └── ChainBlock(0)
           ├── X(0)
           └── Y(0)
└── KronBlock(0,1)
    └── KronBlock(0,1)
        ├── X(0)
        └── X(1)

ChainBlock(0,1)
├── [mul: 2.000] 
   └── X(0)
├── Y(0)
└── KronBlock(0,1)
    ├── X(0)
    └── X(1)

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))
ChainBlock(0,1)
├── ChainBlock(0,1)
   ├── X(0)
   └── X(1)
└── ChainBlock(0,1)
    ├── X(0)
    └── X(1)

Source code in qadence/transpile/transpile.py
def transpile(*fs: Callable) -> Callable:
    """`AbstractBlock` or `QuantumCircuit` transpilation.

    Compose functions that
    accept a circuit/block and returns a circuit/block.

    Arguments:
        *fs: composable functions that either map blocks to blocks
            (`Callable[[AbstractBlock], AbstractBlock]`)
            or circuits to circuits (`Callable[[QuantumCircuit], QuantumCircuit]`).

    Returns:
        Composed function.

    Examples:

    Flatten a block of nested chains and krons:
    ```python exec="on" source="material-block" result="json"
    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)
    print() # markdown-exec: hide

    # 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.
    ```python exec="on" source="material-block" result="json"
    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))
    ```
    """
    return lambda x: reduce(lambda acc, f: f(acc), reversed(fs), x)

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
def chain_single_qubit_ops(block: AbstractBlock) -> AbstractBlock:
    """Transpile a chain of krons into a kron of chains of single qubit operations.

    Examples:
    ```python exec="on" source="above" result="json"
    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))
    ```
    """
    if is_chain_of_primitivekrons(block):
        try:
            return kron(*map(lambda bs: chain(*bs), zip(*block)))  # type: ignore[misc]
        except Exception as e:
            logger.debug(
                f"Unable to transpile {block} using chain_single_qubit_ops\
                         due to {e}. Returning original circuit."
            )
            return block

    elif isinstance(block, CompositeBlock):
        return _construct(type(block), tuple(chain_single_qubit_ops(b) for b in block.blocks))
    else:
        return block

scale_primitive_blocks_only(block, scale=None)

Push the scale all the way down into the leaves of the block tree.

When given a scaled CompositeBlock consisting of several PrimitiveBlocks.

PARAMETER DESCRIPTION
block

The block to be transpiled.

TYPE: AbstractBlock

scale

An optional scale parameter. Only to be used for recursive calls internally.

TYPE: Basic DEFAULT: None

RETURNS DESCRIPTION
AbstractBlock

A block of the same type where the scales have been moved into the subblocks.

TYPE: AbstractBlock

Examples:

There are two different cases: ChainBlocks/KronBlocks: 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.000] 
   └── X(0)
└── RX(0) [params: ['theta']]

AddBlocks: 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.000] 
   └── X(0)
└── [mul: 2.000] 
    └── RX(0) [params: ['theta']]

Source code in qadence/transpile/block.py
@singledispatch
def scale_primitive_blocks_only(block: AbstractBlock, scale: sympy.Basic = None) -> AbstractBlock:
    """Push the scale all the way down into the leaves of the block tree.

    When given a scaled CompositeBlock consisting of several PrimitiveBlocks.

    Arguments:
        block: The block to be transpiled.
        scale: An optional scale parameter. Only to be used for recursive calls internally.

    Returns:
        AbstractBlock: A block of the same type where the scales have been moved into the subblocks.

    Examples:

    There are two different cases:
    `ChainBlock`s/`KronBlock`s: Only the first subblock needs to be scaled because chains/krons
    represent multiplications.
    ```python exec="on" source="above" result="json"
    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))
    ```

    `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")).
    ```python exec="on" source="above" result="json"
    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))
    ```
    """
    raise NotImplementedError(f"scale_primitive_blocks_only is not implemented for {type(block)}")

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: AbstractBlock | list[AbstractBlock]

value

The value of the trainable attribute to assign to the input blocks

TYPE: bool DEFAULT: True

inplace

Whether to modify the block(s) in place or not. Currently, only

TYPE: bool DEFAULT: True

RAISES DESCRIPTION
NotImplementedError

if the inplace argument is set to False, the function will raise this exception

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
def set_trainable(
    blocks: AbstractBlock | list[AbstractBlock], value: bool = True, inplace: bool = True
) -> AbstractBlock | list[AbstractBlock]:
    """Set the trainability of all parameters in a block to a given value.

    Args:
        blocks (AbstractBlock | list[AbstractBlock]): Block or list of blocks for which
            to set the trainable attribute
        value (bool, optional): The value of the trainable attribute to assign to the input blocks
        inplace (bool, optional): Whether to modify the block(s) in place or not. Currently, only

    Raises:
        NotImplementedError: if the `inplace` argument is set to False, the function will
            raise  this exception

    Returns:
        AbstractBlock | list[AbstractBlock]: the input block or list of blocks with the trainable
            attribute set to the given value
    """

    if isinstance(blocks, AbstractBlock):
        blocks = [blocks]

    if inplace:
        for block in blocks:
            params: list[sympy.Basic] = parameters(block)
            for p in params:
                if not p.is_number:
                    p.trainable = value
    else:
        raise NotImplementedError("Not inplace set_trainable is not yet available")

    return blocks if len(blocks) > 1 else blocks[0]

validate(block)

Moves a block from global to local qubit numbers by adding PutBlocks.

Reassigns qubit locations appropriately.

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
def validate(block: AbstractBlock) -> AbstractBlock:
    """Moves a block from global to local qubit numbers by adding PutBlocks.

    Reassigns qubit locations appropriately.

    # Example
    ```python exec="on" source="above" result="json"
    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))
    ```
    """
    vblock: AbstractBlock
    from qadence.transpile import reassign

    if isinstance(block, ControlBlock):
        vblock = deepcopy(block)
        b: AbstractBlock
        (b,) = block.blocks
        b = reassign(b, {i: i - min(b.qubit_support) for i in b.qubit_support})
        b = validate(b)
        vblock.blocks = (b,)  # type: ignore[assignment]

    elif isinstance(block, CompositeBlock):
        blocks = []
        for b in block.blocks:
            mi, ma = min(b.qubit_support), max(b.qubit_support)
            nb = reassign(b, {i: i - min(b.qubit_support) for i in b.qubit_support})
            nb = validate(nb)
            nb = PutBlock(nb, tuple(range(mi, ma + 1)))
            blocks.append(nb)
        try:
            vblock = _construct(type(block), tuple(blocks))
        except AssertionError as e:
            if str(e) == "Make sure blocks act on distinct qubits!":
                vblock = chain(*blocks)
            else:
                raise e

    elif isinstance(block, PrimitiveBlock):
        vblock = deepcopy(block)

    else:
        raise NotImplementedError

    vblock.tag = block.tag
    return vblock