Skip to content

Commit

Permalink
Merge 636c919 into 32e2146
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewware committed May 28, 2020
2 parents 32e2146 + 636c919 commit c7c776c
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 29 deletions.
3 changes: 2 additions & 1 deletion QGL/PatternUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import hashlib, collections
import pickle
from copy import copy
from collections.abc import Iterable

from .PulseSequencer import Pulse, TAPulse, PulseBlock, CompositePulse, CompoundGate, align
from .PulsePrimitives import BLANK, X
Expand Down Expand Up @@ -312,7 +313,7 @@ def convert_length_to_samples(wf_length, sampling_rate, quantization=1):
# from Stack Overflow: http://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists-in-python/2158532#2158532
def flatten(l):
for el in l:
if isinstance(el, collections.Iterable) and not isinstance(el, (str, Pulse, CompositePulse)) :
if isinstance(el, Iterable) and not isinstance(el, (str, Pulse, CompositePulse)) :
for sub in flatten(el):
yield sub
else:
Expand Down
129 changes: 103 additions & 26 deletions QGL/drivers/APS2Pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,7 @@ def compress_marker(markerLL):

def write_sequence_file(awgData, fileName):
'''
Main function to pack channel sequences into an APS2 h5 file.
Main function to pack channel sequences into an APS2 file.
'''
# Convert QGL IR into a representation that is closer to the hardware.
awgData['ch1']['linkList'], wfLib = preprocess(
Expand Down Expand Up @@ -1115,7 +1115,7 @@ def write_sequence_file(awgData, fileName):

def read_sequence_file(fileName):
"""
Reads a HDF5 sequence file and returns a dictionary of lists.
Reads a .aps2 sequence file and returns a dictionary of lists.
Dictionary keys are channel strings such as ch1, m1
Lists are or tuples of time-amplitude pairs (time, output)
"""
Expand Down Expand Up @@ -1286,9 +1286,12 @@ def update_wf_library(filename, pulses, offsets):
----------
filename : string
path to the .aps2 file to update
pulses : dict{labels: pulse objects}
pulses : dict{labels: pulse objects} or 2-by-N list of raw data values
A dictionary of pulse labels and the the new pulse objects to write
into file.
into file or a two element list with raw waveform data from [-1, 1]
listed for the real, then imaginary quadrature. This method does no
checking of the data to assure and pulse structure. The WF data is
directly packed into the file.
offsets : dict{waveform_names : address values}
A dictionary of waveform names used to index the newly
created wavefroms
Expand Down Expand Up @@ -1324,23 +1327,31 @@ def update_wf_library(filename, pulses, offsets):
wf_len_chan2 = struct.unpack('<Q', FID.read(8))[0]
chan2_start = FID.tell() # Save beginning of chan1 data block

for label, pulse in pulses.items():
#create a new waveform
if pulse.isTimeAmp:
shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4)
else:
shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape
try:
length = offsets[label][1]
except KeyError:
print("\t{} not found in offsets so skipping".format(pulse))
continue
for offset in offsets[label][0]:
# print("\tUpdating {} at offset {}".format(pulse, offset))
FID.seek(chan1_start + 2*offset, 0) # Chan 1 block + 2 bytes per offset sample
FID.write(np.int16(MAX_WAVEFORM_VALUE * shape.real).tobytes())
FID.seek(chan2_start + 2*offset, 0) # Chan 1 block + 2 bytes per offset sample
FID.write(np.int16(MAX_WAVEFORM_VALUE * shape.imag).tobytes())
if isinstance(pulses, dict):
for label, pulse in pulses.items():
#create a new waveform
if pulse.isTimeAmp:
shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4)
else:
shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape
try:
length = offsets[label][1]
except KeyError:
print("\t{} not found in offsets so skipping".format(pulse))
continue
for offset in offsets[label][0]:
# print("\tUpdating {} at offset {}".format(pulse, offset))
FID.seek(chan1_start + 2*offset, 0) # Chan 1 block + 2 bytes per offset sample
FID.write(np.int16(MAX_WAVEFORM_VALUE * shape.real).tobytes())
FID.seek(chan2_start + 2*offset, 0) # Chan 1 block + 2 bytes per offset sample
FID.write(np.int16(MAX_WAVEFORM_VALUE * shape.imag).tobytes())
elif isinstance(pulses, list) and len(pulses) == 2:
logger.debug('Using raw data to update WF library in file.')
# write raw data to file
FID.seek(chan1_start, 0) # Chan 1 block + 2 bytes per offset sample
FID.write(np.int16(MAX_WAVEFORM_VALUE * pulses[0]).tobytes())
FID.seek(chan2_start, 0) # Chan 1 block + 2 bytes per offset sample
FID.write(np.int16(MAX_WAVEFORM_VALUE * pulses[1]).tobytes())


