Skip to content

Commit

Permalink
Merge pull request #168 from BBN-Q/142.annotated-measurement
Browse files Browse the repository at this point in the history
Support for TDM sequencer and custom ops
  • Loading branch information
Diego Ristè committed Jul 3, 2018
2 parents 5bdcda5 + b0b1ebb commit d799908
Show file tree
Hide file tree
Showing 13 changed files with 785 additions and 164 deletions.
90 changes: 90 additions & 0 deletions QGL/BasicSequences/Feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from itertools import product
import operator
from ..ControlFlow import *
from ..TdmInstructions import *
from functools import reduce


Expand Down Expand Up @@ -99,3 +100,92 @@ def Reset(qubits,
if showPlot:
plot_pulse_files(metafile)
return metafile


# do not make it a subroutine for now
def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e-6, docals=False, calRepeats=2):
"""
Encoding on 3-qubit bit-flip code, followed by n rounds of syndrome detection, and final correction using the n results.
Parameters
----------
data_qs : tuple of logical channels for the 3 code qubits
ancilla_qs: tuple of logical channels for the 2 syndrome qubits
theta, phi: longitudinal and azimuthal rotation angles for encoded state (default = no encoding)
meas_delay : delay between syndrome check rounds
docals, calRepeats: enable calibration sequences, repeated calRepeats times
Returns
-------
metafile : metafile path
"""
if len(data_qs) != 3 or len(ancilla_qs) != 2:
raise Exception("Wrong number of qubits")
seqs = [
DecodeSetRounds(1,0,nrounds),
Invalidate(addr=10, mask=2*nrounds),
Invalidate(addr=11, mask=0x1)]

# encode single-qubit state into 3 qubits
if theta and phi:
seqs+=[Utheta(data_qs[1], theta, phi), CNOT(data_qs[1], data_qs[0]), CNOT(data_qs[1], data_qs[2])]

# multiple rounds of syndrome measurements
for n in range(nrounds):
seqs+=[CNOT(data_qs[0],ancilla_qs[0])*CNOT(data_qs[1],ancilla_qs[1])],
seqs+=[CNOT(data_qs[1], ancilla_qs[0])*CNOT(data_qs[2],ancilla_qs[1])],
seqs+= [MEASA(ancilla_qs[0], maddr=(10, 2*n))*MEASA(ancilla_qs[1], maddr=(10, 2*n+1)),
Id(ancilla_qs[0], meas_delay),
MEAS(data_qs[0], amp=0)*MEAS(data_qs[1], amp=0)*MEAS(data_qs[2], amp=0)] # virtual msmt's just to keep the number of segments uniform across digitizer channels
seqs+=Decode(10, 11, 2*nrounds)
seqs+=qwait("RAM",11)
seqs+=[MEAS(data_qs[0])*MEAS(data_qs[1])*MEAS(data_qs[2])*
MEAS(ancilla_qs[0], amp=0)*MEAS(ancilla_qs[1], amp=0)] # virtual msmt's

# apply corrective pulses depending on the decoder result
FbGates = []
for q in data_qs:
FbGates.append([gate(q) for gate in [Id, X]])
FbSeq = [reduce(operator.mul, x) for x in product(*FbGates)]
for k in range(8):
seqs += qif(k, [FbSeq[k]])
if docals:
seqs += create_cal_seqs(qubits,
calRepeats)
metafile = compile_to_hardware(seqs, 'BitFlip/BitFlip')
return metafile

def MajorityVoteN(qubits, nrounds, prep=[], meas_delay=1e-6, docals=False, calRepeats=2):
"""
Majority vote across multiple measurement results (same or different qubits)
Parameters
----------
qubits : tuple of logical channels
nrounds: number of consecutive measurements
meas_delay : delay between measurements
docals, calRepeats: enable calibration sequences, repeated calRepeats times
Returns
-------
metafile : metafile path
"""
nqubits = len(qubits)
seqs = [MajorityMask(nrounds*nqubits),
Invalidate(addr=10, mask=nrounds*nqubits),
Invalidate(addr=11, mask=1)]
if prep:
seqs += [reduce(operator.mul, [X(q) for n,q in enumerate(qubits) if prep[n]])]
for n in range(nrounds):
seqs += [reduce(operator.mul, [MEASA(q, (10, nqubits*n+m)) for m,q in enumerate(qubits)]), Id(qubits[0],meas_delay)]
seqs+=MajorityVote(10,11, nrounds*nqubits)
seqs+=qwait("RAM", 11)
seqs+=[Id(qubits[0],100e-9)]
seqs+=qif(1,[X(qubits[0])]) # placeholder for any conditional operation
seqs=[seqs]
if docals:
seqs += create_cal_seqs(qubits,
calRepeats)
metafile = compile_to_hardware(seqs, 'MajorityVote/MajorityVote')
return metafile
2 changes: 1 addition & 1 deletion QGL/BasicSequences/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
from .helpers import create_cal_seqs, delay_descriptor, cal_descriptor
from .CR import EchoCRPhase, EchoCRLen, EchoCRAmp, PiRabi
from .AllXY import AllXY
from .Feedback import Reset
from .Feedback import Reset, BitFlip3, MajorityVoteN
2 changes: 1 addition & 1 deletion QGL/BasicSequences/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def create_cal_seqs(qubits, numRepeats, measChans=None, waitcmp=False, delay=Non
for _ in range(numRepeats)]

