diff --git a/QGL/BasicSequences/RB.py b/QGL/BasicSequences/RB.py
index 11285889..6163e700 100644
--- a/QGL/BasicSequences/RB.py
+++ b/QGL/BasicSequences/RB.py
@@ -86,6 +86,56 @@ def SingleQubitRB(qubit, seqs, purity=False, showPlot=False, add_cals=True):
plot_pulse_files(metafile)
return metafile
+def SingleQubitLeakageRB(qubit, seqs, pi2args, showPlot=False):
+ """Single qubit randomized benchmarking using 90 and 180 generators to
+ measure leakage outside the qubit subspace.
+ See https://journals.aps.org/prl/supplemental/10.1103/PhysRevLett.123.120502/Rol_SOM.pdf
+ for description of algorithm.
+
+ Parameters
+ ----------
+ qubit : logical channel to implement sequence (LogicalChannel)
+ seqs : list of lists of Clifford group integers
+ pi2args: arguments passed to the X90 gate for the 1 <-> 2 transition during calibration
+ showPlot : whether to plot (boolean)
+ """
+
+ seqsBis = []
+ for seq in seqs:
+ combined_seq = reduce(operator.add, [clifford_seq(c, qubit) for c in seq])
+
+ # Append sequence with tomography ids and measurement
+ seqsBis.append(combined_seq + [Id(qubit), Id(qubit), MEAS(qubit)])
+
+ # Append sequence with tomography pulses and measurement
+ seqsBis.append(combined_seq + [X90(qubit), X90(qubit), MEAS(qubit)])
+
+ # Add the calibration sequences
+ seqsBis.append([Id(qubit), Id(qubit), Id(qubit), Id(qubit), MEAS(qubit)])
+ seqsBis.append([X90(qubit), X90(qubit), Id(qubit), Id(qubit), MEAS(qubit)])
+ seqsBis.append([X90(qubit), X90(qubit), X90(qubit, **pi2args), X90(qubit, **pi2args), MEAS(qubit)])
+
+ axis_descriptor = [
+ {
+ 'name': 'length',
+ 'unit': None,
+ 'points': [len(s) for s in seqs for i in range(2)],
+ 'partition': 1
+ },
+ {
+ 'name': 'calibration',
+ 'unit': 'state',
+ 'partition': 2,
+ 'points': ['0', '1', '2']
+ }]
+
+ metafile = compile_to_hardware(seqsBis, 'RB/LRB', axis_descriptor = axis_descriptor, extra_meta = {'sequences':seqs})
+
+ if showPlot:
+ plot_pulse_files(metafile)
+ return metafile
+
+
def TwoQubitRB(q1, q2, seqs, showPlot=False, suffix="", add_cals=True):
"""Two qubit randomized benchmarking using 90 and 180 single qubit generators and ZX90
@@ -124,6 +174,53 @@ def TwoQubitRB(q1, q2, seqs, showPlot=False, suffix="", add_cals=True):
plot_pulse_files(metafile)
return metafile
+def TwoQubitLeakageRB(q1, q2, meas_qubit, seqs, pi2args, showPlot=False):
+ """Two qubit randomized benchmarking using 90 and 180 single qubit generators and ZX90 to
+ measure leakage outside the qubit subspace.
+ See https://journals.aps.org/prl/supplemental/10.1103/PhysRevLett.123.120502/Rol_SOM.pdf
+ for description of algorithm.
+ Parameters
+ ----------
+ qubit : logical channel to implement sequence (LogicalChannel)
+ seqs : list of lists of Clifford group integers
+ showPlot : whether to plot (boolean)
+ suffix : suffix to apply to sequence file names
+ """
+ seqsBis = []
+ for seq in seqs:
+ combined_seq = reduce(operator.add, [clifford_seq(c, q2, q1) for c in seq])
+
+ # Append sequence with tomography ids and measurement
+ seqsBis.append(combined_seq + [Id(meas_qubit), Id(meas_qubit), MEAS(meas_qubit)])
+
+ # Append sequence with tomography pulses and measurement
+ seqsBis.append(combined_seq + [X90(meas_qubit), X90(meas_qubit), MEAS(meas_qubit)])
+
+ # Add the calibration sequences
+ seqsBis.append([Id(meas_qubit), Id(meas_qubit), Id(meas_qubit), Id(meas_qubit), MEAS(meas_qubit)])
+ seqsBis.append([X90(meas_qubit), X90(meas_qubit), Id(meas_qubit), Id(meas_qubit), MEAS(meas_qubit)])
+ seqsBis.append([X90(meas_qubit), X90(meas_qubit), X90(meas_qubit, **pi2args), X90(meas_qubit, **pi2args), MEAS(meas_qubit)])
+
+ axis_descriptor = [
+ {
+ 'name': 'length',
+ 'unit': None,
+ 'points': [len(s) for s in seqs for i in range(2)],
+ 'partition': 1
+ },
+ {
+ 'name': 'calibration',
+ 'unit': 'state',
+ 'partition': 2,
+ 'points': ['0', '1', '2']
+ }]
+
+ metafile = compile_to_hardware(seqsBis, 'RB/LRB', axis_descriptor = axis_descriptor, extra_meta = {'sequences':seqs})
+
+ if showPlot:
+ plot_pulse_files(metafile)
+ return metafile
+
def SingleQubitRB_AC(qubit, seqs, purity=False, showPlot=False, add_cals=True):
"""Single qubit randomized benchmarking using atomic Clifford pulses.
diff --git a/QGL/ChannelLibraries.py b/QGL/ChannelLibraries.py
index 1f88b774..c4339a1d 100644
--- a/QGL/ChannelLibraries.py
+++ b/QGL/ChannelLibraries.py
@@ -47,7 +47,7 @@
import bbndb
-from bqplot import Figure, LinearScale, Axis, Lines, Figure
+from bqplot import Figure, LinearScale, ColorScale, Axis, Lines, Figure
from bqplot.marks import Graph, Lines, Label
from ipywidgets import Layout, VBox, HBox
@@ -55,7 +55,8 @@
from . import Channels
from . import PulseShapes
-from IPython.display import HTML, display
+from ipywidgets import Layout, HTML
+from IPython.display import HTML as IPHTML, display
channelLib = None
@@ -140,23 +141,12 @@ def ls(self):
cdb = Channels.ChannelDatabase
q = self.session.query(cdb.label, cdb.time, cdb.id, cdb.notes).\
order_by(-Channels.ChannelDatabase.id, Channels.ChannelDatabase.label, Channels.ChannelDatabase.notes).all()
+
table_code = ""
for i, (label, time, id, notes) in enumerate(q):
y, d, t = map(time.strftime, ["%Y", "%b. %d", "%I:%M:%S %p"])
- # t = time.strftime("(%Y) %b. %d @ %I:%M:%S %p")
table_code += f"
{id} | {y} | {d} | {t} | {label} | {notes} |
"
- display(HTML(f"id | Year | Date | Time | Name | Notes |
---|
{table_code}
"))
-
- def cal_ls(self):
- ''' List of auspex.pulse_calibration results '''
- caldb = bbndb.calibration.Calibration
- c = self.session.query(caldb.sample_id, caldb.name, caldb.value, caldb.date).order_by(-caldb.sample_id).all()
- table_code = ""
- for i, (sample_id, name, value, time) in enumerate(c):
- d,t = str(time).split()
- sample = self.session.query(bbndb.calibration.Sample).filter_by(id=sample_id).first()
- table_code += f"{id} | {d} | {t.split('.')[0]} | {sample.name} | {name} | {round(value,9)} |
"
- display(HTML(f"id | Date | Time | Sample | Name | Value |
---|
{table_code}
"))
+ display(IPHTML(f"id | Year | Date | Time | Name | Notes |
---|
{table_code}
"))
def ent_by_type(self, obj_type, show=False):
q = self.session.query(obj_type).filter(obj_type.channel_db.has(label="working")).order_by(obj_type.label).all()
@@ -244,6 +234,72 @@ def next_level(nodes, iteration=0, offset=0, accum=[]):
fig = Figure(marks=[bq_graph], layout=fig_layout)
return fig
+ def show_connectivity(self, verbose=False):
+ graph_edges = []
+ qub_objs = self.qubits()
+ edges = self.edges()
+ for e in edges:
+ graph_edges.append((e.source.label, e.target.label))
+
+ table = HTML("Re-evaluate this plot to see information about qubits. Otherwise it will be stale.")
+ table.add_class("hover_tooltip")
+ display(IPHTML("""
+
+ """))
+
+ graph = nx.digraph.DiGraph()
+ for q in qub_objs:
+ graph.add_node(q.label, node_obj = q)
+
+ graph.add_edges_from(graph_edges)
+
+ indices = {n: i for i, n in enumerate(graph.nodes())}
+
+ node_data = [{'label': n, 'data': v['node_obj'].print(show=False, verbose=verbose), 'edge_data': v['node_obj'].print_edges(show=False, verbose=verbose, edges = [e for e in self.edges() if e.source.label == n or e.target.label == n]
+ )} for n,v in graph.nodes(True)] # fix edges
+ link_data = [{'source': indices[s], 'target': indices[t]} for s, t in graph.edges()]
+
+ qub_objs.sort(key=lambda x: x.label)
+ qubit_names = [q.label for q in qub_objs]
+
+ loc = {}
+
+ nqubits = len(qub_objs)
+ dtheta = 2*np.pi/nqubits
+ rho = 4
+ x = [rho*np.cos(dtheta*ind) for ind,n in enumerate(qub_objs)]
+ y = [rho*np.sin(dtheta*ind) for ind,n in enumerate(qub_objs)]
+ hovered_symbol = ''
+ def hover_handler(self, content, hovered_symbol=hovered_symbol, table=table):
+ symbol = content.get('data', '')
+ if(symbol != hovered_symbol):
+ hovered_symbol = symbol
+ table.value = symbol['data']
+
+ def click_handler(self, content, hovered_symbol=hovered_symbol, table=table):
+ symbol = content.get('data', '')
+ if(symbol != hovered_symbol):
+ hovered_symbol = symbol
+ table.value = symbol['edge_data']
+
+ xs = LinearScale(min=min(x)-0.5, max=max(x)+0.6)
+ ys = LinearScale(min=min(y)-0.5, max=max(y)+0.6)
+ fig_layout = Layout(width='500px', height='500px')
+ cs = ColorScale(scheme = 'PuBuGn')
+ bq_graph = Graph(node_data=node_data, link_data=link_data, x=x, y=y,scales={'x':xs, 'y':ys, 'color': cs},
+ link_type='line', color=np.linspace(0,1,len(node_data)), directed=True)
+ bgs_lines = []
+ middles = []
+ bq_graph.tooltip = table
+ bq_graph.on_hover(hover_handler)
+ bq_graph.on_element_click(click_handler)
+ fig = Figure(marks=[bq_graph], layout=fig_layout)
+ return fig
+
def show_frequency_plan(self):
c_freqs = {}
m_freqs = {}
@@ -273,6 +329,64 @@ def spike_at(f):
figs.append(Figure(marks=lines+[labels], axes=[ax, ay], title=f"{ss} Frequency Plan"))
return HBox(figs)
+ def diff(self, name1, name2, index1=1, index2=1):
+ '''
+ Compare 2 channel library versions. Print the difference between 2 libraries, including parameter values and channel allocations. It requires both versions to be saved in the same sqlite database.
+ Args
+ name1: name of first version to compare
+ name2: name of second version to compare
+ index1, index2: by default, loading the most recent instances for the given names. Specifying index1/2 = 2 will select the second most recent instance etc."""
+ '''
+ cdb = Channels.ChannelDatabase
+ db1 = self.session.query(cdb).filter(cdb.label==name1).order_by(cdb.time.asc())[-1*index1]
+ db2 = self.session.query(cdb).filter(cdb.label==name2).order_by(cdb.time.asc())[-1*index2]
+ copied_db1 = bbndb.deepcopy_sqla_object(db1)
+ copied_db2 = bbndb.deepcopy_sqla_object(db2)
+ dict_1 = {c.label: c for c in copied_db1.channels + copied_db1.all_instruments()}
+ dict_2 = {c.label: c for c in copied_db2.channels + copied_db2.all_instruments()}
+ def iter_diff(value_iter1, value_iter2, ct, label=''):
+ table_code = ''
+ for key, key2 in zip(value_iter1, value_iter2):
+ if key in ['_sa_instance_state', 'channel_db']:
+ continue
+ if isinstance(value_iter1, dict):
+ cmp1 = value_iter1[key]
+ cmp2 = value_iter2[key]
+ if label in value_iter1:
+ label = value_iter1['label']
+ elif isinstance(value_iter1, list):
+ cmp1 = key
+ cmp2 = key2 #TODO fix. why would they be in any order?
+ else:
+ cmp1 = getattr(value_iter1, key)
+ cmp2 = getattr(value_iter2, key)
+ if (cmp1 == None) ^ (cmp2 == None):
+ table_code += f"{label} | {key} | {cmp1} | {cmp2} |
"
+ continue
+ if (cmp1 == None) or (cmp2 == None) or ((isinstance(cmp1, dict) or isinstance(cmp1, list)) and len(cmp1) == 0):
+ continue
+ if isinstance(cmp1, (bbndb.qgl.DatabaseItem, bbndb.qgl.Channel, bbndb.qgl.Instrument)):
+ cmp1 = cmp1.__dict__
+ cmp2 = cmp2.__dict__
+ if isinstance(cmp1, (dict, list, bbndb.qgl.DatabaseItem, bbndb.qgl.Channel, bbndb.qgl.Instrument)):
+ if ct<1: # up to 2 recursion levels for now, to avoid infinite loops for bidirectional relations
+ ct+=1
+ table_code += iter_diff(cmp1, cmp2, ct, label=label)
+ continue
+ if cmp1 != cmp2:
+ table_code += f"{label} | {key} | {cmp1} | {cmp2} |
"
+ return table_code
+
+ table_code = ''
+ for chan in set(list(dict_1.keys()) + list(dict_2.keys())):
+ if chan not in dict_1 or chan not in dict_2: # don't display differences of unique channels
+ continue
+ this_dict1 = dict_1[chan].__dict__
+ this_dict2 = dict_2[chan].__dict__
+ ct = 0
+ table_code += iter_diff(this_dict1, this_dict2, ct, chan)
+ display(HTML(f"Object | Parameter | {name1} | {name2} |
---|
{table_code}
"))
+
def receivers(self):
return self.ent_by_type(Channels.Receiver)
@@ -285,6 +399,9 @@ def transceivers(self):
def qubits(self):
return self.ent_by_type(Channels.Qubit)
+ def edges(self):
+ return self.ent_by_type(Channels.Edge)
+
def meas(self):
return self.ent_by_type(Channels.Measurement)
@@ -295,7 +412,7 @@ def markers(self):
def load(self, name, index=1):
"""Load the latest instance for a particular name. Specifying index = 2 will select the second most recent instance """
cdb = Channels.ChannelDatabase
- items = self.session.query(cdb).filter(cdb.label==name).order_by(cdb.time.desc()).all()
+ items = self.session.query(cdb).filter(cdb.label==name).order_by(cdb.time.asc()).all()
self.load_obj(items[-index])
@check_session_dirty
@@ -478,6 +595,42 @@ def new_APS2_rack(self, label, ip_addresses, tdm_ip=None, **kwargs):
self.add_and_update_dict(this_transceiver)
return this_transceiver
+ @check_for_duplicates
+ def new_transceiver(self, model, label, address, numtx=1, numrx=1, nummark=4, record_length = 1024, **kwargs):
+ translator = model+"Pattern"
+ stream_sel = model+"StreamSelector"
+
+ chans = []
+ for i in range(numtx):
+ chan = Channels.PhysicalQuadratureChannel(label=f"{label}-Tx{i+1}-1", instrument=label, channel=i, translator=translator, channel_db=self.channelDatabase)
+ chans.append(chan)
+ for i in range(nummark):
+ chan = Channels.PhysicalMarkerChannel(label=f"{label}-Tx{i+1}-M", channel=i, instrument=label, translator=translator, channel_db=self.channelDatabase)
+ chans.append(chan)
+
+ transmitter = Channels.Transmitter(label=f"{label}-Tx", model=model, address=address, channels=chans, channel_db=self.channelDatabase)
+ transmitter.trigger_source = "external"
+ transmitter.address = address
+
+ chans = []
+ for i in range(numrx):
+ chan = Channels.ReceiverChannel(label=f"RecvChan-{label}-{i+1}", channel=i, channel_db=self.channelDatabase)
+ chans.append(chan)
+
+ receiver = Channels.Receiver(label=f"{label}-Rx", model=model, address=address, channels=chans, record_length=record_length, channel_db=self.channelDatabase)
+ receiver.trigger_source = "external"
+ receiver.stream_types = "raw"
+ receiver.address = address
+ receiver.stream_sel = stream_sel
+
+ transceiver = Channels.Transceiver(label=label, address=address, model=model, transmitters=[transmitter], receivers = [receiver], initialize_separately=False, channel_db=self.channelDatabase)
+ transmitter.transceiver = transceiver
+ receiver.transceiver = transceiver
+
+ self.add_and_update_dict(transceiver)
+ return transceiver
+
+
@check_for_duplicates
def new_X6(self, label, address, dsp_channel=0, record_length=1024, **kwargs):
diff --git a/QGL/PulsePrimitives.py b/QGL/PulsePrimitives.py
index a99a6e6a..ba908919 100644
--- a/QGL/PulsePrimitives.py
+++ b/QGL/PulsePrimitives.py
@@ -638,9 +638,12 @@ def flat_top_gaussian(chan,
"""
A constant pulse with rising and falling gaussian shape
"""
- p = Utheta(chan, length=riseFall, amp=amp, phase=phase, shape_fun=PulseShapes.gaussOn, label=label+"_rise") + \
- Utheta(chan, length=length, amp=amp, phase=phase, shape_fun=PulseShapes.constant, label=label+"_top") + \
- Utheta(chan, length=riseFall, amp=amp, phase=phase, shape_fun=PulseShapes.gaussOff, label=label+"_fall")
+ if riseFall == 0:
+ p = Utheta(chan, length=length, amp=amp, phase=phase, shape_fun=PulseShapes.constant, label=label+"_top")
+ else:
+ p = Utheta(chan, length=riseFall, amp=amp, phase=phase, shape_fun=PulseShapes.gaussOn, label=label+"_rise") + \
+ Utheta(chan, length=length, amp=amp, phase=phase, shape_fun=PulseShapes.constant, label=label+"_top") + \
+ Utheta(chan, length=riseFall, amp=amp, phase=phase, shape_fun=PulseShapes.gaussOff, label=label+"_fall")
return p._replace(label=label)
diff --git a/QGL/PulseSequencer.py b/QGL/PulseSequencer.py
index d19027f5..d70069b7 100644
--- a/QGL/PulseSequencer.py
+++ b/QGL/PulseSequencer.py
@@ -47,7 +47,11 @@ def __new__(cls, label, channel, shapeParams, amp=1.0, phase=0, frameChange=0, i
for param in requiredParams:
if param not in shapeParams.keys():
raise NameError("shapeParams must include {0}".format(param))
- isTimeAmp = (shapeParams['shape_fun'] == PulseShapes.constant)
+ if isinstance(shapeParams['shape_fun'],str):
+ shape = getattr(PulseShapes, shapeParams['shape_fun'])
+ else:
+ shape = shapeParams['shape_fun']
+ isTimeAmp = (shape == PulseShapes.constant)
isZero = (amp == 0)
return super(cls, Pulse).__new__(cls, label, channel,
shapeParams['length'], amp, phase,
diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py
index d6e3d10c..8b2c1633 100644
--- a/QGL/drivers/APS2Pattern.py
+++ b/QGL/drivers/APS2Pattern.py
@@ -47,8 +47,9 @@
MAX_NUM_INSTRUCTIONS = 2**26
MAX_REPEAT_COUNT = 2**16 - 1
MAX_TRIGGER_COUNT = 2**32 - 1
-
+MAX_VRAM_ADDRESS = 2**(12-2)-1
MODULATION_CLOCK = 300e6
+NUM_NCO = 4
# instruction encodings
WFM = 0x0
@@ -581,13 +582,19 @@ def to_instruction(self, write_flag=True, label=None):
MODULATOR_OP_OFFSET = 44
NCO_SELECT_OP_OFFSET = 40
+ nco_select_bits = {1 : 0b0001,
+ 2 : 0b0010,
+ 3 : 0b0100,
+ 4 : 0b1000,
+ 15: 0b1111}[self.nco_select]
+
op_code_map = {"MODULATE": 0x0,
"RESET_PHASE": 0x2,
"SET_FREQ": 0x6,
"SET_PHASE": 0xa,
"UPDATE_FRAME": 0xe}
payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | (
- self.nco_select << NCO_SELECT_OP_OFFSET)
+ (nco_select_bits) << NCO_SELECT_OP_OFFSET)
if self.instruction == "MODULATE":
#zero-indexed quad count
payload |= np.uint32(self.length / ADDRESS_UNIT - 1)
@@ -615,9 +622,9 @@ def inject_modulation_cmds(seqs):
for ct,seq in enumerate(seqs):
#check whether we have modulation commands
freqs = np.unique([entry.frequency for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)])
- if len(freqs) > 2:
- raise Exception("Max 2 frequencies on the same channel allowed.")
- no_freq_cmds = np.all(np.less(np.abs(freqs), 1e-8))
+ if len(freqs) > NUM_NCO:
+ raise Exception("Max {} frequencies on the same channel allowed.".format(NUM_NCO))
+ no_freq_cmds = np.allclose(freqs, 0)
phases = [entry.phase for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)]
no_phase_cmds = np.all(np.less(np.abs(phases), 1e-8))
frame_changes = [entry.frameChange for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)]
@@ -641,7 +648,7 @@ def inject_modulation_cmds(seqs):
#heuristic to insert phase reset before trigger if we have modulation commands
if isinstance(entry, ControlFlow.Wait):
if not ( no_modulation_cmds and (cur_freq == 0) and (cur_phase == 0)):
- mod_seq.append(ModulationCommand("RESET_PHASE", 0x3))
+ mod_seq.append(ModulationCommand("RESET_PHASE", 0xF))
for nco_ind, freq in enumerate(freqs):
mod_seq.append( ModulationCommand("SET_FREQ", nco_ind + 1, frequency = -freq) )
elif isinstance(entry, ControlFlow.Return):
@@ -1139,8 +1146,6 @@ def start_new_seq():
wf_len = struct.unpack('= 2019.2
+bbndb >= 2020.1
numpy >= 1.11.1
scipy >= 0.17.1
networkx >= 1.11
-bqplot >= 0.11.5
+bqplot >= 0.12.2
sqlalchemy >= 1.2.15
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 8aa67efe..520799fa 100644
--- a/setup.py
+++ b/setup.py
@@ -1,17 +1,17 @@
from setuptools import setup, find_packages
setup(name='QGL',
- version='2019.2',
+ version='2020.1',
packages=find_packages(exclude=["tests"]),
url='https://github.com/BBN-Q/QGL',
download_url='https://github.com/BBN-Q/QGL',
license="Apache 2.0 License",
install_requires=[
- "bbndb >= 2019.2",
+ "bbndb >= 2020.1",
"numpy >= 1.11.1",
"scipy >= 0.17.1",
"networkx >= 1.11",
- "bqplot >= 0.11.5",
+ "bqplot >= 0.12.2",
"sqlalchemy >= 1.2.15"
],
description="Quantum Gate Language (QGL) is a domain specific language embedded in python for specifying pulse sequences.",