|
| 1 | +import numpy as np |
| 2 | + |
| 3 | +from tidy3d import constants as td_const |
| 4 | +from tidy3d.components.source.time import GaussianPulse |
| 5 | + |
| 6 | + |
| 7 | + |
| 8 | +def um_to_m(val): |
| 9 | + """um_to_m() is a simple function for convertion from [um] to [m]""" |
| 10 | + return val * 1e-6 |
| 11 | + |
| 12 | + |
| 13 | +class FreqRange: |
| 14 | + """ |
| 15 | + Convenience class for handling frequency/wavelength conversion and assignment |
| 16 | +
|
| 17 | + Example |
| 18 | + ------- |
| 19 | + >>> freq0 = 1e12 |
| 20 | + >>> fwidth = 1e11 |
| 21 | + >>> freq_range = FreqRange(freq0, fwidth) |
| 22 | + """ |
| 23 | + |
| 24 | + # constructor |
| 25 | + def __init__(self, freq0, fwidth): |
| 26 | + """ |
| 27 | + FreqRange constructor accepts central frequency "freq0" and one-sided bandwidth "fwidth" |
| 28 | + as input arguments; this constructor makes an assumption that freq0 is a central frequency, |
| 29 | + i.e. freq0 = (fmin + fmax) / 2; this implies that lda0 != (lda_min + lda_max) / 2 |
| 30 | + |
| 31 | + when class methods with wavelength parameters are called, |
| 32 | + "fmin" and "fmax" have to be reassigned inside those methods, as there the assumption is: |
| 33 | + lda0 = (lda_min + lda_max) / 2; this implies that freq0 != (fmin + fmax) / 2 |
| 34 | +
|
| 35 | + Parameters |
| 36 | + ---------- |
| 37 | + freq0 - central frequency [Hz] |
| 38 | + fwidth - one-sided bandwidth [Hz] |
| 39 | + """ |
| 40 | + |
| 41 | + if not isinstance(freq0, (int, float)): |
| 42 | + raise TypeError("Central frequency freq0 must be a number (int or float)") |
| 43 | + if not isinstance(fwidth, (int, float)): |
| 44 | + raise TypeError("Frequency bandwidth fwidth must be a number (int or float)") |
| 45 | + |
| 46 | + |
| 47 | + # assign input args |
| 48 | + self.freq0 = freq0 |
| 49 | + self.fwidth = fwidth |
| 50 | + |
| 51 | + # infer frequency range from central frequency and one-side bandwidth |
| 52 | + self.fmin = self.freq0 - self.fwidth |
| 53 | + self.fmax = self.freq0 + self.fwidth |
| 54 | + |
| 55 | + self.num_points = 1 # set default number of wavelengths/frequencies to 1 |
| 56 | + self.freqs = np.array([self.freq0]) # until num_points is updated set freqs to carrier frequency/central frequency |
| 57 | + self.lda0 = um_to_m(td_const.C_0 / self.freq0) # set central wavelength |
| 58 | + self.ldas = np.array([self.lda0]) # set wavelength range to central wavelength |
| 59 | + |
| 60 | + #----------------------------------- |
| 61 | + # class methods |
| 62 | + #----------------------------------- |
| 63 | + @classmethod |
| 64 | + def from_interval(clf, fmin, fmax): |
| 65 | + """ |
| 66 | + method from_interval() updated instance of class FreqRange by reassigning new |
| 67 | + frequency- and wavelength-related parameters; this method assumes, that the following |
| 68 | + definition is true: |
| 69 | +
|
| 70 | + freq0 = (fmin + fmax) / 2; it implies that lda0 != (lda_min + lda_max) / 2 |
| 71 | +
|
| 72 | + Input Parameters |
| 73 | + ---------------- |
| 74 | + fmin - the lowest frequency [Hz] |
| 75 | + fmax - the highest frequency [Hz] |
| 76 | +
|
| 77 | + Returns |
| 78 | + ------- |
| 79 | + updated instance of class FreqRange |
| 80 | +
|
| 81 | + Example |
| 82 | + ------- |
| 83 | + >>> fmin = 1e12 |
| 84 | + >>> fmax = 1e13 |
| 85 | + >>> freq_range = FreqRange.from_interval(fmin, fmax) |
| 86 | + """ |
| 87 | + |
| 88 | + if fmax < fmin: |
| 89 | + raise ValueError("fmax has to be higher than (or equal to) fmin") |
| 90 | + if fmin < 0: |
| 91 | + raise ValueError("fmin has to be a non-negative value") |
| 92 | + |
| 93 | + # extract frequency-related info |
| 94 | + freq0 = 0.5 * (fmax + fmin) # extract central freq |
| 95 | + fwidth = 0.5 * (fmax - fmin) # extract bandwidth |
| 96 | + return clf(freq0, fwidth) |
| 97 | + |
| 98 | + @classmethod |
| 99 | + def from_wavelength(clf, wvl0, wvl_width): |
| 100 | + """ |
| 101 | + method from_wavelength() updated instance of class FreqRange by reassigning new |
| 102 | + frequency- and wavelength-related parameters; this method assumes, that the following |
| 103 | + definition is true: |
| 104 | +
|
| 105 | + lda0 = (lda_min + lda_max) / 2; it implies that freq0 != (fmin + fmax) / 2 |
| 106 | +
|
| 107 | + Input Parameters |
| 108 | + ---------------- |
| 109 | + wvl0 - central wavelength [m] |
| 110 | + wvl_width - wavelength range [m] |
| 111 | +
|
| 112 | + Returns |
| 113 | + ------- |
| 114 | + updated instance of class FreqRange |
| 115 | +
|
| 116 | + Example |
| 117 | + ------- |
| 118 | + >>> wvl0 = 1e-6 |
| 119 | + >>> wvl_width = 1e-7 |
| 120 | + >>> freq_range = FreqRange.from_wavelength(wvl0, wvl_width) |
| 121 | + """ |
| 122 | + if wvl0 < wvl_width: |
| 123 | + raise ValueError("negative wavelengths are not allowed") |
| 124 | + if wvl_width <= 0: |
| 125 | + raise ValueError("wavelength range has to be a positive value") |
| 126 | + |
| 127 | + |
| 128 | + freq0 = um_to_m(td_const.C_0) / wvl0 |
| 129 | + |
| 130 | + fmin = um_to_m(td_const.C_0) / (wvl0 + wvl_width) |
| 131 | + fmax = um_to_m(td_const.C_0) / (wvl0 - wvl_width) |
| 132 | + |
| 133 | + fwidth = 0.5 * (fmax - fmin) |
| 134 | + |
| 135 | + freq_range = clf(freq0, fwidth) |
| 136 | + |
| 137 | + freq_range.fmin = fmin |
| 138 | + freq_range.fmax = fmax |
| 139 | + |
| 140 | + return freq_range |
| 141 | + |
| 142 | + @classmethod |
| 143 | + def from_wavelegth_interval(clf, wvl_min, wvl_max): |
| 144 | + """ |
| 145 | + method from_wavelegth_interval() updated instance of class FreqRange by reassigning new |
| 146 | + frequency- and wavelength-related parameters; this method assumes, that the following |
| 147 | + definition is true: |
| 148 | +
|
| 149 | + lda0 = (lda_min + lda_max) / 2; it implies that freq0 != (fmin + fmax) / 2 |
| 150 | +
|
| 151 | + Input Parameters |
| 152 | + ---------------- |
| 153 | + wvl_min - the shortest wavelength [m] |
| 154 | + wvl_max - the longest wavelength [m] |
| 155 | +
|
| 156 | + Returns |
| 157 | + ------- |
| 158 | + updated instance of class FreqRange |
| 159 | +
|
| 160 | + Example |
| 161 | + ------- |
| 162 | + >>> wvl_min = 1e-7 |
| 163 | + >>> wvl_max = 1e-6 |
| 164 | + >>> freq_range = FreqRange.from_wavelength(wvl_min, wvl_max) |
| 165 | + """ |
| 166 | + |
| 167 | + if wvl_max <= wvl_min: |
| 168 | + raise ValueError("longest wavelength must be greater than the shortest wavelength") |
| 169 | + if wvl_min <= 0: |
| 170 | + raise ValueError("wavelength has to be a positive value") |
| 171 | + |
| 172 | + # convert wavelength intervals to freq. range |
| 173 | + lda0 = 0.5 * (wvl_min + wvl_max) # get central wavelength |
| 174 | + freq0 = um_to_m(td_const.C_0 / lda0) # get central frequency |
| 175 | + fmax = um_to_m(td_const.C_0 / wvl_min) |
| 176 | + fmin = um_to_m(td_const.C_0 / wvl_max) |
| 177 | + fwidth = 0.5 * (fmax - fmin) # define width of source frequency range |
| 178 | + |
| 179 | + |
| 180 | + freq_range = clf(freq0, fwidth) |
| 181 | + |
| 182 | + freq_range.fmin = fmin |
| 183 | + freq_range.fmax = fmax |
| 184 | + return freq_range |
| 185 | + |
| 186 | + |
| 187 | + def samples(self, num_points): |
| 188 | + """ |
| 189 | + method samples() performes discretization of the frequency range by a |
| 190 | + given number of points "num_points"; frequency samples are uniformly |
| 191 | + distributed in ascending order (e.g. freqs = [3,4,...,100]Hz) |
| 192 | +
|
| 193 | + Parameters |
| 194 | + ---------- |
| 195 | + num_points - number of frequency points |
| 196 | +
|
| 197 | + Returns |
| 198 | + ------- |
| 199 | + freqs - a numpy array of uniformly distributed frequency samples in ascending order |
| 200 | + """ |
| 201 | + |
| 202 | + # update number of freq points |
| 203 | + self.num_points = num_points |
| 204 | + |
| 205 | + if num_points == 1: # if one sample frequency point is selected |
| 206 | + |
| 207 | + self.freqs = np.array([self.freq0]) |
| 208 | + self.ldas = np.array([self.lda0]) |
| 209 | + |
| 210 | + else: # otherwise |
| 211 | + |
| 212 | + if self.fmax <= self.fmin: |
| 213 | + raise ValueError("fmin has to be larger than fmin") |
| 214 | + if self.fmin <=0: |
| 215 | + raise ValueError("frequencies have to be positive values") |
| 216 | + |
| 217 | + # calculate frequency points and corresponding wavelengths |
| 218 | + self.freqs = np.linspace(self.fmin, self.fmax, self.num_points) |
| 219 | + |
| 220 | + # define shortest and longest wavelengths |
| 221 | + lmin = um_to_m(td_const.C_0 / self.fmax) |
| 222 | + lmax = um_to_m(td_const.C_0 / self.fmin) |
| 223 | + |
| 224 | + # update array of wavelengths (wavelengths would be in descending order) |
| 225 | + self.ldas = um_to_m(td_const.C_0 / self.freqs) |
| 226 | + |
| 227 | + return self.freqs |
| 228 | + |
| 229 | + def gaussian_pulse(self): |
| 230 | + """ |
| 231 | + method gaussian_pulse() returns instance of class GaussianPulse |
| 232 | + with central frequency freq0 and width of the source frequency fwidth |
| 233 | + """ |
| 234 | + |
| 235 | + # create an instance of GaussianPulse class with defined frequency params |
| 236 | + return GaussianPulse(freq0 = self.freq0, fwidth = self.fwidth) |
| 237 | + |
0 commit comments