Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
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
Beispiel #6
0
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