Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added APS3 pulse shapes and removed NCO number check in the APS3 driver #268

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions QGL/ChannelLibraries.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,12 +436,13 @@ def build_connectivity_graph(self):
self.connectivityG[chan.source][chan.target]['channel'] = chan

@check_for_duplicates
def new_APS3(self, label, address, serial_port, **kwargs):
def new_APS3(self, label, address, **kwargs):
# Address must be specified as 'ip;serial_port'
chan1 = Channels.PhysicalQuadratureChannel(label=f"{label}-1", channel=0, instrument=label, translator="APS3Pattern", sampling_rate=2.5e9, channel_db=self.channelDatabase)
chan2 = Channels.PhysicalQuadratureChannel(label=f"{label}-2", channel=1, instrument=label, translator="APS3Pattern", sampling_rate=2.5e9, channel_db=self.channelDatabase)
m1 = Channels.PhysicalMarkerChannel(label=f"{label}-m1", channel=0, instrument=label, translator="APS3Pattern", sampling_rate=2.5e9, channel_db=self.channelDatabase)

this_transmitter = Channels.Transmitter(label=label, model="APS3", address=address, serial_port=serial_port, channels=[chan1, chan2, m1], channel_db=self.channelDatabase, **kwargs)
this_transmitter = Channels.Transmitter(label=label, model="APS3", address=address, channels=[chan1, chan2, m1], channel_db=self.channelDatabase, **kwargs)
this_transmitter.trigger_source = 'external' if 'trigger_source' not in kwargs else kwargs['trigger_source']

self.add_and_update_dict(this_transmitter)
Expand Down
14 changes: 11 additions & 3 deletions QGL/PulsePrimitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,12 +638,20 @@ def flat_top_gaussian(chan,
"""
A constant pulse with rising and falling gaussian shape
"""
# Note: use_cos is a hack that should definitely be fixed more elegantly later
use_cos = False
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")
if use_cos:
#print('Using cosine ZX90')
p = Utheta(chan, length=riseFall, amp=amp, phase=phase, shape_fun=PulseShapes.cosOn, 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.cosOff, label=label+"_fall")
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)


