def calcNFGainIPLoss(self, coldfile_cal, hotfile_cal, coldfile, hotfile, ENR2dB, ENR12dB, iplossfile): # don't base noise figure on gain anymore after NF has been set this way self.resistive = False # import measurement data rbw, f_nf, p_hot = yf.loadcsv(hotfile) _, _, p_cold = yf.loadcsv(coldfile) _, fcal, cal_hot = yf.loadcsv(hotfile_cal) _, _, cal_cold = yf.loadcsv(coldfile_cal) # convert to Watts p_cold = dBm_to_W(p_cold) p_hot = dBm_to_W(p_hot) cal_cold = dBm_to_W(cal_cold) cal_hot = dBm_to_W(cal_hot) # input loss f2, sp = vna.loadcsv(iplossfile) iploss = np.abs(sp[:, 2]) iploss = np.interp(f_nf, f2, iploss) # smooth data if self.navg > 1: p_cold, trim = self.smooth(p_cold) p_hot, _ = self.smooth(p_hot) cal_cold, _ = self.smooth(cal_cold) cal_hot, _ = self.smooth(cal_hot) iploss, _ = self.smooth(iploss) f_nf = f_nf[trim:-trim] fcal = fcal[trim:-trim] # make sure calibration frequency space is the same as DUT measurement if len(fcal) != len(f_nf): cal_cold = np.interp(f_nf, fcal, cal_cold) cal_hot = np.interp(f_nf, fcal, cal_hot) # convert input loss to db iploss = 20 * np.log10(iploss) # calculate equivalent noise temperature nf, gain = yf.yfactor_cal_ipLoss(cal_cold, cal_hot, p_cold, p_hot, ENR2dB, ENR12dB, iploss) self.Teq = (10**(nf / 10) - 1) * 290 # interpolate self.gain = np.interp(self.fspace, f_nf, gain) self.Teq = np.interp(self.fspace, f_nf, self.Teq) # convert to noise figure in dB F = self.Teq / 290 + 1 self.nf = 10 * np.log10(F)
def calcNF(self, coldfile, hotfile, ENR_dB): # don't base noise figure on gain anymore after NF has been set this way self.resistive = False # import measurement data rbw, f_nf, p_hot = yf.loadcsv(hotfile) _, _, p_cold = yf.loadcsv(coldfile) # convert to Watts p_cold = dBm_to_W(p_cold) p_hot = dBm_to_W(p_hot) # smooth data if self.navg > 1: p_cold, trim = self.smooth(p_cold) p_hot, _ = self.smooth(p_hot) f_nf = f_nf[trim:-trim] # calculate equivalent noise temperature _, self.Teq = yf.yfactor_W(p_cold, p_hot, ENR_dB) # interpolate self.Teq = np.interp(self.fspace, f_nf, self.Teq) # convert to noise figure in dB F = self.Teq / 290 + 1 self.nf = 10 * np.log10(F)
def calcNFgainCal(self, coldfile, hotfile, ENR_dB, T_SA): # don't base noise figure on gain anymore after NF has been set this way self.resistive = False # import measurement data rbw, f_nf, p_hot = yf.loadcsv(hotfile) _, _, p_cold = yf.loadcsv(coldfile) # convert to Watts p_cold = dBm_to_W(p_cold) p_hot = dBm_to_W(p_hot) # smooth data if self.navg > 1: p_cold, trim = self.smooth(p_cold) p_hot, _ = self.smooth(p_hot) f_nf = f_nf[trim:-trim] # interpolate p_cold = np.interp(self.fspace, f_nf, p_cold) p_hot = np.interp(self.fspace, f_nf, p_hot) T_SA = np.interp(self.fspace, f_nf, T_SA) # calculate noise figure self.nf = yf.yfactor_cal_W2(p_cold, p_hot, T_SA, self.gain, ENR_dB) self.Teq = (10**(self.nf / 10) - 1) * 290
def timeDomainResponseWC(self, t, vin): if len(self.OIP3) == 0: self.calcCascade() # calculate power series gain coefficients from gain / OIP3 # for now, use the worst case OIP3 # TODO: take into account the OIP3 varying over frequency roi = (np.where((self.components[0].fspace >= 1300) & (self.components[0].fspace <= 1720)))[0] oip3 = min(self.OIP3[roi]) gain = min(self.G[roi]) print('worst case OIP3: ', oip3) print('worst case gain: ', gain) Z0 = 50 a1 = 10**(gain / 20) a3 = -2 * a1**3 / (3 * Z0 * yf.dBm_to_W(oip3)) # calculate nonlinear response dt = min(np.diff(t)) Fs = 1 / dt NFFT = len(vin) fmin = -1 / (2 * dt) fmax = 1 / (2 * dt) f = np.linspace(0, fmax, int(NFFT / 2)) df = Fs / NFFT S_in = psd_1sided(vin, Fs, NFFT) S_in = S_in[int(len(vin) / 2):] p_tot = sum(S_in) * df p_tot_dbm = 10 * np.log10(1000 * p_tot) print("total input power (dBm): ", p_tot_dbm) vout3 = a3 * vin**3 vout = a1 * vin + vout3 S_out = psd_1sided(vout, Fs, NFFT) S_out = S_out[int(len(vin) / 2):] S3 = psd_1sided(vout3, Fs, NFFT) S3 = S3[int(len(vin) / 2):] peak_psd = 10 * np.log10(1000 * max(S_out)) p_tot = sum(S_out) * df p_tot_dbm = 10 * np.log10(1000 * p_tot) print("total output power (dBm): ", p_tot_dbm) return f, S_out, S3, S_in
def timeDomainResponse(self, t, vin): if len(self.OIP3) == 0: self.calcCascade() fspace = self.components[0].fspace # calculate power series gain coefficients from gain / OIP3 a1f = 10**(self.G / 20) # TODO: use direct a3 measurements if available a3_est = -2 * 10**(1.5 * self.G / 10) / (3 * dBm_to_W(self.OIP3) * 50) a3f = a3_est # calculate nonlinear response dt = min(np.diff(t)) Fs = 1 / dt NFFT = len(vin) fmin = -1 / (2 * dt) fmax = 1 / (2 * dt) f = np.linspace(0, fmax, int(NFFT / 2)) df = Fs / NFFT S_in = psd_1sided(vin, Fs, NFFT) S_in = S_in[int(len(vin) / 2):] p_tot = sum(S_in) * df p_tot_dbm = 10 * np.log10(1000 * p_tot) print("total input power (dBm): ", p_tot_dbm) vout1, vout3 = timeDomainVoltage(vin, a1f, a3f, Fs, fspace) ## debug: # ~ plt.figure() # ~ plt.title('a3 voltage gain from component') # ~ plt.plot(fspace, a3f) # ~ plt.figure() # ~ plt.plot(ff, vin_f, label='input fft') # ~ plt.plot(ff, vin_f*A1f, label='output linear') # ~ plt.plot(ff, fft(vin**3)*A3f, label='output 3rd order') # ~ plt.plot(ff, A3f, label='a3 gain') # ~ plt.xlabel("frequency (Hz)") # ~ plt.ylabel("V/sqrt(Hz)") # ~ plt.legend() # ~ S_out_lin = psd_1sided(vout1, Fs, NFFT) # ~ S_out_lin = S_out_lin[int(len(vin)/2):] S3 = psd_1sided(vout3, Fs, NFFT) S3 = S3[int(len(vin) / 2):] # ~ plt.figure() # ~ plt.plot(f, 30+10*np.log10(S_in), label='input psd') # ~ plt.plot(f, 30+10*np.log10(S_out_lin), label='linear output psd') # ~ plt.plot(f, 30+10*np.log10(S3), label='3rd order output psd') # ~ plt.legend() #plt.show() # sum 1st and 3rd order outputs vout = vout1 + vout3 S_out = psd_1sided(vout, Fs, NFFT) S_out = S_out[int(len(vin) / 2):] peak_psd = 10 * np.log10(1000 * max(S_out)) p_tot = sum(S_out) * df p_tot_dbm = 10 * np.log10(1000 * p_tot) print("total output power (dBm): ", p_tot_dbm) return f, S_out, S3, S_in
def timeDomainStepByStepV(self, t, vin): fspace = self.components[0].fspace plot = False if plot: plt.figure() plt.title('component nonlinear contributions') #set up frequency space dt = min(np.diff(t)) Fs = 1 / dt NFFT = len(vin) fmin = -1 / (2 * dt) fmax = 1 / (2 * dt) f = np.linspace(0, fmax, int(NFFT / 2)) df = Fs / NFFT S_in = psd_1sided(vin, Fs, NFFT) S_in = S_in[int(len(vin) / 2):] Vout = vin #v1sum = np.zeros(len(vin)) v3sum = np.zeros(len(vin)) vout3 = np.zeros(len(vin)) oip3_hi = 100 # number to use for linear components (dBm) iip3_w = 10**((oip3_hi - 30) / 10) for c in self.components: # calculate power series gain coefficients from this component's gain/OIP3 a1f = 10**(c.gain / 20) if len(c.oip3) == 0: #c_oip3 = oip3_hi a3_est = [ ] # just set a3 to zero since we're modeling these devices as linear else: #c_oip3 = c.oip3 a3_est = -2 * 10**(1.5 * c.gain / 10) / (3 * dBm_to_W(c.oip3) * 50) a3f = a3_est # Vout is fed back in to the next component vout1, vout3 = timeDomainVoltage(Vout, a1f, a3f, Fs, fspace) Vout = vout1 + vout3 #v1sum = v1sum + vout1 v3sum = v3sum + vout3 if plot and (c.name != '') and (len(c.oip3) != 0): S3 = psd_1sided(vout3, Fs, NFFT) S3 = S3[int(len(vin) / 2):] S1 = psd_1sided(vout1, Fs, NFFT) S1 = S1[int(len(vin) / 2):] plt.plot(f * 1e-6, 30 + 10 * np.log10(S3), ':', label='3rd order (after ' + c.name + ')') plt.plot(f * 1e-6, 30 + 10 * np.log10(S1), label='linear (after ' + c.name + ')') if plot: plt.plot(f * 1e-6, 30 + 10 * np.log10(S_in), label='input') plt.legend(loc="upper right") plt.xlabel("frequency (MHz)") plt.ylabel("dBm/Hz") S_out = psd_1sided(Vout, Fs, NFFT) S_out = S_out[int(len(vin) / 2):] S3 = psd_1sided(v3sum, Fs, NFFT) S3 = S3[int(len(vin) / 2):] return f, S_out, S3, S_in, Vout
def intermodEstimate(self, rfiList, t): #set up frequency space dt = min(np.diff(t)) Fs = 1 / dt fmin = -1 / (2 * dt) fmax = 1 / (2 * dt) fnl = np.linspace(0, fmax, int(len(t) / 2)) df = Fs / len(t) rfi_freq = np.zeros(len(rfiList)) rfi_bw = np.zeros(len(rfiList)) rfi_pow = np.zeros(len(rfiList)) for ix in range(len(rfi_freq)): rfi_freq[ix] = rfiList[ix].fc rfi_bw[ix] = rfiList[ix].bw rfi_pow[ix] = rfiList[ix].power rfi_freq = np.concatenate((-np.array(rfi_freq), np.array(rfi_freq))) rfi_bw = np.concatenate((-np.array(rfi_bw), np.array(rfi_bw))) rfi_pow = dBm_to_W(np.array(rfi_pow)) rfi_pow = np.concatenate((rfi_pow, rfi_pow)) n = len(rfi_freq) # initialize output spectrum (input state) (1-sided spectrum) VSDout = np.zeros(int(len(t) / 2)) #S_out_est = np.ones(int(len(t)/2)) * dBm_to_W(-200) # rfi source input power for ix in range(len(rfi_freq)): if rfi_freq[ix] > 0: rng_fund = ( np.where((fnl >= rfi_freq[ix] - rfi_bw[ix] / 2) & (fnl <= rfi_freq[ix] + rfi_bw[ix] / 2)))[0] VSDout[rng_fund] = np.sqrt(2 * 50 * rfi_pow[ix] / np.abs(rfi_bw[ix])) #S_out_est[rng_fund] = rfi_pow[ix]/np.abs(rfi_bw[ix]) plot = False if plot: plt.figure() plt.title('component contributions, estimate') for c in self.components: # calculate power series gain coefficients from this component's gain/OIP3 a1f = 10**(c.gain / 20) # apply gain to whole spectrum in this case # it's ok that this is also affecting the fundamental before that's applied to IM power calculation, # because that calculation is using the array of RFI powers anyway (not the voltage spectrum) a1 = np.interp(fnl, c.fspace * 1e6, a1f) VSDout = VSDout * a1 if len(c.oip3) == 0: #c_oip3 = oip3_hi a3_est = [ ] # just set a3 to zero since we're modeling these devices as linear #S_out_est = S_out_est*a1**2 else: #c_oip3 = c.oip3 a3_est = -2 * 10**(1.5 * c.gain / 10) / (3 * dBm_to_W(c.oip3) * 50) a3f = a3_est # for every combination of 3 rfi sources for i1 in range(n): for i2 in range(n): for i3 in range(n): # sum frequency fsum = rfi_freq[i1] + rfi_freq[i2] + rfi_freq[i3] # calculate power for positive frequencies if fsum > 0: a3 = np.interp(fsum, c.fspace * 1e6, a3f) P = ( 50 / 2 * a3 )**2 * rfi_pow[i1] * rfi_pow[i2] * rfi_pow[i3] # calculate bandwidth BW = np.abs(rfi_bw[i1] + rfi_bw[i2] + rfi_bw[i3]) # modify output spectrum rng_im = ( np.where((fnl >= fsum - BW / 2) & (fnl <= fsum + BW / 2)))[0] VSDout[rng_im] = VSDout[rng_im] + np.sqrt( 2 * 50 * P / BW) #S_out_est[rng_im] = S_out_est[rng_im] + P/np.abs(BW) if plot and (c.name != '') and (len(c.oip3) != 0): Sp = VSDout**2 / (2 * 50) + dBm_to_W(-300) plt.plot(fnl * 1e-6, 30 + 10 * np.log10(Sp), '--', label='output after ' + c.name) # calculate fundamental gain from this component and update plot # ~ for ix in range(len(rfi_freq)): # ~ if rfi_freq[ix] > 0: # ~ a1 = np.interp(rfi_freq[ix], c.fspace*1e6, a1f) # ~ rng_fund = (np.where((fnl >= rfi_freq[ix]-rfi_bw[ix]/2) & (fnl <= rfi_freq[ix]+rfi_bw[ix]/2)))[0] # ~ VSDout[rng_fund] = VSDout[rng_fund]*a1 #S_out_est[rng_fund] = S_out_est[rng_fund]*a1**2 # update rfi source power with component gain for ix in range(len(rfi_freq)): a1 = np.interp(rfi_freq[ix], c.fspace * 1e6, a1f) rfi_pow[ix] = rfi_pow[ix] * a1**2 # find peak regions for plotting (max intermod?) if plot: plt.legend(loc='upper right') S_out_est = VSDout**2 / (2 * 50) + dBm_to_W(-300) return fnl, S_out_est