#Add on the measurement operator.
measBlock = reduce(operator.mul, [MEAS(q) for q in qubits])
measBlock = reduce(operator.mul, [MEAS(q) for q in measChans])
#Add optional delay
full_cal_seqs = [[seq, Id(qubits[0], delay), measBlock] if delay else [seq, measBlock] for seq in cal_seqs]
if waitcmp:
Expand Down
23 changes: 21 additions & 2 deletions QGL/Compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from .PulseSequencer import Pulse, PulseBlock, CompositePulse
from . import ControlFlow
from . import BlockLabel
from . import TdmInstructions # only for APS2-TDM

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -76,7 +77,9 @@ def merge_channels(wires, channels):
break
# control flow on any channel should pass thru
if any(isinstance(e, (ControlFlow.ControlInstruction,
BlockLabel.BlockLabel)) for e in entries):
BlockLabel.BlockLabel, TdmInstructions.WriteAddrInstruction,
TdmInstructions.CustomInstruction,
TdmInstructions.LoadCmpVramInstruction)) for e in entries):
# for the moment require uniform control flow so that we
# can pull from the first channel
assert all(e == entries[0]
Expand Down Expand Up @@ -432,6 +435,15 @@ def compile_to_hardware(seqs,
else:
files[awgName] = fullFileName

# generate TDM sequences FIXME: what's the best way to identify the need for a TDM seq.? Support for single TDM
if 'APS2Pattern' in [wire.translator for wire in physWires]:
aps2tdm_module = import_module('QGL.drivers.APS2Pattern') # this is redundant with above
tdm_instr = aps2tdm_module.tdm_instructions(seqs)
files['TDM'] = os.path.normpath(os.path.join(
config.AWGDir, fileName + '-' + 'TDM' + suffix + data[
'seqFileExt']))
aps2tdm_module.write_tdm_seq(tdm_instr, files['TDM'])

# create meta output
if not axis_descriptor:
axis_descriptor = [{
Expand Down Expand Up @@ -542,7 +554,10 @@ def flatten_to_pulses(obj):
wires[chan] += [copy(block)]
continue
# control flow broadcasts to all channels if channel attribute is None
if isinstance(block, ControlFlow.ControlInstruction):
if (isinstance(block, ControlFlow.ControlInstruction) or
isinstance(block, TdmInstructions.WriteAddrInstruction) or
isinstance(block, TdmInstructions.CustomInstruction) or
isinstance(block, TdmInstructions.LoadCmpVramInstruction)):
# Need to deal with attaching measurements and edges to control
# instruction. Until we have a proper solution for that, we will
# always broadcast control instructions to all channels
Expand Down Expand Up @@ -648,6 +663,8 @@ def __init__(self, pulse=None):
self.frameChange = 0
self.isTimeAmp = False
self.frequency = 0

self.maddr = (-1, 0)
else:
self.label = pulse.label
self.key = pulse.hashshape()
Expand All @@ -658,6 +675,8 @@ def __init__(self, pulse=None):
self.isTimeAmp = pulse.isTimeAmp
self.frequency = pulse.frequency

self.maddr = pulse.maddr

def __repr__(self):
return self.__str__()

Expand Down
10 changes: 8 additions & 2 deletions QGL/ControlFlow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .PulseSequencer import Pulse
from functools import wraps
from .mm import multimethod
from .TdmInstructions import WriteAddrInstruction, LoadCmpVramInstruction

## QGL control-flow statements ##

Expand Down Expand Up @@ -83,14 +84,19 @@ def repeatall(n, seqs):
return seqs


def qwait(channels=None, kind="TRIG"):
def qwait(kind="TRIG", addr=None, channels=None):
'''
Insert a WAIT or LOADCMP command on the target channels (an iterable, or None)
'''
if kind == "TRIG":
return Wait(channels)
else:
elif kind == "CMP":
return LoadCmp(channels)
elif kind == "RAM":
if addr is None:
raise Exception('Please specify address')
return [WriteAddrInstruction('INVALIDATE', None, 1, addr, 0xffffffff, False), LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, 0xff, False)]



def qsync(channels=None):
Expand Down
9 changes: 7 additions & 2 deletions QGL/PatternUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .PulsePrimitives import BLANK
from . import ControlFlow
from . import BlockLabel
from . import TdmInstructions
import QGL.drivers

def hash_pulse(shape):
Expand Down Expand Up @@ -133,8 +134,12 @@ def apply_gating_constraints(chan, linkList):
# first pass consolidates entries
previousEntry = None
for ct,entry in enumerate(miniLL):
if isinstance(entry, (ControlFlow.ControlInstruction,
BlockLabel.BlockLabel)):
if isinstance(entry,
(ControlFlow.ControlInstruction, BlockLabel.BlockLabel,
TdmInstructions.CustomInstruction,
TdmInstructions.WriteAddrInstruction,
TdmInstructions.LoadCmpVramInstruction)):

if previousEntry:
gateSeq.append(previousEntry)
previousEntry = None
Expand Down
58 changes: 53 additions & 5 deletions QGL/PulsePrimitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,12 +718,21 @@ def CNOT(source, target, **kwargs):
cnot_impl = globals()[config.cnot_implementation]
return cnot_impl(source, target, **kwargs)

## Measurement operators
@_memoize
def MEAS(qubit, **kwargs):
# The worker method for MEAS and MEASA
def _MEAS(qubit, **kwargs):

'''
MEAS(q1) measures a qubit. Applies to the pulse with the label M-q1
_MEAS(q1) implements both MEAS and MEASA, but because of
the way memoize works, we want to distinguish them and
memoize them separately. (this may change if the way
memoization works is changed)
TODO: this is annoying because measuring the same qubit
but storing the result to two different addresses creates
two distinct pulses, unless we also memoize the waveforms
themselves.
'''

channelName = "M-" + qubit.label
measChan = ChannelLibraries.MeasFactory(channelName)
params = overrideDefaults(measChan, kwargs)
Expand All @@ -736,8 +745,47 @@ def MEAS(qubit, **kwargs):
if 'amp' not in kwargs:
ignoredStrParams.append('amp')
meas_label = "MEAS_no_trig" if 'dig_trig' in kwargs and not kwargs['dig_trig'] else "MEAS"
return Pulse(meas_label, measChan, params, amp, 0.0, 0.0, ignoredStrParams)
if 'maddr' in kwargs:
maddr = kwargs['maddr']
else:
maddr = (-1, 0)

return Pulse(meas_label, measChan, params,
amp, 0.0, 0.0, ignoredStrParams, maddr=maddr)

## Measurement operators
@_memoize
def MEAS(qubit, **kwargs):
'''
MEAS(q1) measures a qubit (applying the pulse with the label M-q1)
This is the 'traditional' measurement; it does not require
a measurement address, and will gripe if one is provided in
the kwargs
Note that in the future there may be hardware that requires a
measurement address (even if it's just a placeholder)
'''

if 'maddr' in kwargs:
raise ValueError('MEAS must not have a maddr kwarg')

return _MEAS(qubit, **kwargs)

@_memoize
def MEASA(qubit, maddr, **kwargs):
'''
MEASA(q1) measures a qubit (applying the pulse with the label M-q1)
and stores the result in the given address in "measurement memory".
Note that a failure will occur if the hardware does not support
measurement memory and a MEASA is requested.
There may be special values for maddr that change the behavior
of this operation, but they have not been specified yet.
'''

return _MEAS(qubit, maddr=maddr, **kwargs)

#MEAS and ring-down time on one qubit, echo on every other
def MeasEcho(qM, qD, delay, piShift=None, phase=0):
Expand Down
11 changes: 7 additions & 4 deletions QGL/PulseSequencer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@

class Pulse(namedtuple("Pulse", ["label", "channel", "length", "amp", "phase", "frequency",
"frameChange", "shapeParams", "isTimeAmp",
"isZero", "ignoredStrParams"])):
"isZero", "ignoredStrParams",
"maddr", "moffset"])):
__slots__ = ()