def tdm_instructions(seqs):
Expand Down Expand Up @@ -1525,17 +1536,77 @@ def read_waveforms(filename):
wf_dat.append(dat)
return wf_dat

def replace_instructions(filename, instructions, channel = 1):
channelStr = get_channel_instructions_string(channel)
with h5py.File(filename, 'r+') as fid:
del fid[channelStr]
fid.create_dataset(channelStr, data=instructions)
def replace_instructions(filename, instructions):
"""
Update an aps2 instruction set in place give an iterable of
properly encoded instructions.
Parameters
----------
filename : string
path to the .aps2 file to update
instructions : list of raw aps2 instructions
list of raw, uint64 instructions to replace the current ones in file
Examples
--------
>>> APS2Pattern.replace_instructions('path/to/.aps2', instructions)
"""

with open(filename, 'rb+') as FID:
# read all the existing file information so we can faithfully
# reconstruct it.

target_hw = FID.read(4).decode('utf-8')
file_version = struct.unpack('<f', FID.read(4))[0]
min_fw = struct.unpack('<f', FID.read(4))[0]
num_chans = struct.unpack('<H', FID.read(2))[0]

old_inst_len = struct.unpack('<Q', FID.read(8))[0]
old_instructions = np.frombuffer(FID.read(8*old_inst_len), dtype=np.uint64)

wf_dat = []
for i in range(num_chans):
wf_len = struct.unpack('<Q', FID.read(8))[0]
dat = np.frombuffer(FID.read(2*wf_len), dtype=np.int16).flatten()
wf_dat.append(dat)

# Write new data

# skip over the header
FID.seek(0) # return to the start of the file
FID.seek(4) # target_hw
FID.seek(4, 1) # file version
FID.seek(4, 1) # minimum firmware version
FID.seek(2, 1) # number of channels

FID.write(np.uint64(instructions.size).tobytes()) # instruction length
FID.write(instructions.tobytes()) # instructions in uint64 form

# if the instruction length has changed we need to rewrite the
# waveform data
for i in range(num_chans):
data = wf_dat[i]
FID.write(np.uint64(data.size).tobytes()) # waveform data length for channel
FID.write(data.astype(np.int16).tobytes())

# chop off any remaining old data
FID.truncate()


def display_decompiled_file(filename, tdm = False):
"""
Display all the decomplied instructions from a .aps2 file as
{instructions #}{instruction value in 16 bit hex}{decompiled instruction}
"""
raw = raw_instructions(filename)
display_decompiled_instructions(raw, tdm)

def display_decompiled_instructions(raw, tdm = False, display_op_codes = True):
"""
Display the decomplied instructions from their packed, 16 bit values
{instructions #}{instruction value in 16 bit hex}{decompiled instruction}
"""
cmds = decompile_instructions(raw, tdm)
opcodeStr = ''
for i,a in enumerate(zip(raw,cmds)):
Expand All @@ -1545,10 +1616,16 @@ def display_decompiled_instructions(raw, tdm = False, display_op_codes = True):
print("{:5}: {}{}".format(i, opcodeStr,y))

def display_raw_instructions(raw):
"""
Format a raw number as a 16 bit hex instruction
"""
for x in raw:
print("0x{:016x}".format(x))

def display_raw_file(filename):
"""
Display all the raw hex instructions from a .aps2 file as
"""
raw = raw_instructions(filename)
display_raw_instructions(raw)

Expand Down
93 changes: 91 additions & 2 deletions tests/test_APS2Pattern.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import unittest
import os
import pickle
import numpy as np
from copy import copy