Expand Down
113 changes: 113 additions & 0 deletions QGL/PulseShapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,88 @@ def drag(amp=1,
QQuad = drag_scaling * derivScale * xPts * np.exp(-0.5 * (xPts**2))
return amp * (IQuad + 1j * QQuad)

def alt_gaussian(pulse_length, sampling_rate, cutoff=2):
num_points = int(round(pulse_length*sampling_rate))

# Cut off the gaussian at some number of std devs away
x_points = np.linspace(-cutoff, cutoff, num_points)

# Compute the gaussian itself
gaussian_points = np.exp(-0.5 * (x_points**2))

# Shift it down so that the cutoff point becomes zero
gaussian_points -= np.min(gaussian_points)

# Scale to an amp of 1
gaussian_points /= np.max(gaussian_points)

# Pad with zeroes to match the vector length and return
return gaussian_points

def gng(amp=1,
length=0,
sampling_rate=1e9,
secondary_gaussian_width=0,
cutoff=2,
notch_freq=0,
**params):
# Create the original gaussian pulse and the second term which will be upconverted
print('Making gng with amp={:.2f} length={:f} sampling_rate={:f} sgw={:f} cutoff={:d} notch_freq={:f}'.format(amp,length,sampling_rate,secondary_gaussian_width, cutoff, notch_freq))
g = alt_gaussian(length, sampling_rate, cutoff)

if notch_freq == 0 or secondary_gaussian_width == 0:
return g

secondary_g = alt_gaussian(secondary_gaussian_width, sampling_rate, cutoff)

# Create the upconverion
secondary_time_points = np.linspace(0, secondary_gaussian_width, int(round(secondary_gaussian_width*sampling_rate)))
shift = np.exp(2*np.pi*notch_freq*secondary_time_points)

# Upconvert the second gaussian and calculate the spectrum of both
shifted_fft = np.fft.fft(list(secondary_g*shift) + [0]*(len(g) - len(secondary_g)))
fft = np.fft.fft(list(g) + [0]*(len(secondary_g) - len(g)))

# Scale and rotate the shifted gaussian so that at the notch, the frequency component disappears
idx_notch = np.argmin(np.abs(np.fft.fftfreq(len(g), d=1/sampling_rate) - notch_freq))
scale_factor = (np.abs(fft[idx_notch])/np.abs(shifted_fft[idx_notch]))
phase_factor = np.exp(1j*(np.angle(fft[idx_notch]) - np.angle(shifted_fft[idx_notch]) + np.pi))

# Calculate the new spectrum with the desired tone cancelled
fft += shifted_fft*scale_factor*phase_factor

# Return the time samples scaled to an absolute magnitude of 1
ifft = np.fft.ifft(fft)
return ifft * amp / np.max(np.abs(ifft))

def notched_gaussian(amp=1,
length=0,
sampling_rate=1e9,
cutoff=2,
notch_freq=0,
notch_width=0,
**params):
notch_freqs = [notch_freq]
notch_widths = [notch_width]
g = alt_gaussian(length, sampling_rate, cutoff)
fft = np.fft.fft(g)
fft_freqs = np.fft.fftfreq(fft.size, d=1/sampling_rate)

# Find the index of the frequency closest to the edges of the desired notches
idx_notch_starts = [np.argmin(np.abs(fft_freqs - c - (notch_widths[idx]/2))) for idx,c in enumerate(notch_freqs)]
idx_notch_stops = [np.argmin(np.abs(fft_freqs - c + (notch_widths[idx]/2))) for idx,c in enumerate(notch_freqs)]

# print(str(pulse_length) + ' ' + str(idx_notch_starts[0]) + str(idx_notch_stops[0]))

for i in range(len(notch_freqs)):
zeros = np.zeros(abs(idx_notch_stops[i] - idx_notch_starts[i]), dtype=fft.dtype)
if(idx_notch_stops[i] > idx_notch_starts[i]):
fft[idx_notch_starts[i]:idx_notch_stops[i]] = zeros
else:
fft[idx_notch_stops[i]:idx_notch_starts[i]] = zeros

ifft = np.fft.ifft(fft)
return ifft * amp / np.max(np.abs(ifft))

def gaussOn(amp=1, length=0, cutoff=2, sampling_rate=1e9, **params):
'''
Expand All @@ -82,6 +164,24 @@ def gaussOn(amp=1, length=0, cutoff=2, sampling_rate=1e9, **params):
amp = (amp / (1 - nextPoint))
return (amp * (np.exp(-0.5 * (xPts**2)) - nextPoint)).astype(np.complex)

def cosOn(amp=1, length=0, sampling_rate=1e9, **params):
'''
A half-cosine pulse going from zero to full
'''
numPts = int(np.round(length * sampling_rate))
times = np.linspace(0, length, numPts)
vals = amp*(1 - np.cos((times * np.pi / length)))/2
return vals.astype(np.complex)

def cosOff(amp=1, length=0, sampling_rate=1e9, **params):
'''
A half-cosine pulse going from full to zero
'''
numPts = int(np.round(length * sampling_rate))
times = np.linspace(0, length, numPts)
vals = amp*(1 - np.cos((times * np.pi / length) + np.pi))/2
return vals.astype(np.complex)


def gaussOff(amp=1, length=0, cutoff=2, sampling_rate=1e9, **params):
'''
Expand Down Expand Up @@ -163,6 +263,19 @@ def exp_decay(amp=1, length=0, sigma=0, sampling_rate=1e9, steady_state=0.4, **p
timePts = (1.0 / sampling_rate) * np.arange(numPts)
return amp * ((1-steady_state) * np.exp(-timePts / sigma) + steady_state).astype(np.complex)

# def composite(amp=1, resolution=1e6, band_start=0, band_stop=0.5e9, sampling_rate=1e9, **params):
# """

# """
# numPts = int((1/resolution)*sampling_rate)
# freqs = np.zeros(numPts, dtype=np.complex)
# idx_start = int(round(numPts * band_start / sampling_rate))
# idx_stop = int(round(numPts * band_stop / sampling_rate))
# freqs[idx_start : idx_stop] = 1
# samples = np.ifft(



def CLEAR(amp=1, length=0, sigma=0, sampling_rate=1e9, **params):
"""
Pulse shape to quickly deplete the cavity at the end of a measurement.
Expand Down
5 changes: 3 additions & 2 deletions QGL/drivers/APS3Pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,8 +615,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.")
# print(freqs)
# 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))
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))
Expand Down