"""
Support functions.
"""
from typing import Union, List
import numpy as np
import json
import base64
from collections import OrderedDict
[docs]def to_int(val, scale, quantize=1, parname=None, trunc=False):
"""Convert a parameter value from user units to ASM units.
Normally this means converting from float to int.
For the v2 tProcessor this can also convert QickSweep to QickSweepRaw.
To avoid overflow, values are rounded towards zero using np.trunc().
Parameters
----------
val : float or QickSweep
parameter value or sweep range
scale : float
conversion factor
quantize : int
rounding step for ASM value
parname : str
parameter type - only for sweeps
trunc : bool
round towards zero using np.trunc(), instead of to closest integer using np.round()
Returns
-------
int or QickSweepRaw
ASM value
"""
if hasattr(val, 'to_int'):
return val.to_int(scale, quantize=quantize, parname=parname, trunc=trunc)
else:
if trunc:
return int(quantize * np.trunc(val*scale/quantize))
else:
return int(quantize * np.round(val*scale/quantize))
[docs]def check_bytes(val, length):
"""Test if a signed int will fit in the specified number of bytes.
Parameters
----------
val : int
value to test
length : int
number of bytes
Returns
-------
bool
True if value will fit, False otherwise
"""
try:
int(val).to_bytes(length=length, byteorder='little', signed=True)
return True
except OverflowError:
return False
[docs]def cosine(length=100, maxv=30000):
"""
Create a numpy array containing a cosine shaped envelope function
:param length: Length of array
:type length: int
:param maxv: Maximum amplitude of cosine flattop function
:type maxv: float
:return: Numpy array containing a cosine flattop function
:rtype: array
"""
x = np.linspace(0,2*np.pi,length)
y = maxv*(1-np.cos(x))/2
return y
[docs]def gauss(mu=0, si=25, length=100, maxv=30000):
"""
Create a numpy array containing a Gaussian function
:param mu: Mu (peak offset) of Gaussian
:type mu: float
:param sigma: Sigma (standard deviation) of Gaussian
:type sigma: float
:param length: Length of array
:type length: int
:param maxv: Maximum amplitude of Gaussian
:type maxv: float
:return: Numpy array containing a Gaussian function
:rtype: array
"""
x = np.arange(0, length)
y = maxv * np.exp(-(x-mu)**2/si**2)
return y
[docs]def DRAG(mu, si, length, maxv, delta, alpha):
"""
Create I and Q arrays for a DRAG pulse.
Based on QubiC and Qiskit-Pulse implementations.
:param mu: Mu (peak offset) of Gaussian
:type mu: float
:param si: Sigma (standard deviation) of Gaussian
:type si: float
:param length: Length of array
:type length: int
:param maxv: Maximum amplitude of Gaussian
:type maxv: float
:param delta: anharmonicity of the qubit (units of 1/sample time)
:type delta: float
:param alpha: alpha parameter of DRAG (order-1 scale factor)
:type alpha: float
:return: Numpy array with I and Q components of the DRAG pulse
:rtype: array, array
"""
x = np.arange(0, length)
gaus = maxv * np.exp(-(x-mu)**2/si**2)
# derivative of the gaussian
dgaus = -(x-mu)/(si**2)*gaus
idata = gaus
qdata = -1 * alpha * dgaus / delta
return idata, qdata
[docs]def triang(length=100, maxv=30000):
"""
Create a numpy array containing a triangle function
:param length: Length of array
:type length: int
:param maxv: Maximum amplitude of triangle function
:type maxv: float
:return: Numpy array containing a triangle function
:rtype: array
"""
y = np.zeros(length)
# if length is even, there are length//2 samples in the ramp
# if length is odd, there are length//2 + 1 samples in the ramp
halflength = (length + 1) // 2
y1 = np.linspace(0, maxv, halflength)
y[:halflength] = y1
y[length//2:length] = np.flip(y1)
return y
[docs]class NpEncoder(json.JSONEncoder):
"""
JSON encoder with support for numpy objects and custom classes with to_dict methods.
Taken from https://stackoverflow.com/questions/50916422/python-typeerror-object-of-type-int64-is-not-json-serializable
"""
[docs] def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)
if isinstance(obj, np.floating):
return float(obj)
if isinstance(obj, np.ndarray):
# base64 is considerably more compact and faster to pack/unpack
# return obj.tolist()
return (base64.b64encode(obj.tobytes()).decode(), obj.shape, obj.dtype.str)
if hasattr(obj, "to_dict"):
return obj.to_dict()
return super().default(obj)
[docs]def decode_array(json_array):
"""
Convert a base64-encoded array back into numpy.
"""
data, shape, dtype = json_array
return np.frombuffer(base64.b64decode(data), dtype=np.dtype(dtype)).reshape(shape)
[docs]def progs2json(proglist):
"""Dump QICK programs to a JSON string.
Parameters
----------
proglist : list of dict
A list of program dictionaries to dump.
Returns
-------
str
A JSON string.
"""
return json.dumps(proglist, cls=NpEncoder)
[docs]def json2progs(s):
"""Read QICK programs from JSON.
Parameters
----------
s : file-like object or string
A JSON file or JSON string.
Returns
-------
list of dict
A list of program dictionaries.
"""
if hasattr(s, 'read'):
# input is file-like, we should use json.load()
# be sure to read dicts back in order (only matters for Python <3.7)
proglist = json.load(s, object_pairs_hook=OrderedDict)
else:
# input is string or bytes
# be sure to read dicts back in order (only matters for Python <3.7)
proglist = json.loads(s, object_pairs_hook=OrderedDict)
return proglist
[docs]def ch2list(ch: Union[List[int], int]) -> List[int]:
"""
convert a channel number or a list of ch numbers to list of integers
:param ch: channel number or list of channel numbers
:return: list of channel number(s)
"""
if ch is None:
return []
try:
ch_list = [int(ch)]
except TypeError:
ch_list = ch
return ch_list
[docs]def check_keys(keys, required, optional):
"""Check whether the keys defined for a pulse are supported and sufficient for this generator and pulse type.
Raise an exception if there is a problem.
Parameters
----------
params : set-like
Parameter keys defined for this pulse
required : list
Required keys (these must be present)
optional : list
Optional keys (these are not required, but may be present)
"""
required = set(required)
allowed = required | set(optional)
defined = set(keys)
if required - defined:
raise RuntimeError("missing required pulse parameter(s)", required - defined)
if defined - allowed:
raise RuntimeError("unsupported pulse parameter(s)", defined - allowed)