Expand All @@ -9,10 +11,57 @@
class APSPatternUtils(unittest.TestCase):
def setUp(self):
self.cl = ChannelLibrary(db_resource_name=":memory:")
self.q1gate = Channels.LogicalMarkerChannel(label='q1-gate', channel_db=self.cl.channelDatabase)
#self.q1gate = Channels.LogicalMarkerChannel(label='q1-gate',
# channel_db=self.cl.channelDatabase)
self.q1 = self.cl.new_qubit(label='q1')
self.q1.gate_chan = self.q1gate
#self.q1.gate_chan = self.q1gate
self.q1.pulse_params['length'] = 30e-9

ip_addresses = [f"192.168.1.{i}" for i in [23, 24, 25, 28]]
aps2 = self.cl.new_APS2_rack("Maxwell",
ip_addresses,
tdm_ip="192.168.1.11")
aps2.px("TDM").trigger_interval = 500e-6
self.cl.set_master(aps2.px("TDM"))

# initialize all four APS2 to linear regime
for i in range(1,4):
aps2.tx(i).ch(1).I_channel_amp_factor = 0.5
aps2.tx(i).ch(1).Q_channel_amp_factor = 0.5
aps2.tx(i).ch(1).amp_factor = 1

dig_1 = self.cl.new_X6("MyX6", address=0)

dig_1.record_length = 1024 + 256

#### QUBIT 1 ######################################################
#### Qubit 1 Instruments ##########################################
AM1 = self.cl.new_source("AutodyneM1",
"HolzworthHS9000",
"HS9004A-492-1",
power=16.0,
frequency= 6.74621e9, reference="10MHz")

q1src = self.cl.new_source("q1source",
"HolzworthHS9000",
"HS9004A-492-2",
power=16.0,
frequency=5.0122e9, reference="10MHz")

self.cl.set_measure(self.q1, aps2.tx(2), dig_1.channels[1],
gate=False,
trig_channel=aps2.tx(2).ch("m2"),
generator=AM1)
self.cl.set_control(self.q1, aps2.tx(4), generator=q1src)

#### Qubit 2 Measure Chan ########################################

self.cl["q1"].measure_chan.autodyne_freq = 11e6
self.cl["q1"].measure_chan.pulse_params = {"length": 1.0e-6,
"amp": 0.5,
"sigma": 1.0e-8,
"shape_fun": "tanh"}

self.cl.update_channelDict()

def test_synchronize_control_flow(self):
Expand Down Expand Up @@ -43,6 +92,46 @@ def test_synchronize_control_flow(self):
instrOpCode = (actual.header >> 4) & 0xf
assert (instrOpCode == expected)

def test_inplace_updates(self):
q1 = self.q1
APS2Pattern.SAVE_WF_OFFSETS = True

mf = RabiAmp(q1, np.linspace(-1, 1, 122))
aps2_f = os.path.join(os.path.dirname(mf), "Rabi-Maxwell_U4.aps2")

# overwrite the instructions and waveforms
# here we completely change the experiment from Rabi to
# SPAM characterization
spam_mf = SPAM(q1, np.linspace(-1.0, 1.0, 11));

offset_f = os.path.join(os.path.dirname(spam_mf), "SPAM-Maxwell_U4.offsets")
with open(offset_f, "rb") as FID:
offsets = pickle.load(FID)

#pulses = {l: Utheta(q1, amp=0.5, phase=0) for l in offsets}
aps2_f = os.path.join(os.path.dirname(mf), "Rabi-Maxwell_U4.aps2")
wfm_f = os.path.join(os.path.dirname(spam_mf), "SPAM-Maxwell_U4.aps2")
# read raw waveforms
spam_waveforms = APS2Pattern.read_waveforms(wfm_f)

spam_instrs = APS2Pattern.raw_instructions(wfm_f)
APS2Pattern.replace_instructions(aps2_f, spam_instrs)

APS2Pattern.update_wf_library(aps2_f, spam_waveforms, offsets)

# assert the data now in the file is what we wrote above
instructions = APS2Pattern.read_instructions(aps2_f)
waveforms = APS2Pattern.read_waveforms(aps2_f)
decomp_spam_instr = APS2Pattern.decompile_instructions(spam_instrs)

for actual, expected in zip(instructions, decomp_spam_instr):
instrOpCode = (actual.header >> 4) & 0xf
expe_OpCode = (expected.header >> 4) & 0xf
assert (int(instrOpCode) == int(expe_OpCode))

for actual, expected in zip(waveforms, spam_waveforms):
assert (actual.all() == expected.all())


if __name__ == "__main__":
unittest.main()

0 comments on commit c7c776c

Please sign in to comment.