Skip to content

Intermediate Representation compilation

Qadence 2 expression package provides compilation functionality to target Qadence 2 Intermediate Representation (IR) defined here. Please note that the IR follows SSA form for classical computations.

In following is a pure digital program with local possibly parametrized gates compiled to the IR model.

from qadence2_expressions import *

a = parameter('a')
b = parameter('b')

phi = variable("phi")

expr = RX(a * phi / 2)(2) * Z(1) * RY(b * phi / 2)(0)

reset_ir_options()
ir = compile_to_model(expr)
Model(
  AllocQubits(3),
  {
    'b': Alloc(1, trainable=False),
    'phi': Alloc(1, trainable=True),
    'a': Alloc(1, trainable=False),
  },
  [
    Assign('%0', Call('mul', 0.5, Load('b'))),
    Assign('%1', Call('mul', Load('%0'), Load('phi'))),
    QuInstruct('ry', Support((0,)), Load('%1')),
    QuInstruct('z', Support((1,))),
    Assign('%2', Call('mul', 0.5, Load('a'))),
    Assign('%3', Call('mul', Load('%2'), Load('phi'))),
    QuInstruct('rx', Support((2,)), Load('%3')),
  ]
)

Similar example with interleaved global gates.

expr = RX(a * phi / 2)(2) * CZ() * RY(b * phi / 2)(0)

ir = compile_to_model(expr)
Model(
  AllocQubits(3),
  {
    'a': Alloc(1, trainable=False),
    'phi': Alloc(1, trainable=True),
    'b': Alloc(1, trainable=False),
  },
  [
    Assign('%0', Call('mul', 0.5, Load('a'))),
    Assign('%1', Call('mul', Load('%0'), Load('phi'))),
    QuInstruct('rx', Support((2,)), Load('%1')),
    QuInstruct('cz', Support.target_all()),
    Assign('%2', Call('mul', 0.5, Load('b'))),
    Assign('%3', Call('mul', Load('%2'), Load('phi'))),
    QuInstruct('ry', Support((0,)), Load('%3')),
  ]
)

Options can be set at any point to supplement the model to be recompiled.

set_qubits_positions([(-2, 1), (0, 0), (3, 1)])
set_grid_type("triangular")

ir = compile_to_model(expr)
Model(
  AllocQubits(3, qubit_positions=[(-2, 1), (0, 0), (3, 1)], grid_type='triangular'),
  {
    'a': Alloc(1, trainable=False),
    'phi': Alloc(1, trainable=True),
    'b': Alloc(1, trainable=False),
  },
  [
    Assign('%0', Call('mul', 0.5, Load('a'))),
    Assign('%1', Call('mul', Load('%0'), Load('phi'))),
    QuInstruct('rx', Support((2,)), Load('%1')),
    QuInstruct('cz', Support.target_all()),
    Assign('%2', Call('mul', 0.5, Load('b'))),
    Assign('%3', Call('mul', Load('%2'), Load('phi'))),
    QuInstruct('ry', Support((0,)), Load('%3')),
  ]
)

Pure analog programs can be constructed using NativeDrive and FreeEvolution operations that leave their concrete definitions and implementations to the backend. They can accept arbitrary combinations of single valued or arrays of parameters.

set_qubits_positions([(-1,0), (-1, 1), (1, 0), (1, 1)])
set_grid_type("triangular")

t = variable("t")
omega = array_variable("omega", 4)
detuning = array_variable("detuning", 3)
phase = parameter("phase")

expr = (
    NativeDrive(t / 2, omega, detuning, phase)()
    * FreeEvolution(2.5)()
    * NativeDrive(t / 2, omega, -detuning, phase)()
)

ir = compile_to_model(expr)
Model(
  AllocQubits(4, qubit_positions=[(-1, 0), (-1, 1), (1, 0), (1, 1)], grid_type='triangular'),
  {
    't': Alloc(1, trainable=True),
    'omega': Alloc(4, trainable=True),
    'detuning': Alloc(3, trainable=True),
    'phase': Alloc(1, trainable=False),
  },
  [
    Assign('%0', Call('mul', 0.5, Load('t'))),
    QuInstruct('dyn_pulse', Support.target_all(), Load('%0'), Load('omega'), Load('detuning'), Load('phase')),
    QuInstruct('dyn_wait', Support.target_all(), 2.5),
    Assign('%1', Call('mul', -1.0, Load('detuning'))),
    QuInstruct('dyn_pulse', Support.target_all(), Load('%0'), Load('omega'), Load('%1'), Load('phase')),
  ]
)