def test_calc_tdiff(file_type="fitacf", password=True, tdiff_plot=None): '''Test calc_tdiff Parameters ----------- file_type : (str) Type of file to load (default="fitacf") password : (str, bool, NoneType) Password to access SuperDARN database (default=True) tdiff_plot : (matplotlib.figure.Figure or NoneType) figure handle for output or None to not produce a plot (default=None) Returns ----------- test_tdiff : (float) tdiff for 11.075-11.275 MHz at han on 13 Oct 2006 trange : (np.ndarray) Numpy array of tdiff values spanning 6 periods centred about test_tdiff ldist : (np.ndarray) Numpy array of latitude distribution values for each trange value. Can be used with trange to examine the function that was minimized to find test_tdiff Example ---------- In[1]: import davitpy.radar.tdiff as dtdiff In[5]: tt, trange, ldist = dtdiff.test_tdiff.test_calc_tdiff() --- shows simplex output --- In[6]: print tt 0.155995791387 ''' import datetime as dt import davitpy.pydarn.radar as pyrad import davitpy.pydarn.sdio as sdio import davitpy.pydarn.proc.fov as fov import davitpy.pydarn.radar.tdiff.bscatter_distribution as bs_dist # Set the radar beam selection criteria rad = 'han' radcp = -6401 stime = dt.datetime(2006, 10, 13, 15) etime = dt.datetime(2006, 10, 13, 15, 30) # Load the data try: rad_ptr = sdio.radDataRead.radDataOpen(stime, rad, eTime=etime, fileType=file_type, password=password, cp=radcp) except: print "Unable to load tdiff test data:\nRadar: {:s}".format(rad) print "Program: {:d}\nTime: {:} to {:}".format(radcp, stime, etime) return None, None, None hard = pyrad.site(code=rad, dt=stime) rad_bands = pyrad.tdiff.rad_freqbands.radFreqBands(rad) # Set the beam selection criteria tb = 3 bnum = 5 pmin = 10.0 rmin = 10 rmax = 25 tmins = [dt.datetime(2006, 10, 13, 15, 4), dt.datetime(2006, 10, 13, 15, 8), dt.datetime(2006, 10, 13, 15, 12)] tmaxs = [dt.datetime(2006, 10, 13, 15, 6),dt.datetime(2006, 10, 13, 15, 10), dt.datetime(2006, 10, 13, 15, 14)] sattr = ["slist", "time", "tfreq", "p_l", "phi0", "phi0e", "bmnum", "dist"] bm = rad_ptr.readRec() beams = list() while bm is not None: tband = rad_bands.get_tfreq_band_num(bm.prm.tfreq) if bm.bmnum == bnum and tband == tb: if hasattr(bm, "fit"): # Add attribute to beam bm.fit.dist = fov.update_backscatter.calc_distance(bm) beams.append(bm) bm = rad_ptr.readRec() sdata = pyrad.tdiff.calc_tdiff.select_bscatter(beams, sattr, radcp, tb, bnum, min_power=pmin, min_rg=rmin, max_rg=rmax, stimes=tmins, etimes=tmaxs) # Set the reference location tperiod = 1000.0 / rad_bands.get_mean_tband_freq(tb) ref_alt = 230.0 ref_lat = han_heater_field_line_lat(ref_alt, heater="tromso") ref_err = max(abs(han_heater_field_line_lat(np.array([ref_alt - 10.0, ref_alt + 10.0]), heater="tromso") - ref_lat)) ttol = 1.0e-4 fovflg = [1 for i in sdata["phi0"]] bm_az = [np.radians(hard.beamToAzim(b) - hard.boresite) for b in sdata['bmnum']] lat_args = (hard, sdata["phi0"], sdata["phi0e"], fovflg, bm_az, sdata["tfreq"], sdata['dist']) # Estimate tdiff tout = pyrad.tdiff.calc_tdiff.calc_tdiff(hard.tdiff, ref_lat, ref_err, lat_args, bs_dist.lat_distribution, 0.01, tdiff_tol=ttol, tperiod=tperiod) # Calculate the latitude distribution as a function of tdiff trange = np.arange(tout[0] - 3.0 * tperiod, tout[0] + 3.0 * tperiod, ttol) ldist = np.array([bs_dist.lat_distribution(t, *((ref_lat,) + lat_args)) for t in trange]) # If desired, create the figure showing the data selection results, # tdiff functional variations, and difference in the location distributions # using the initial and final tdiffs if tdiff_plot is not None: import matplotlib as mpl import matplotlib.pyplot as plt from davitpy.utils import calcDistPnt fov_dir = {1:"front", -1:"back"} try: sax = tdiff_plot.add_subplot(3,1,1) hax = tdiff_plot.add_subplot(2,2,4) tax = tdiff_plot.add_subplot(2,2,3) except: print "Unable to add subplots to figure" return tout[0], trange, ldist # Add the title stitle = "{:s} Beam {:d} on {:}\n".format(rad, bnum, stime.date()) stitle = "{:s}{:.3f}-{:.3f} MHz with {:d}".format(stitle, \ rad_bands.tmins[tb] / 1000.0, rad_bands.tmaxs[tb] / 1000.0, len(sdata['phi0'])) stitle = "{:s} heater backscatter observations".format(stitle) tdiff_plot.suptitle(stitle) # Extract all of the read in data for the selected beam sax_time = list() sax_range = list() sax_p = list() for bm in beams: for i,ss in enumerate(bm.fit.slist): sax_time.append(bm.time) sax_range.append(ss) sax_p.append(bm.fit.p_l[i]) # Identify selected data over RTI using power as intensity sax.scatter(sax_time, sax_range, c=sax_p, vmin=0, vmax=40, cmap=mpl.cm.get_cmap("Spectral_r"), marker="|", linewidth=14, s=45, zorder=2) sax.plot(sdata["time"], sdata["slist"], "k.", zorder=3) for i,ss in enumerate(tmins): p = mpl.patches.Rectangle((mpl.dates.date2num(ss), rmin), (tmaxs[i]-ss).total_seconds()/86400., rmax-rmin, color="0.6", zorder=1) sax.add_patch(p) sax.xaxis.set_major_formatter(mpl.dates.DateFormatter("%H:%M")) sax.xaxis.set_major_locator(mpl.dates.MinuteLocator(interval=5)) sax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(5)) sax.set_xlim(stime, etime) sax.set_ylim(rmin - 1, rmax + 1) sax.set_xlabel("Universal Time (HH:MM)") sax.set_ylabel("Range Gate") sax.set_axis_bgcolor("0.9") cb = fov.test_update_backscatter.add_colorbar(tdiff_plot, \ sax.collections[0], 0, 40, 5, name="Power", units="dB", loc=[.91, .67, .01, .22]) # Add the tdiff-lat dist function tax.plot(trange, ldist, "b-", linewidth=2) ymin, ymax = tax.get_ylim() tax.plot([hard.tdiff, hard.tdiff], [ymin, ymax], "k--", linewidth=2, label="Initial $\delta t_c$") tax.plot([tout[0], tout[0]], [ymin, ymax], "k-", linewidth=2, label="Estimated $\delta t_c$") tax.plot([tout[0]+tperiod, tout[0]+tperiod], [ymin, ymax], "-", c="0.6", linewidth=2, label="Estimated $\delta t_c$ $\pm$ T") tax.plot([tout[0]-tperiod, tout[0]-tperiod], [ymin, ymax], "-", c="0.6", linewidth=2) tax.plot([tout[0]+2.0*tperiod, tout[0]+2.0*tperiod], [ymin, ymax], "-", c="0.8", linewidth=2, label="Estimated $\delta t_c$ $\pm$ 2T") tax.plot([tout[0]-2.0*tperiod, tout[0]-2.0*tperiod], [ymin, ymax], "-", c="0.8", linewidth=2) tax.set_xlim(tout[0]-2.5*tperiod, tout[0]+2.5*tperiod) tax.set_ylim(ymin, ymax) tax.set_xlabel("$\delta t_c$ ($\mu s$)") tax.set_ylabel("g($\delta t_c$) ($^\circ$)") tax.legend(ncol=2, fontsize="xx-small", bbox_to_anchor=(1.1,1.33)) # Add the latitude histogram elv = np.array(fov.calc_elevation.calc_elv_list(sdata['phi0'], sdata['phi0e'], fovflg, bm_az, sdata['tfreq'], hard.interfer, hard.tdiff)) elv = np.degrees(elv) az = np.array([pyrad.radFov.calcAzOffBore(e, np.degrees(bm_az[i]), fov_dir[fovflg[i]]) + hard.boresite for i,e in enumerate(elv)]) loc = calcDistPnt(hard.geolat, hard.geolon, hard.alt, az=az, el=elv, dist=np.array(sdata['dist'])) hax.hist(loc['distLat'], 8, color="0.6", label="{:.3f}".format(hard.tdiff)) elv = np.array(fov.calc_elevation.calc_elv_list(sdata['phi0'], sdata['phi0e'], fovflg, bm_az, sdata['tfreq'], hard.interfer, tout[0])) elv = np.degrees(elv) az = np.array([pyrad.radFov.calcAzOffBore(e, np.degrees(bm_az[i]), fov_dir[fovflg[i]]) + hard.boresite for i,e in enumerate(elv)]) loc = calcDistPnt(hard.geolat, hard.geolon, hard.alt, az=az, el=elv, dist=np.array(sdata['dist'])) hax.hist(loc['distLat'], 8, color="c", alpha=.5, label="{:.3f}".format(tout[0])) # Set the main axis labels and limits xmin, xmax = hax.get_xlim() hax.set_ylim(0, 15) hax.set_ylabel("Counts") hax.yaxis.set_label_coords(-.08, .5) hax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(3)) hax.set_xlabel("Latitude ($^\circ$)") hax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(1)) hax.legend(ncol=2, fontsize="xx-small", title="$\delta t_c$ ($\mu s$)", bbox_to_anchor=(.85,1.33)) # Add the reference locations to a twin of the histogram plot hax2 = hax.twinx() line_alt = np.arange(0.0, 315.0, 15.0) line_lat = han_heater_field_line_lat(line_alt, heater="tromso") hax2.plot(line_lat, line_alt, "k-", linewidth=4) hax2.plot([xmin, xmax], [ref_alt, ref_alt], "k:") hax2.plot([ref_lat, ref_lat], [0, 300], "k--", linewidth=2) hax.set_xlim(xmin, xmax) hax2.set_ylabel("Altitude (km)") return tout[0], trange, ldist
def calc_elv(beam, phi0_attr="phi0", phi0_e_attr="phi0_e", hard=None, tdiff=None, alias=0.0, fov='front'): """Calculate the elevation angle for observations along a beam at a radar Parameters ----------- beam : (class `pydarn.sdio.radDataTypes.beamData`) Data for a single radar and beam along all range gates at a given time hard : (class `pydarn.radar.radStruct.site` or NoneType) Radar hardware data or None to load here (default=None) tdiff : (float or NoneType) The relative time delay of the signal paths from the interferometer array to the receiver and the main array to the reciver (microsec) or None to use the value supplied by the hardware file (default=None) alias : (float) Amount to offset the acceptable phase shifts by. The default phase shift range starts at the calculated max - 2 pi, any (positive) alias will remove 2 pi from the minimum allowable phase shift. (default=0.0) fov : (str) 'front' = Calculate elevation angles from front Field-of-View (fov); 'back' = Calculate elevation angle from back FoV. (default='front') Returns -------- elv : (np.array) A list of floats of the same size as the myBeam.fit.slist list, containing the new elevation angles for each range gate or NaN if an elevation angle could not be calculated phase_amb: (np.array) A list of integers containing the phase ambiguity integer used to alias the elevation angles hard : (class `pydarn.radar.radStruct.site` or NoneType) Radar hardware data or None to load here (default=None) """ import davitpy.pydarn.sdio as sdio import davitpy.pydarn.radar as pyrad #------------------------------------------------------------------------- # Test input assert isinstance(beam, sdio.radDataTypes.beamData), \ logging.error('the beam must be a beamData class') assert isinstance(phi0_attr, str) and hasattr(beam.fit, phi0_attr), \ logging.error('the phase lag data is not in this beam') assert isinstance(hard, pyrad.site) or hard is None, \ logging.error('supply the hardware class or None') assert isinstance(tdiff, float) or tdiff is None, \ logging.error('the tdiff should be a float or NoneType') assert isinstance(alias, float), \ logging.error('the alias number should be a float') assert(isinstance(fov, str) and (fov.find("front") >= 0 or fov.find("back") >= 0)), \ logging.error('the field-of-view must be "front" or "back"') # Only use this if the interferometer data was stored during this scan assert beam.prm.xcf == 1, \ logging.error('no interferometer data at this time') # Load the phase lag data phi0 = getattr(beam.fit, phi0_attr) # This method cannot be applied at Goose Bay or any other radar/beam that # does not include phi0 in their FIT output assert phi0 is not None, \ logging.error('phi0 missing from rad {:d} beam {:d}'.format(beam.stid, beam.bmnum)) # If possible, load the phase lag error data (not required to run) if hasattr(beam.fit, phi0_e_attr): phi0_e = getattr(beam.fit, phi0_e_attr) else: phi0_e = [1.0 for p in phi0] #------------------------------------------------------------------------- # Initialize output elv = np.empty(shape=(len(phi0),), dtype=float) * np.nan phase_amb = np.zeros(shape=(len(phi0),), dtype=int) #------------------------------------------------------------------------- # If desired, load the radar hardware data for the specified site and time if hard is None: hard = pyrad.site(radId=beam.stid, dt=beam.time) # Set the proper angle sign based on whether this is backlobe or # front lobe data if fov.find("front") == 0: back = 1.0 else: back = -1.0 # If desired, overwrite the hardware value for tdiff if tdiff is None: tdiff = hard.tdiff #------------------------------------------------------------------------- # Calculate the beam-specific variables # # Phi is the beam direction off the radar boresite assuming an elevation # angle of zero. This is calculated by the hardware routine 'beamToAzim' # located in pydarn.radar.radStruct bm_az = np.radians(hard.beamToAzim(beam.bmnum) - hard.boresite) cos_az = np.cos(bm_az) sin_az = np.sin(bm_az) az_sign = 1.0 if hard.interfer[1] > 0.0 else -1.0 # Find the elevation angle with the maximum phase lag el_max = np.arcsin(az_sign * hard.interfer[2] * cos_az / np.sqrt(hard.interfer[1]**2 + hard.interfer[2]**2)) if el_max < 0.0: el_max = 0.0 cos_max = np.cos(el_max) sin_max = np.sin(el_max) # k is the wavenumber in radians per meter k = 2.0 * np.pi * beam.prm.tfreq * 1.0e3 / scicon.c # Calculate the phase shift due to the radar cables del_chi = -np.pi * beam.prm.tfreq * tdiff * 2.0e-3 # Find the maximum possible phase shift chimax = k * (hard.interfer[0] * sin_az + hard.interfer[1] * np.sqrt(cos_max**2- sin_az**2) + hard.interfer[2] * sin_max) chimin = chimax - (alias + 1.0) * az_sign * 2.0 * np.pi #------------------------------------------------------------------------- # Calculate the elevation for each value of phi0 for i,p0 in enumerate(phi0): #--------------------------------------------------------------------- # Use only data where the angular drift between signals is not # identically zero with an error of zero, since this could either be a # sloppy flag denoting no data or an actual measurement. if p0 != 0.0 or phi0_e[i] != 0.0: # Find the right phase. This method works for both front and back # lobe calculations, unlike the more efficient method used by RST # in elevation.c: # phi_tempf = phi0 + 2.0*np.pi* np.floor(((chimax+del_chi)-phi0)/ # (2.0*np.pi)) # if phi_sign < 0.: # phi_tempf += 2.0 * np.pi # cos_thetaf = (phi_tempf - del_chi) / (k * asep) # sin2_deltaf = cos_phi**2 - cos_thetaf**2 # # They both yield the same elevation for front lobe calculations phi_temp = (back * (p0 - del_chi)) % (2.0 * np.pi) amb_temp = -np.floor(back * (p0 - del_chi) / (2.0 * np.pi)) # Ensure that phi_temp falls within the proper limits while phi_temp > max(chimax, chimin): phi_temp += az_sign * 2.0 * np.pi amb_temp += az_sign while abs(phi_temp) < abs(chimin): phi_temp += az_sign * 2.0 * np.pi amb_temp += az_sign #-------------------------- # Evaluate the phase shift if phi_temp > max(chimax, chimin): estr = "can't fix phase shift for beam {:d}".format(beam.bmnum) estr = "{:s} [{:f} not between ".format(estr, phi_temp) estr = "{:s}{:f},{:f} on ".format(estr, chimin, chimax) estr = "{:s}{:} at range gate {:d}".format(estr, beam.time, beam.fit.slist[i]) logging.critical(estr) else: # Calcualte the elevation angle and set if realistic cos_theta = phi_temp / k - hard.interfer[0] * sin_az yz_sum2 = hard.interfer[1]**2 + hard.interfer[2]**2 sin_delta = (cos_theta * hard.interfer[2] + np.sqrt((cos_theta * hard.interfer[2])**2 - yz_sum2 * (cos_theta**2 - (hard.interfer[1] * cos_az)**2))) / yz_sum2 if sin_delta <= 1.0: elv[i] = np.degrees(np.arcsin(sin_delta)) phase_amb[i] = int(amb_temp) return elv, phase_amb, hard
def calc_elv_w_err(beam, phi0_attr="phi0", phi0_e_attr="phi0_e", hard=None, bmaz_e=0.0, boresite_e=0.0, ix_e=0.0, iy_e=0.0, iz_e=0.0, tdiff=None, tdiff_e=0.0, alias=0.0, fov='front'): """Calculate the elevation angle for observations along a beam at a radar Parameters ----------- beam : (class `pydarn.sdio.radDataTypes.beamData`) Data for a single radar and beam along all range gates at a given time phi0_attr : (str) Name of the phase lag attribute in the beam.fit object (default="phi0") phi0_e_attr : (str) Name of the phase lag error attribute (default="phi0_e") hard : (class `pydarn.radar.radStruct.site` or NoneType) Radar hardware data or None to load here (default=None) bmaz_e : (float) Error in beam azimuth in degrees (default=0.0) boresite_e : (float) Error in the boresite location in degrees (default=0.0) ix_e : (float) Error in the interferometer x coordinate in meters (default=0.0) iy_e : (float) Error in the interferometer y coordinate in meters (default=0.0) iz_e : (float) Error in the interferometer z coordinate in meters (default=0.0) tdiff : (float or NoneType) The relative time delay of the signal paths from the interferometer array to the receiver and the main array to the reciver (microsec) or None to use the value supplied by the hardware file (default=None) tdiff_e : (float) Error in the tdiff value in microseconds (default=0.0) alias : (float) Amount to offset the acceptable phase shifts by. The default phase shift range starts at the calculated max - 2 pi, any (positive) alias will remove 2 pi from the minimum allowable phase shift. (default=0.0) fov : (str) 'front' = Calculate elevation angles from front Field-of-View (fov); 'back' = Calculate elevation angle from back FoV. (default='front') Returns -------- elv : (np.array) A list of floats of the same size as the myBeam.fit.slist list, containing the elevation angle for each range gate or NaN if an elevation angle could not be calculated. elv_e : (np.array) A list of floats of the same size as the myBeam.fit.slist list, containing the elevation angle errors for each range gate or NaN if an elevation angle error could not be calculated. phase_amb: (np.array) A list of integers containing the phase ambiguity integer used to alias the elevation angles hard : (class `pydarn.radar.radStruct.site` or NoneType) Radar hardware data or None to load here (default=None) """ import davitpy.pydarn.sdio as sdio import davitpy.pydarn.radar as pyrad #------------------------------------------------------------------------- # Test input assert isinstance(beam, sdio.radDataTypes.beamData), \ logging.error('the beam must be a beamData class') assert isinstance(phi0_attr, str) and hasattr(beam.fit, phi0_attr), \ logging.error('the phase lag data is not in this beam') assert isinstance(phi0_e_attr, str) and hasattr(beam.fit, phi0_attr), \ logging.error('the phase lag error data is not in this beam') assert isinstance(hard, pyrad.site) or hard is None, \ logging.error('supply the hardware class or None') assert isinstance(tdiff, float) or tdiff is None, \ logging.error('the tdiff should be a float or NoneType') assert isinstance(tdiff_e, float) or tdiff is None, \ logging.error('the tdiff should be a float or NoneType') assert isinstance(bmaz_e, float), \ logging.error('the beam azimuth error should be a float') assert isinstance(boresite_e, float), \ logging.error('the boresite error should be a float') assert isinstance(ix_e, float), \ logging.error('the interferometer x error should be a float') assert isinstance(iy_e, float), \ logging.error('the interferometer y error should be a float') assert isinstance(iz_e, float), \ logging.error('the interferometer z error should be a float') assert isinstance(alias, float), \ logging.error('the alias number should be a float') assert(isinstance(fov, str) and (fov.find("front") >= 0 or fov.find("back") >= 0)), \ logging.error('the field-of-view must be "front" or "back"') # Only use this if the interferometer data was stored during this scan assert beam.prm.xcf == 1, \ logging.error('no interferometer data at this time') # Load the phase lag data phi0 = getattr(beam.fit, phi0_attr) # This method cannot be applied at Goose Bay or any other radar/beam that # does not include phi0 in their FIT output assert phi0 is not None, \ logging.error('phi0 missing from rad {:d} beam {:d}'.format(beam.stid, beam.bmnum)) # If possible, load the phase lag error data (not required to run) if hasattr(beam.fit, phi0_e_attr): phi0_e = getattr(beam.fit, phi0_e_attr) else: phi0_e = [0.0 for p in phi0] # If any of the errors are NaN, set to zero if np.isnan(bmaz_e): bmaz_e = 0.0 if np.isnan(boresite_e): boresite_e = 0.0 if np.isnan(ix_e): ix_e = 0.0 if np.isnan(iy_e): iy_e = 0.0 if np.isnan(iz_e): iz_e = 0.0 if np.isnan(tdiff_e): tdiff_e = 0.0 #------------------------------------------------------------------------- # Initialize output elv = np.empty(shape=(len(phi0),), dtype=float) * np.nan elv_e = np.empty(shape=(len(phi0),), dtype=float) * np.nan phase_amb = np.zeros(shape=(len(phi0),), dtype=int) #------------------------------------------------------------------------- # If desired, load the radar hardware data for the specified site and time if hard is None: hard = pyrad.site(radId=beam.stid, dt=beam.time) # Set the proper angle sign based on whether this is backlobe or # front lobe data if fov.find("front") == 0: back = 1.0 else: back = -1.0 # If desired, overwrite the hardware value for tdiff if tdiff is None: tdiff = hard.tdiff #------------------------------------------------------------------------- # Calculate the beam-specific variables # # Phi is the beam direction off the radar boresite assuming an elevation # angle of zero. This is calculated by the hardware routine 'beamToAzim' # located in pydarn.radar.radStruct bm_az = np.radians(hard.beamToAzim(beam.bmnum) - hard.boresite) cos_az = np.cos(bm_az) sin_az = np.sin(bm_az) az_sign = 1.0 if hard.interfer[1] > 0.0 else -1.0 sig2_az = bmaz_e**2 + boresite_e**2 # Find the elevation angle with the maximum phase lag el_max = np.arcsin(az_sign * hard.interfer[2] * cos_az / np.sqrt(hard.interfer[1]**2 + hard.interfer[2]**2)) if el_max < 0.0: el_max = 0.0 cos_max = np.cos(el_max) sin_max = np.sin(el_max) # k is the wavenumber in radians per meter k = 2.0 * np.pi * beam.prm.tfreq * 1.0e3 / scicon.c # Calculate the phase-lag independent portion of the theta error theta2_base = sig2_az * (hard.interfer[0] * cos_az)**2 if ix_e > 0.0: theta2_base += (ix_e * sin_az)**2 if tdiff_e > 0.0: theta2_base += (scicon.c * tdiff_e * 1.0e-6)**2 # Calculate the phase shift due to the radar cables del_chi = -np.pi * beam.prm.tfreq * tdiff * 2.0e-3 # Find the maximum possible phase shift chimax = k * (hard.interfer[0] * sin_az + hard.interfer[1] * np.sqrt(cos_max**2- sin_az**2) + hard.interfer[2] * sin_max) chimin = chimax - (alias + 1.0) * az_sign * 2.0 * np.pi #------------------------------------------------------------------------- # Calculate the elevation for each value of phi0 for i,p0 in enumerate(phi0): #--------------------------------------------------------------------- # Use only data where the angular drift between signals is not # identically zero and the error is not NaN if p0 != 0.0 and not np.isnan(phi0_e[i]): # Find the right phase. This method works for both front and back # lobe calculations, unlike the more efficient method used by RST # in elevation.c: # phi_tempf = phi0 + 2.0*np.pi* np.floor(((chimax+del_chi)-phi0)/ # (2.0*np.pi)) # if phi_sign < 0.: # phi_tempf += 2.0 * np.pi # cos_thetaf = (phi_tempf - del_chi) / (k * asep) # sin2_deltaf = cos_phi**2 - cos_thetaf**2 # # They both yield the same elevation for front lobe calculations phi_temp = (back * (p0 - del_chi)) % (2.0 * np.pi) amb_temp = -np.floor(back * (p0 - del_chi) / (2.0 * np.pi)) # Ensure that phi_temp falls within the proper limits while phi_temp > max(chimax, chimin): phi_temp += az_sign * 2.0 * np.pi amb_temp += az_sign while abs(phi_temp) < abs(chimin): phi_temp += az_sign * 2.0 * np.pi amb_temp += az_sign #-------------------------- # Evaluate the phase shift if phi_temp > max(chimax, chimin): estr = "can't fix phase shift for beam {:d}".format(beam.bmnum) estr = "{:s} [{:f} not between ".format(estr, phi_temp) estr = "{:s}{:f},{:f} on ".format(estr, chimin, chimax) estr = "{:s}{:} at range gate {:d}".format(estr, beam.time, beam.fit.slist[i]) logging.critical(estr) else: # Calculate the elevation angle and set if realistic cos_theta = phi_temp / k - hard.interfer[0] * sin_az yz_sum2 = hard.interfer[1]**2 + hard.interfer[2]**2 yscale2 = ((cos_theta * hard.interfer[2])**2 - yz_sum2 * (cos_theta**2 - (hard.interfer[1] * cos_az)**2)) sin_delta = (cos_theta * hard.interfer[2] + np.sqrt(yscale2)) / yz_sum2 if sin_delta <= 1.0: # Calculate the elevation elv[i] = np.degrees(np.arcsin(sin_delta)) phase_amb[i] = int(amb_temp) # Now that the elevation was calculated, find the # elevation error, using the propagation of error to # propagate the provided uncertainties asin_der = 1.0 / np.sqrt(1.0 - sin_delta**2) # If the azimuthal error is not zero, calculate derivative if sig2_az > 0.0: az_term2 = (cos_az * sin_az)**2 / yscale2 az_term2 *= np.power(hard.interfer[1], 4) * sig2_az else: az_term2 = 0.0 # If there is an error in the phase lag, add this to the # theta sigma and then calculate the theta term if phi0_e[i] > 0.0: theta_term2 = theta2_base + (phi0_e[i] / k)**2 else: theta_term2 = theta2_base if theta_term2 > 0.0: theta_term2 *= ((hard.interfer[2] - hard.interfer[1]**2 * cos_theta / np.sqrt(yscale2)) / yz_sum2)**2 # If the interferometer Y error is not zero, get derivative if iy_e > 0.0: iy_term2 = (yz_sum2 - 2.0 * cos_theta * hard.interfer[1] * hard.interfer[2] - 2.0 * hard.interfer[1] * np.sqrt(yscale2) + hard.interfer[1]**2 * yz_sum2 * (yz_sum2 * cos_az**2 - cos_theta**2) / np.sqrt(yscale2))**2 / np.power(yz_sum2, 4) iy_term2 *= iy_e**2 else: iy_term2 = 0.0 # If the interferometer Z error is not zero, get derivative if iz_e > 0.0: iz_term2 = ((yz_sum2 * (cos_theta + hard.interfer[2] * (hard.interfer[1] * cos_az)**2 / np.sqrt(yscale2)) - 2.0 * hard.interfer[2] * np.sqrt(yscale2)) * iz_e)**2 / np.power(yz_sum2, 4) else: iz_term2 = 0.0 # Calculate the elevation error, adding in quadrature temp = np.sqrt(az_term2 + theta_term2 + iy_term2 + iz_term2) # If the elevation error is identically zero, assume # that no error could be obtained elv_e[i] = np.nan if temp == 0.0 else temp return elv, elv_e, phase_amb, hard
def test_calc_tdiff(file_type="fitacf", password=True, tdiff_plot=None): '''Test calc_tdiff Parameters ----------- file_type : (str) Type of file to load (default="fitacf") password : (str, bool, NoneType) Password to access SuperDARN database (default=True) tdiff_plot : (matplotlib.figure.Figure or NoneType) figure handle for output or None to not produce a plot (default=None) Returns ----------- test_tdiff : (float) tdiff for 11.075-11.275 MHz at han on 13 Oct 2006 trange : (np.ndarray) Numpy array of tdiff values spanning 6 periods centred about test_tdiff ldist : (np.ndarray) Numpy array of latitude distribution values for each trange value. Can be used with trange to examine the function that was minimized to find test_tdiff Example ---------- In[1]: import davitpy.radar.tdiff as dtdiff In[5]: tt, trange, ldist = dtdiff.test_tdiff.test_calc_tdiff() --- shows simplex output --- In[6]: print tt 0.155995791387 ''' import datetime as dt import davitpy.pydarn.radar as pyrad import davitpy.pydarn.sdio as sdio import davitpy.pydarn.proc.fov as fov import davitpy.pydarn.radar.tdiff.bscatter_distribution as bs_dist # Set the radar beam selection criteria rad = 'han' radcp = -6401 stime = dt.datetime(2006, 10, 13, 15) etime = dt.datetime(2006, 10, 13, 15, 30) # Load the data try: rad_ptr = sdio.radDataRead.radDataOpen(stime, rad, eTime=etime, fileType=file_type, password=password, cp=radcp) except: print "Unable to load tdiff test data:\nRadar: {:s}".format(rad) print "Program: {:d}\nTime: {:} to {:}".format(radcp, stime, etime) return None, None, None hard = pyrad.site(code=rad, dt=stime) rad_bands = pyrad.tdiff.rad_freqbands.radFreqBands(rad) # Set the beam selection criteria tb = 3 bnum = 5 pmin = 10.0 rmin = 10 rmax = 25 tmins = [ dt.datetime(2006, 10, 13, 15, 4), dt.datetime(2006, 10, 13, 15, 8), dt.datetime(2006, 10, 13, 15, 12) ] tmaxs = [ dt.datetime(2006, 10, 13, 15, 6), dt.datetime(2006, 10, 13, 15, 10), dt.datetime(2006, 10, 13, 15, 14) ] sattr = ["slist", "time", "tfreq", "p_l", "phi0", "phi0e", "bmnum", "dist"] bm = rad_ptr.readRec() beams = list() while bm is not None: tband = rad_bands.get_tfreq_band_num(bm.prm.tfreq) if bm.bmnum == bnum and tband == tb: if hasattr(bm, "fit"): # Add attribute to beam bm.fit.dist = fov.update_backscatter.calc_distance(bm) beams.append(bm) bm = rad_ptr.readRec() sdata = pyrad.tdiff.calc_tdiff.select_bscatter(beams, sattr, radcp, tb, bnum, min_power=pmin, min_rg=rmin, max_rg=rmax, stimes=tmins, etimes=tmaxs) # Set the reference location tperiod = 1000.0 / rad_bands.get_mean_tband_freq(tb) ref_alt = 230.0 ref_lat = han_heater_field_line_lat(ref_alt, heater="tromso") ref_err = max( abs( han_heater_field_line_lat( np.array([ref_alt - 10.0, ref_alt + 10.0]), heater="tromso") - ref_lat)) ttol = 1.0e-4 fovflg = [1 for i in sdata["phi0"]] bm_az = [ np.radians(hard.beamToAzim(b) - hard.boresite) for b in sdata['bmnum'] ] lat_args = (hard, sdata["phi0"], sdata["phi0e"], fovflg, bm_az, sdata["tfreq"], sdata['dist']) # Estimate tdiff tout = pyrad.tdiff.calc_tdiff.calc_tdiff(hard.tdiff, ref_lat, ref_err, lat_args, bs_dist.lat_distribution, 0.01, tdiff_tol=ttol, tperiod=tperiod) # Calculate the latitude distribution as a function of tdiff trange = np.arange(tout[0] - 3.0 * tperiod, tout[0] + 3.0 * tperiod, ttol) ldist = np.array([ bs_dist.lat_distribution(t, *((ref_lat, ) + lat_args)) for t in trange ]) # If desired, create the figure showing the data selection results, # tdiff functional variations, and difference in the location distributions # using the initial and final tdiffs if tdiff_plot is not None: import matplotlib as mpl import matplotlib.pyplot as plt from davitpy.utils import calcDistPnt fov_dir = {1: "front", -1: "back"} try: sax = tdiff_plot.add_subplot(3, 1, 1) hax = tdiff_plot.add_subplot(2, 2, 4) tax = tdiff_plot.add_subplot(2, 2, 3) except: print "Unable to add subplots to figure" return tout[0], trange, ldist # Add the title stitle = "{:s} Beam {:d} on {:}\n".format(rad, bnum, stime.date()) stitle = "{:s}{:.3f}-{:.3f} MHz with {:d}".format(stitle, \ rad_bands.tmins[tb] / 1000.0, rad_bands.tmaxs[tb] / 1000.0, len(sdata['phi0'])) stitle = "{:s} heater backscatter observations".format(stitle) tdiff_plot.suptitle(stitle) # Extract all of the read in data for the selected beam sax_time = list() sax_range = list() sax_p = list() for bm in beams: for i, ss in enumerate(bm.fit.slist): sax_time.append(bm.time) sax_range.append(ss) sax_p.append(bm.fit.p_l[i]) # Identify selected data over RTI using power as intensity sax.scatter(sax_time, sax_range, c=sax_p, vmin=0, vmax=40, cmap=mpl.cm.get_cmap("Spectral_r"), marker="|", linewidth=14, s=45, zorder=2) sax.plot(sdata["time"], sdata["slist"], "k.", zorder=3) for i, ss in enumerate(tmins): p = mpl.patches.Rectangle((mpl.dates.date2num(ss), rmin), (tmaxs[i] - ss).total_seconds() / 86400., rmax - rmin, color="0.6", zorder=1) sax.add_patch(p) sax.xaxis.set_major_formatter(mpl.dates.DateFormatter("%H:%M")) sax.xaxis.set_major_locator(mpl.dates.MinuteLocator(interval=5)) sax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(5)) sax.set_xlim(stime, etime) sax.set_ylim(rmin - 1, rmax + 1) sax.set_xlabel("Universal Time (HH:MM)") sax.set_ylabel("Range Gate") sax.set_axis_bgcolor("0.9") cb = fov.test_update_backscatter.add_colorbar(tdiff_plot, \ sax.collections[0], 0, 40, 5, name="Power", units="dB", loc=[.91, .67, .01, .22]) # Add the tdiff-lat dist function tax.plot(trange, ldist, "b-", linewidth=2) ymin, ymax = tax.get_ylim() tax.plot([hard.tdiff, hard.tdiff], [ymin, ymax], "k--", linewidth=2, label="Initial $\delta t_c$") tax.plot([tout[0], tout[0]], [ymin, ymax], "k-", linewidth=2, label="Estimated $\delta t_c$") tax.plot([tout[0] + tperiod, tout[0] + tperiod], [ymin, ymax], "-", c="0.6", linewidth=2, label="Estimated $\delta t_c$ $\pm$ T") tax.plot([tout[0] - tperiod, tout[0] - tperiod], [ymin, ymax], "-", c="0.6", linewidth=2) tax.plot([tout[0] + 2.0 * tperiod, tout[0] + 2.0 * tperiod], [ymin, ymax], "-", c="0.8", linewidth=2, label="Estimated $\delta t_c$ $\pm$ 2T") tax.plot([tout[0] - 2.0 * tperiod, tout[0] - 2.0 * tperiod], [ymin, ymax], "-", c="0.8", linewidth=2) tax.set_xlim(tout[0] - 2.5 * tperiod, tout[0] + 2.5 * tperiod) tax.set_ylim(ymin, ymax) tax.set_xlabel("$\delta t_c$ ($\mu s$)") tax.set_ylabel("g($\delta t_c$) ($^\circ$)") tax.legend(ncol=2, fontsize="xx-small", bbox_to_anchor=(1.1, 1.33)) # Add the latitude histogram elv = np.array( fov.calc_elevation.calc_elv_list(sdata['phi0'], sdata['phi0e'], fovflg, bm_az, sdata['tfreq'], hard.interfer, hard.tdiff)) elv = np.degrees(elv) az = np.array([ pyrad.radFov.calcAzOffBore(e, np.degrees(bm_az[i]), fov_dir[fovflg[i]]) + hard.boresite for i, e in enumerate(elv) ]) loc = calcDistPnt(hard.geolat, hard.geolon, hard.alt, az=az, el=elv, dist=np.array(sdata['dist'])) hax.hist(loc['distLat'], 8, color="0.6", label="{:.3f}".format(hard.tdiff)) elv = np.array( fov.calc_elevation.calc_elv_list(sdata['phi0'], sdata['phi0e'], fovflg, bm_az, sdata['tfreq'], hard.interfer, tout[0])) elv = np.degrees(elv) az = np.array([ pyrad.radFov.calcAzOffBore(e, np.degrees(bm_az[i]), fov_dir[fovflg[i]]) + hard.boresite for i, e in enumerate(elv) ]) loc = calcDistPnt(hard.geolat, hard.geolon, hard.alt, az=az, el=elv, dist=np.array(sdata['dist'])) hax.hist(loc['distLat'], 8, color="c", alpha=.5, label="{:.3f}".format(tout[0])) # Set the main axis labels and limits xmin, xmax = hax.get_xlim() hax.set_ylim(0, 15) hax.set_ylabel("Counts") hax.yaxis.set_label_coords(-.08, .5) hax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(3)) hax.set_xlabel("Latitude ($^\circ$)") hax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(1)) hax.legend(ncol=2, fontsize="xx-small", title="$\delta t_c$ ($\mu s$)", bbox_to_anchor=(.85, 1.33)) # Add the reference locations to a twin of the histogram plot hax2 = hax.twinx() line_alt = np.arange(0.0, 315.0, 15.0) line_lat = han_heater_field_line_lat(line_alt, heater="tromso") hax2.plot(line_lat, line_alt, "k-", linewidth=4) hax2.plot([xmin, xmax], [ref_alt, ref_alt], "k:") hax2.plot([ref_lat, ref_lat], [0, 300], "k--", linewidth=2) hax.set_xlim(xmin, xmax) hax2.set_ylabel("Altitude (km)") return tout[0], trange, ldist
def calc_elv(beam, phi0_attr="phi0", phi0_e_attr="phi0_e", hard=None, tdiff=None, alias=0.0, fov='front'): """Calculate the elevation angle for observations along a beam at a radar Parameters ----------- beam : (class `pydarn.sdio.radDataTypes.beamData`) Data for a single radar and beam along all range gates at a given time hard : (class `pydarn.radar.radStruct.site` or NoneType) Radar hardware data or None to load here (default=None) tdiff : (float or NoneType) The relative time delay of the signal paths from the interferometer array to the receiver and the main array to the reciver (microsec) or None to use the value supplied by the hardware file (default=None) alias : (float) Amount to offset the acceptable phase shifts by. The default phase shift range starts at the calculated max - 2 pi, any (positive) alias will remove 2 pi from the minimum allowable phase shift. (default=0.0) fov : (str) 'front' = Calculate elevation angles from front Field-of-View (fov); 'back' = Calculate elevation angle from back FoV. (default='front') Returns -------- elv : (np.array) A list of floats of the same size as the myBeam.fit.slist list, containing the new elevation angles for each range gate or NaN if an elevation angle could not be calculated phase_amb: (np.array) A list of integers containing the phase ambiguity integer used to alias the elevation angles hard : (class `pydarn.radar.radStruct.site` or NoneType) Radar hardware data or None to load here (default=None) """ import davitpy.pydarn.sdio as sdio import davitpy.pydarn.radar as pyrad #------------------------------------------------------------------------- # Test input assert isinstance(beam, sdio.radDataTypes.beamData), \ logging.error('the beam must be a beamData class') assert isinstance(phi0_attr, str) and hasattr(beam.fit, phi0_attr), \ logging.error('the phase lag data is not in this beam') assert isinstance(hard, pyrad.site) or hard is None, \ logging.error('supply the hardware class or None') assert isinstance(tdiff, float) or tdiff is None, \ logging.error('the tdiff should be a float or NoneType') assert isinstance(alias, float), \ logging.error('the alias number should be a float') assert(isinstance(fov, str) and (fov.find("front") >= 0 or fov.find("back") >= 0)), \ logging.error('the field-of-view must be "front" or "back"') # Only use this if the interferometer data was stored during this scan assert beam.prm.xcf == 1, \ logging.error('no interferometer data at this time') # Load the phase lag data phi0 = getattr(beam.fit, phi0_attr) # This method cannot be applied at Goose Bay or any other radar/beam that # does not include phi0 in their FIT output assert phi0 is not None, \ logging.error('phi0 missing from rad {:d} beam {:d}'.format(beam.stid, beam.bmnum)) # If possible, load the phase lag error data (not required to run) if hasattr(beam.fit, phi0_e_attr): phi0_e = getattr(beam.fit, phi0_e_attr) else: phi0_e = [1.0 for p in phi0] #------------------------------------------------------------------------- # Initialize output elv = np.empty(shape=(len(phi0), ), dtype=float) * np.nan phase_amb = np.zeros(shape=(len(phi0), ), dtype=int) #------------------------------------------------------------------------- # If desired, load the radar hardware data for the specified site and time if hard is None: hard = pyrad.site(radId=beam.stid, dt=beam.time) # Set the proper angle sign based on whether this is backlobe or # front lobe data if fov.find("front") == 0: back = 1.0 else: back = -1.0 # If desired, overwrite the hardware value for tdiff if tdiff is None: tdiff = hard.tdiff #------------------------------------------------------------------------- # Calculate the beam-specific variables # # Phi is the beam direction off the radar boresite assuming an elevation # angle of zero. This is calculated by the hardware routine 'beamToAzim' # located in pydarn.radar.radStruct bm_az = np.radians(hard.beamToAzim(beam.bmnum) - hard.boresite) cos_az = np.cos(bm_az) sin_az = np.sin(bm_az) az_sign = 1.0 if hard.interfer[1] > 0.0 else -1.0 # Find the elevation angle with the maximum phase lag el_max = np.arcsin(az_sign * hard.interfer[2] * cos_az / np.sqrt(hard.interfer[1]**2 + hard.interfer[2]**2)) if el_max < 0.0: el_max = 0.0 cos_max = np.cos(el_max) sin_max = np.sin(el_max) # k is the wavenumber in radians per meter k = 2.0 * np.pi * beam.prm.tfreq * 1.0e3 / scicon.c # Calculate the phase shift due to the radar cables del_chi = -np.pi * beam.prm.tfreq * tdiff * 2.0e-3 # Find the maximum possible phase shift chimax = k * (hard.interfer[0] * sin_az + hard.interfer[1] * np.sqrt(cos_max**2 - sin_az**2) + hard.interfer[2] * sin_max) chimin = chimax - (alias + 1.0) * az_sign * 2.0 * np.pi #------------------------------------------------------------------------- # Calculate the elevation for each value of phi0 for i, p0 in enumerate(phi0): #--------------------------------------------------------------------- # Use only data where the angular drift between signals is not # identically zero with an error of zero, since this could either be a # sloppy flag denoting no data or an actual measurement. if p0 != 0.0 or phi0_e[i] != 0.0: # Find the right phase. This method works for both front and back # lobe calculations, unlike the more efficient method used by RST # in elevation.c: # phi_tempf = phi0 + 2.0*np.pi* np.floor(((chimax+del_chi)-phi0)/ # (2.0*np.pi)) # if phi_sign < 0.: # phi_tempf += 2.0 * np.pi # cos_thetaf = (phi_tempf - del_chi) / (k * asep) # sin2_deltaf = cos_phi**2 - cos_thetaf**2 # # They both yield the same elevation for front lobe calculations phi_temp = (back * (p0 - del_chi)) % (2.0 * np.pi) amb_temp = -np.floor(back * (p0 - del_chi) / (2.0 * np.pi)) # Ensure that phi_temp falls within the proper limits while phi_temp > max(chimax, chimin): phi_temp += az_sign * 2.0 * np.pi amb_temp += az_sign while abs(phi_temp) < abs(chimin): phi_temp += az_sign * 2.0 * np.pi amb_temp += az_sign #-------------------------- # Evaluate the phase shift if phi_temp > max(chimax, chimin): estr = "can't fix phase shift for beam {:d}".format(beam.bmnum) estr = "{:s} [{:f} not between ".format(estr, phi_temp) estr = "{:s}{:f},{:f} on ".format(estr, chimin, chimax) estr = "{:s}{:} at range gate {:d}".format( estr, beam.time, beam.fit.slist[i]) logging.critical(estr) else: # Calcualte the elevation angle and set if realistic cos_theta = phi_temp / k - hard.interfer[0] * sin_az yz_sum2 = hard.interfer[1]**2 + hard.interfer[2]**2 sin_delta = (cos_theta * hard.interfer[2] + np.sqrt( (cos_theta * hard.interfer[2])**2 - yz_sum2 * (cos_theta**2 - (hard.interfer[1] * cos_az)**2))) / yz_sum2 if sin_delta <= 1.0: elv[i] = np.degrees(np.arcsin(sin_delta)) phase_amb[i] = int(amb_temp) return elv, phase_amb, hard
def calc_elv_w_err(beam, phi0_attr="phi0", phi0_e_attr="phi0_e", hard=None, bmaz_e=0.0, boresite_e=0.0, ix_e=0.0, iy_e=0.0, iz_e=0.0, tdiff=None, tdiff_e=0.0, alias=0.0, fov='front'): """Calculate the elevation angle for observations along a beam at a radar Parameters ----------- beam : (class `pydarn.sdio.radDataTypes.beamData`) Data for a single radar and beam along all range gates at a given time phi0_attr : (str) Name of the phase lag attribute in the beam.fit object (default="phi0") phi0_e_attr : (str) Name of the phase lag error attribute (default="phi0_e") hard : (class `pydarn.radar.radStruct.site` or NoneType) Radar hardware data or None to load here (default=None) bmaz_e : (float) Error in beam azimuth in degrees (default=0.0) boresite_e : (float) Error in the boresite location in degrees (default=0.0) ix_e : (float) Error in the interferometer x coordinate in meters (default=0.0) iy_e : (float) Error in the interferometer y coordinate in meters (default=0.0) iz_e : (float) Error in the interferometer z coordinate in meters (default=0.0) tdiff : (float or NoneType) The relative time delay of the signal paths from the interferometer array to the receiver and the main array to the reciver (microsec) or None to use the value supplied by the hardware file (default=None) tdiff_e : (float) Error in the tdiff value in microseconds (default=0.0) alias : (float) Amount to offset the acceptable phase shifts by. The default phase shift range starts at the calculated max - 2 pi, any (positive) alias will remove 2 pi from the minimum allowable phase shift. (default=0.0) fov : (str) 'front' = Calculate elevation angles from front Field-of-View (fov); 'back' = Calculate elevation angle from back FoV. (default='front') Returns -------- elv : (np.array) A list of floats of the same size as the myBeam.fit.slist list, containing the elevation angle for each range gate or NaN if an elevation angle could not be calculated. elv_e : (np.array) A list of floats of the same size as the myBeam.fit.slist list, containing the elevation angle errors for each range gate or NaN if an elevation angle error could not be calculated. phase_amb: (np.array) A list of integers containing the phase ambiguity integer used to alias the elevation angles hard : (class `pydarn.radar.radStruct.site` or NoneType) Radar hardware data or None to load here (default=None) """ import davitpy.pydarn.sdio as sdio import davitpy.pydarn.radar as pyrad #------------------------------------------------------------------------- # Test input assert isinstance(beam, sdio.radDataTypes.beamData), \ logging.error('the beam must be a beamData class') assert isinstance(phi0_attr, str) and hasattr(beam.fit, phi0_attr), \ logging.error('the phase lag data is not in this beam') assert isinstance(phi0_e_attr, str) and hasattr(beam.fit, phi0_attr), \ logging.error('the phase lag error data is not in this beam') assert isinstance(hard, pyrad.site) or hard is None, \ logging.error('supply the hardware class or None') assert isinstance(tdiff, float) or tdiff is None, \ logging.error('the tdiff should be a float or NoneType') assert isinstance(tdiff_e, float) or tdiff is None, \ logging.error('the tdiff should be a float or NoneType') assert isinstance(bmaz_e, float), \ logging.error('the beam azimuth error should be a float') assert isinstance(boresite_e, float), \ logging.error('the boresite error should be a float') assert isinstance(ix_e, float), \ logging.error('the interferometer x error should be a float') assert isinstance(iy_e, float), \ logging.error('the interferometer y error should be a float') assert isinstance(iz_e, float), \ logging.error('the interferometer z error should be a float') assert isinstance(alias, float), \ logging.error('the alias number should be a float') assert(isinstance(fov, str) and (fov.find("front") >= 0 or fov.find("back") >= 0)), \ logging.error('the field-of-view must be "front" or "back"') # Only use this if the interferometer data was stored during this scan assert beam.prm.xcf == 1, \ logging.error('no interferometer data at this time') # Load the phase lag data phi0 = getattr(beam.fit, phi0_attr) # This method cannot be applied at Goose Bay or any other radar/beam that # does not include phi0 in their FIT output assert phi0 is not None, \ logging.error('phi0 missing from rad {:d} beam {:d}'.format(beam.stid, beam.bmnum)) # If possible, load the phase lag error data (not required to run) if hasattr(beam.fit, phi0_e_attr): phi0_e = getattr(beam.fit, phi0_e_attr) else: phi0_e = [0.0 for p in phi0] # If any of the errors are NaN, set to zero if np.isnan(bmaz_e): bmaz_e = 0.0 if np.isnan(boresite_e): boresite_e = 0.0 if np.isnan(ix_e): ix_e = 0.0 if np.isnan(iy_e): iy_e = 0.0 if np.isnan(iz_e): iz_e = 0.0 if np.isnan(tdiff_e): tdiff_e = 0.0 #------------------------------------------------------------------------- # Initialize output elv = np.empty(shape=(len(phi0), ), dtype=float) * np.nan elv_e = np.empty(shape=(len(phi0), ), dtype=float) * np.nan phase_amb = np.zeros(shape=(len(phi0), ), dtype=int) #------------------------------------------------------------------------- # If desired, load the radar hardware data for the specified site and time if hard is None: hard = pyrad.site(radId=beam.stid, dt=beam.time) # Set the proper angle sign based on whether this is backlobe or # front lobe data if fov.find("front") == 0: back = 1.0 else: back = -1.0 # If desired, overwrite the hardware value for tdiff if tdiff is None: tdiff = hard.tdiff #------------------------------------------------------------------------- # Calculate the beam-specific variables # # Phi is the beam direction off the radar boresite assuming an elevation # angle of zero. This is calculated by the hardware routine 'beamToAzim' # located in pydarn.radar.radStruct bm_az = np.radians(hard.beamToAzim(beam.bmnum) - hard.boresite) cos_az = np.cos(bm_az) sin_az = np.sin(bm_az) az_sign = 1.0 if hard.interfer[1] > 0.0 else -1.0 sig2_az = bmaz_e**2 + boresite_e**2 # Find the elevation angle with the maximum phase lag el_max = np.arcsin(az_sign * hard.interfer[2] * cos_az / np.sqrt(hard.interfer[1]**2 + hard.interfer[2]**2)) if el_max < 0.0: el_max = 0.0 cos_max = np.cos(el_max) sin_max = np.sin(el_max) # k is the wavenumber in radians per meter k = 2.0 * np.pi * beam.prm.tfreq * 1.0e3 / scicon.c # Calculate the phase-lag independent portion of the theta error theta2_base = sig2_az * (hard.interfer[0] * cos_az)**2 if ix_e > 0.0: theta2_base += (ix_e * sin_az)**2 if tdiff_e > 0.0: theta2_base += (scicon.c * tdiff_e * 1.0e-6)**2 # Calculate the phase shift due to the radar cables del_chi = -np.pi * beam.prm.tfreq * tdiff * 2.0e-3 # Find the maximum possible phase shift chimax = k * (hard.interfer[0] * sin_az + hard.interfer[1] * np.sqrt(cos_max**2 - sin_az**2) + hard.interfer[2] * sin_max) chimin = chimax - (alias + 1.0) * az_sign * 2.0 * np.pi #------------------------------------------------------------------------- # Calculate the elevation for each value of phi0 for i, p0 in enumerate(phi0): #--------------------------------------------------------------------- # Use only data where the angular drift between signals is not # identically zero and the error is not NaN if p0 != 0.0 and not np.isnan(phi0_e[i]): # Find the right phase. This method works for both front and back # lobe calculations, unlike the more efficient method used by RST # in elevation.c: # phi_tempf = phi0 + 2.0*np.pi* np.floor(((chimax+del_chi)-phi0)/ # (2.0*np.pi)) # if phi_sign < 0.: # phi_tempf += 2.0 * np.pi # cos_thetaf = (phi_tempf - del_chi) / (k * asep) # sin2_deltaf = cos_phi**2 - cos_thetaf**2 # # They both yield the same elevation for front lobe calculations phi_temp = (back * (p0 - del_chi)) % (2.0 * np.pi) amb_temp = -np.floor(back * (p0 - del_chi) / (2.0 * np.pi)) # Ensure that phi_temp falls within the proper limits while phi_temp > max(chimax, chimin): phi_temp += az_sign * 2.0 * np.pi amb_temp += az_sign while abs(phi_temp) < abs(chimin): phi_temp += az_sign * 2.0 * np.pi amb_temp += az_sign #-------------------------- # Evaluate the phase shift if phi_temp > max(chimax, chimin): estr = "can't fix phase shift for beam {:d}".format(beam.bmnum) estr = "{:s} [{:f} not between ".format(estr, phi_temp) estr = "{:s}{:f},{:f} on ".format(estr, chimin, chimax) estr = "{:s}{:} at range gate {:d}".format( estr, beam.time, beam.fit.slist[i]) logging.critical(estr) else: # Calculate the elevation angle and set if realistic cos_theta = phi_temp / k - hard.interfer[0] * sin_az yz_sum2 = hard.interfer[1]**2 + hard.interfer[2]**2 yscale2 = ((cos_theta * hard.interfer[2])**2 - yz_sum2 * (cos_theta**2 - (hard.interfer[1] * cos_az)**2)) sin_delta = (cos_theta * hard.interfer[2] + np.sqrt(yscale2)) / yz_sum2 if sin_delta <= 1.0: # Calculate the elevation elv[i] = np.degrees(np.arcsin(sin_delta)) phase_amb[i] = int(amb_temp) # Now that the elevation was calculated, find the # elevation error, using the propagation of error to # propagate the provided uncertainties asin_der = 1.0 / np.sqrt(1.0 - sin_delta**2) # If the azimuthal error is not zero, calculate derivative if sig2_az > 0.0: az_term2 = (cos_az * sin_az)**2 / yscale2 az_term2 *= np.power(hard.interfer[1], 4) * sig2_az else: az_term2 = 0.0 # If there is an error in the phase lag, add this to the # theta sigma and then calculate the theta term if phi0_e[i] > 0.0: theta_term2 = theta2_base + (phi0_e[i] / k)**2 else: theta_term2 = theta2_base if theta_term2 > 0.0: theta_term2 *= ( (hard.interfer[2] - hard.interfer[1]**2 * cos_theta / np.sqrt(yscale2)) / yz_sum2)**2 # If the interferometer Y error is not zero, get derivative if iy_e > 0.0: iy_term2 = (yz_sum2 - 2.0 * cos_theta * hard.interfer[1] * hard.interfer[2] - 2.0 * hard.interfer[1] * np.sqrt(yscale2) + hard.interfer[1]**2 * yz_sum2 * (yz_sum2 * cos_az**2 - cos_theta**2) / np.sqrt(yscale2))**2 / np.power( yz_sum2, 4) iy_term2 *= iy_e**2 else: iy_term2 = 0.0 # If the interferometer Z error is not zero, get derivative if iz_e > 0.0: iz_term2 = (( yz_sum2 * (cos_theta + hard.interfer[2] * (hard.interfer[1] * cos_az)**2 / np.sqrt(yscale2)) - 2.0 * hard.interfer[2] * np.sqrt(yscale2)) * iz_e)**2 / np.power(yz_sum2, 4) else: iz_term2 = 0.0 # Calculate the elevation error, adding in quadrature temp = np.sqrt(az_term2 + theta_term2 + iy_term2 + iz_term2) # If the elevation error is identically zero, assume # that no error could be obtained elv_e[i] = np.nan if temp == 0.0 else temp return elv, elv_e, phase_amb, hard