Source code for qugradlab.pulses.sampling

  1"""Methods and classes for producing an array of sample points."""
  2
  3import numpy as np
  4import tensorflow as tf
  5
  6import typing
  7
[docs] 8def get_sample_points(T: int, 9 number_sample_points: int 10 ) -> tuple[np.ndarray[np.float64], np.float64]: 11 """Generates an array of `number_sample_points` equally spaced sample points 12 between 0 and `T` and also returns the step size. 13 14 Parameters 15 ---------- 16 T : int 17 The final point. 18 number_sample_points : int 19 The number of sample points between 0 and `T`. 20 21 Returns 22 ------- 23 tuple[NDArray[Shape[``number_sample_points``], np.float64], np.float64] 24 A tuple containing the array of sample points and the step size. 25 """ 26 t = np.linspace(0, T, number_sample_points, dtype=np.float64) 27 return t, t[1]
28
[docs] 29class SampleTimes(): 30 """Allows for easy generation of equally spaced sample times from a variety 31 of input data 32 """ 33 34 _T: float 35 """The total time""" 36 37 _dt: float 38 """The time step""" 39 40 _number_sample_points: int 41 """The number of sample times between 0 and :attr:`T`""" 42 43 _t: np.ndarray[np.float64] 44 """An array of the sample times""" 45 46 _KEYWORDS = ["T", "dt", "number_sample_points"] 47 """The keywords that are allowed to be passed to the constructor""" 48
[docs] 49 def __init__(self, **kwargs): 50 """Initialises an instance of `SampleTimes`. Exactly two of the three 51 optional keyword arguments must be passed. 52 53 Parameters 54 ---------- 55 T : float, optional 56 The total time. 57 dt : float, optional 58 The time step. 59 number_sample_points : int, optional 60 The number of sample times between 0 and `T`. 61 62 Raises 63 ------ 64 TypeError 65 Raised when more or less than two keyword arguments are provided. 66 TypeError 67 Raised when a keyword arguement other than `T`, `dt`, or 68 `number_sample_points` is passed. 69 """ 70 if len(kwargs) != 2: 71 raise TypeError("Must be exactly two arguments.") 72 for key, value in kwargs.items(): 73 if key not in self._KEYWORDS: 74 raise TypeError(f"Only {self._KEYWORDS} are allowed arguments.") 75 setattr(self, "_"+key, value) 76 if "T" not in kwargs.keys(): 77 self._T = self._dt*(self._number_sample_points-1) 78 self._t = np.linspace(0, self._T, self._number_sample_points) 79 elif "dt" not in kwargs.keys(): 80 self._t, self._dt = get_sample_points(self._T, self._number_sample_points) 81 else: 82 assert np.floor(self._T/self._dt)*self._dt == self._T 83 self._number_sample_points = 1 + int(self._T/self._dt) 84 self._t = np.linspace(0, self._T, self._number_sample_points) 85 self._t.flags.writeable = False
86 @property 87 def T(self) -> float: 88 """ 89 The total time. If updated, so is :attr:`dt` while 90 :attr:`number_sample_points` is kept constant. 91 """ 92 return self._T 93 @T.setter 94 def T(self, value: float): 95 self._T = value 96 self._t, self._dt = get_sample_points(self._T, self._number_sample_points) 97 self._t.flags.writeable = False 98 @property 99 def dt(self) -> float: 100 """ 101 The time step. If updated, so is :attr:`T` while 102 :attr:`number_sample_points` is kept constant. 103 """ 104 return self._dt 105 @dt.setter 106 def dt(self, value: float): 107 self._dt = value 108 self._T = self._dt*(self._number_sample_points-1) 109 self._t = np.linspace(0, self._T, self._number_sample_points) 110 self._t.flags.writeable = False 111 @property 112 def number_sample_points(self) -> int: 113 """ 114 The number of sample times between 0 and `T`. If updated, so is 115 :attr:`dt` while :attr:`T` is kept constant.""" 116 return self._number_sample_points 117 @number_sample_points.setter 118 def number_sample_points(self, value: int): 119 self._number_sample_points = value 120 self._t, self._dt = get_sample_points(self._T, self._number_sample_points) 121 self._t.flags.writeable = False 122 @property 123 def t(self) -> np.ndarray[np.float64]: 124 """An array of the sample times""" 125 return self._t
126
[docs] 127def sample_from_piecewise_linear(signal: np.ndarray[complex], 128 fractional_indices: np.ndarray[float] 129 ) -> typing.Any: 130 """ 131 Samples from a piecewise linear signal. 132 133 Parameters 134 ---------- 135 signal : NDArray[Shape[n_points, Any_Shape], complex] 136 The piecewise linear signal to sample from 137 fractional_indices : NDArray[Shape[n_sample_points], complex] 138 Each entry will correspond to a sample. The integral part of each entry 139 corresponds to the index ``i`` in the signal and the fractional part 140 corresponds to the convex combination of the points at indices ``i`` and 141 ``i+1``. 142 143 Returns 144 ------- 145 NDArray[Shape[n_sample_points, ``signal.shape[1:]``], complex] 146 The sampled signal. 147 """ 148 ceil_indices = tf.cast(tf.math.ceil(fractional_indices), dtype=tf.int32) 149 floor_indices = tf.cast(tf.math.floor(fractional_indices), dtype=tf.int32) 150 sample_ceil = tf.gather(signal, ceil_indices) 151 sample_floor = tf.gather(signal, floor_indices) 152 return sample_floor \ 153 + tf.einsum("i,i...->i...", 154 fractional_indices % 1, 155 sample_ceil - sample_floor)