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"{table_code}
idYearDateTimeNameNotes
")) - - 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"{table_code}
idDateTimeSampleNameValue
")) + display(IPHTML(f"{table_code}
idYearDateTimeNameNotes
")) 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"{table_code}
ObjectParameter{name1}{name2}
")) + 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.",