def __new__(cls, label, channel, shapeParams, amp=1.0, phase=0, frameChange=0, ignoredStrParams=[]):
def __new__(cls, label, channel, shapeParams, amp=1.0, phase=0, frameChange=0, ignoredStrParams=[], maddr=-1, moffset=0):
if hasattr(channel, 'frequency'):
frequency = channel.frequency
else:
Expand All @@ -49,7 +50,8 @@ def __new__(cls, label, channel, shapeParams, amp=1.0, phase=0, frameChange=0, i
return super(cls, Pulse).__new__(cls, label, channel,
shapeParams['length'], amp, phase,
frequency, frameChange, shapeParams,
isTimeAmp, isZero, ignoredStrParams)
isTimeAmp, isZero, ignoredStrParams,
maddr, moffset)

def __str__(self):
kwvals = []
Expand Down Expand Up @@ -219,7 +221,8 @@ def __mul__(self, rhs):
return rhs * self
# otherwise, we are promoting to a PulseBlock
rhs = rhs.promote(ptype)

if not np.isclose(self.length, rhs.length, atol=1e-10):
return align('center', self, rhs)
# copy PulseBlock so we don't modify other references
result = copy(self)
result.pulses = copy(self.pulses)
Expand Down

0 comments on commit d799908

Please sign in to comment.