Analog computation addon
The analog computation addon is for creating analog schedules that can be run on the quantum hardware or simulator.
Schedule from ParityOSOutputs
The analog schedule for the Party compiled problem can be represented as follows [Lechner15]:
where \(H_d\) is the driver Hamiltonian (e.g. \(\sum_{i=1}^n{-X}\), where \(n\) is the number of qubits), \(H_p\) is the problem Hamiltonian, \(H_c\) is the constraint Hamiltonian, \(A(t)\), \(B(t)\) and \(C(t)\) are time dependent functions that define the analog schedule. As an example one can define the time dependent parameters in the following way:
where \(t \in [0,1]\) is the time parameter.
Here’s how to create the above-defined schedule from ParityOSOutput using the analog computation
addon (assuming parityos_output is given – see Quickstart section).
import sympy
from parityos_addons.analog_computation import ParitySchedule
t = sympy.Symbol("t")
duration = 1.0
problem_expr = t
constraint_expr = t
driver_expr = duration - t
parity_schedule = ParitySchedule(
output=parityos_output,
interactions_coefficient=problem_expr,
constraints_coefficient=constraint_expr,
driver_coefficient=driver_expr,
time_parameter=t,
duration=duration,
)
The ParitySchedule is using the default driver Hamiltonian \(H_d = \sum_{i=0}^n{-X}\). One
can access interactions_schedule, constraints_schedule and driver_schedule properties
from a ParitySchedule, which are going to give the corresponding ScheduleTerm instances.
Custom Schedules
For constructing a more generic schedule (not necessarily related to LHZ arcitecture) one can
use Schedule class. For that one should define the corresponding Observable and time
dependent expression pairs to define ScheduleTerms, which are going to be summed up in the
Schedule class.
For example let’s define a Schedule that consists of two ScheduleTerms. One will be the
driver ScheduleTerm:
from parityos import Qubit
from parityos.base.gates import X
from parityos_addons.analog_computation import Observable, PauliOp, ScheduleTerm
qubit_count = 3
qubits = [Qubit(index) for index in range(qubit_count)]
driver_interactions = [
PauliOp([X(qubits[0])]),
PauliOp([X(qubits[1])]),
PauliOp([X(qubits[2])]),
]
driver_coefficient = [-1] * len(driver_interactions)
driver_observable = Observable(driver_interactions, driver_coefficient)
t = sympy.Symbol('t')
driver_expression = 1 - t
driver_schedule_term = ScheduleTerm(driver_observable, driver_expression)
And the other one will be the problem ScheduleTerm:
from parityos.base.gates import Z
problem_interactions = [
PauliOp([Z(qubits[0]), Z(qubits[1])]),
PauliOp([Z(qubits[0]), Z(qubits[2])]),
PauliOp([Z(qubits[1]), Z(qubits[2])]),
]
problem_coefficient = [1] * len(problem_interactions)
problem_observable = Observable(problem_interactions, problem_coefficient)
problem_expression = t
problem_schedule_term = ScheduleTerm(problem_observable, problem_expression)
Then one just need to combine these two ScheduleTerms into a single Schedule instance:
from parityos_addons.analog_computation import Schedule
custom_schedule = Schedule([problem_schedule_term, driver_schedule_term], t)
The Schedule class is more generic than ParitySchedule class and one can obtain the
corresponding Schedule for the given ParitySchedule in the following way:
schedule = parity_schedule.schedule
References
Lechner, Wolfgang, Philipp Hauke, and Peter Zoller. “A quantum annealing architecture with all-to-all connectivity from local interactions.” Science advances 1.9 (2015): e1500838.