def gnss_total_group_delay(dset): """Correct for total group delay Args: dset (Dataset): Model data. Returns: numpy.ndarray: GNSS total group delay correction for each observation in meter """ if config.tech.apriori_orbit.str != "broadcast": log.warn( f"Total group delay can only be applied for '{config.tech.apriori_orbit}' orbits. Apriori orbit has " f"to be 'broadcast'.") return np.zeros(dset.num_obs) brdc = apriori.get( "orbit", rundate=dset.analysis["rundate"], system=tuple(dset.unique("system")), station=dset.vars["station"], apriori_orbit="broadcast", ) return brdc.total_group_delay(dset)
def data_handling(dset): """Edits data based on SLR handling file Args: dset: A Dataset containing model data. Returns: Array containing False for observations to throw away """ handling = apriori.get("slr_handling_file", time=dset.time) remove_idx = np.zeros(dset.num_obs, dtype=bool) for station in dset.unique("station"): # TODO: To be implemented if "V" in handling.get(station, {}): log.dev(f"TODO: Station {station}, marked with a V, not sure what that means") # X is data to be deleted # N is a non reliable station, not to be used for operational analysis # Q is a station in quarantene for key in ["X", "N", "Q"]: intervals = handling.get(station, {}).get(key, []) for interval in intervals: start_x, end_x = interval[0] int_idx = dset.filter(station=station) & (dset.time >= start_x) & (dset.time <= end_x) if np.any(int_idx): log.debug(f"Removed data for station {station} in interval {start_x}-{end_x}, marked with {key}") remove_idx |= int_idx return ~remove_idx
def gnss_relativistic_clock(dset): """Determine relativistic clock correction due to orbit eccentricity The correction is caluclated for precise and broadcast orbits after Eq. 10.10 and 10.11 in :cite:`iers2010`. Args: dset (Dataset): Model data. Returns: numpy.ndarray: Relativistic clock correction due to orbit eccentricity corrections for each observation """ if "delay.gnss_relativistic_clock" in dset.fields: return dset.delay.gnss_relativistic_clock else: orbit = apriori.get( "orbit", apriori_orbit=dset.vars["orbit"], rundate=dset.analysis["rundate"], system=tuple(dset.unique("system")), station=dset.vars["station"], ) return -orbit.relativistic_clock_correction( dset.sat_posvel.trs.pos.val, dset.sat_posvel.trs.vel.val)
def term_2(dset, proj_Kb, _, _ve): r"""Part of the vacuum delay dependent on the gravitational potential The term :math:`\hat K \cdot \vec b \cdot \frac{(1 + \gamma) U}{c^2}` scaled by the denominator :math:`1 + \frac{\hat K \cdot (\vec V_\oplus + \vec w_2)}{c}`. The parameterized post-Newtonian (PPN) gamma, :math:`\gamma` is equal to 1 in general relativity theory. The gravitational potential at the geocenter, \f$ U \f$, neglecting the effects of the Earth's mass is calculated. Following table 11.1 in IERS Conventions [2], only the solar potential need to be included at the picosecond level. That is \f[ U = G M_\odot / | \vec R_{\oplus_\odot} | \f] where \f$ \vec R_{\oplus_\odot} \f$ is the vector from the Sun to the geocenter. We calculate the latter using the ephemerides. Args: dset: Model input data. proj_Kb: Scaled projection of baseline in direction of source. Returns: Numpy array: Part of vacuum delay. """ gamma = 1.0 eph = apriori.get("ephemerides", time=dset.time) grav_potential = constant.GM_sun / np.linalg.norm(eph.pos_gcrs("sun"), axis=1) return proj_Kb * (1 + gamma) * grav_potential / constant.c**2
def gnss_relativistic_clock(dset): """Determine relativistic clock correction due to orbit eccentricity The correction is caluclated for precise and broadcast orbits after Eq. 10.10 and 10.11 in :cite:`iers2010`. TODO: 'gnss_relativistic_clock' could be saved in Dataset and used here to avoid calculation of relativistic clock correction twice. Args: dset (Dataset): Model data. Returns: numpy.ndarray: Relativistic clock correction due to orbit eccentricity corrections for each observation """ # TODO: Is there a way that relativistic_clock_correction() could be called directly, without generating an orbit # object? -> Should it be handled over dset.gnss_relativistic_clock ???? orbit = apriori.get( "orbit", apriori_orbit=dset.vars["orbit"], rundate=dset.rundate, time=dset.sat_time, satellite=tuple(dset.satellite), system=tuple(dset.system), station=dset.vars["station"], ) return -orbit.relativistic_clock_correction()
def nnr_crf(dset, param_names): # NNR to CRF celestial_reference_frame = config.tech.celestial_reference_frames.list[0] crf = apriori.get("crf", celestial_reference_frames=celestial_reference_frame, session=dset.dataset_name) n = len(param_names) H2 = np.zeros((3, n)) for idx, column in enumerate(param_names): if "_src_dir-" not in column: continue source = column.split("-", maxsplit=1)[-1].split("_")[0] if source in crf: ra = crf[source].pos.crs[0] dec = crf[source].pos.crs[1] if column.endswith("_ra"): H2[0, idx] = -np.cos(ra) * np.sin(dec) * np.cos(dec) H2[1, idx] = -np.sin(ra) * np.sin(dec) * np.cos(dec) H2[2, idx] = np.cos(dec)**2 if column.endswith("_dec"): H2[0, idx] = np.sin(ra) H2[1, idx] = -np.cos(ra) if H2.any(): log.info("Applying NNR constraint to {}", celestial_reference_frame.upper()) # add NNR to CRF constraints sigma = np.array([1e-6] * 3) return H2, sigma else: return np.zeros((0, n)), np.zeros(0)
def solid_tides_station(dset): """Calculate the solid tide corrections for a station Solid tide corrections are returned in meters in the Geocentric Celestial Reference System for each observation. Args: dset: A Dataset containing model data. Returns: Numpy array with solid tide corrections in meters. """ eph = apriori.get("ephemerides", time=dset.time) dxyz = np.zeros((dset.num_obs, 3)) obs_dt = dset.time.utc.datetime hour_of_day = dset.time.utc.jd_frac * 24 sun_itrs = eph.pos_itrs("sun") moon_itrs = eph.pos_itrs("moon") # Calculate correction for obs, _ in enumerate(dset.values()): dxyz[obs] = iers.dehanttideinel( dset.site_pos.itrs_pos[obs], obs_dt[obs].year, obs_dt[obs].month, obs_dt[obs].day, hour_of_day[obs], sun_itrs[obs], moon_itrs[obs], ) # Transform from terrestial to celestial return dset.site_pos.convert_itrs_to_gcrs(dxyz)
def ocean_tides(dset): """Calculate ocean tide corrections at both stations Ocean tide corrections are returned in meters in the Geocentric Celestial Reference System for each observation. A Numpy array with 6 columns is returned, the first three columns are \f$ x, y, z \f$ for station 1, while the last three columns are \f$ x, y, z \f$ for station 2. Args: rundate: The model run date. tech: The technique. dset: A Dataset containing model data. Returns: Numpy array with ocean tide corrections in meters. """ # Ocean Tides Coefficients otc = apriori.get("ocean_tides") amplitudes = {s: otc[s]["amplitudes"] for s in dset.unique("site_id") if s in otc} phases = {s: otc[s]["phases"] for s in dset.unique("site_id") if s in otc} # Warn about missing Ocean Tides Coefficients for site_id in set(dset.unique("site_id")) - set(amplitudes.keys()): log.error("Missing ocean loading coefficients for site id '{}'. Correction set to zero.", site_id) data_out = list() for _ in dset.for_each("station"): data_out.append(ocean_tides_station(dset, amplitudes, phases)) return np.hstack(data_out)
def site_eccentricity(self): """Mandatory """ ecc = apriori.get("eccentricity", rundate=self.dset.analysis["rundate"]) self.fid.write("+SITE/ECCENTRICITY\n") self.fid.write( "*Code PT SBIN T Data_Start__ Data_End____ typ Apr --> Benchmark (m)_____\n" ) for sta in self.dset.unique("station"): site_id = self.dset.meta[sta]["site_id"] self.fid.write( " {:4} {:>2} {:>4} {:1} {:12} {:12} {:3} {: 8.4f} {: 8.4f} {: 8.4f}\n" "".format( site_id, "A", 1, _TECH[self.dset.meta["tech"]], self.dset.time.yydddsssss[0], self.dset.time.yydddsssss[-1], ecc[site_id]["coord_type"], ecc[site_id]["vector"][0], ecc[site_id]["vector"][1], ecc[site_id]["vector"][2], )) self.fid.write("-SITE/ECCENTRICITY\n")
def gnss_relativistic_clock_rate(dset): """Determine relativistic clock rate correction due to orbit eccentricity The correction is caluclated for precise and broadcast orbits after Eq. 6-19 in :cite:`zhang2007`. Args: dset (Dataset): Model data. Returns: numpy.ndarray: Relativistic clock rate correction due to orbit eccentricity corrections for each observation """ gm = constant.get("GM", source="iers_2010") orbit = apriori.get( "orbit", apriori_orbit=dset.vars["orbit"], rundate=dset.analysis["rundate"], system=tuple(dset.unique("system")), station=dset.vars["station"], ) a = np.power(orbit.get_ephemeris_field("sqrt_a", dset), 2) correction = 2 * gm / constant.c * (1 / a - 1 / dset.sat_posvel.trs.pos.length) return correction
def _write_line(sta_1, sta_2, dset, idx): """Write one line of information about a station or baseline. At the moment, we also print the line to the screen for convenience, this might need to be changed when we run operationally? Args: sta_1: String, name of Station 1. sta_2: String, name of Station 2. dset: Dataset, data from the model run. idx: Bool-array, True for observations that should be included. fid: File-pointer, file to write to. """ rms = dset.rms("residual", idx=idx) if any(idx) else 0 bias = dset.mean("residual", idx=idx) if any(idx) else 0 stars = "" if rms < STAR_THRESHOLD else "*" * min(MAX_NUM_STARS, math.ceil(math.log2(rms / STAR_THRESHOLD))) if sta_2 and any(idx): trf = apriori.get("trf", time=dset.time.utc.mean) site_id_1 = dset.meta[sta_1]["site_id"] site_id_2 = dset.meta[sta_2]["site_id"] pos_1 = trf[site_id_1].pos.trs pos_2 = trf[site_id_2].pos.trs bl_len = (pos_2 - pos_1).length else: bl_len = 0 return f"{sta_1:<9s} {sta_2:<9s} {np.sum(idx):>5d} {bl_len:>11.1f} {bias:>11.6f} {rms:>11.6f} {stars}"
def get_satellite_sun_vector(time, sat_pos): """Determine unit vector pointing from satellite to Sun in ITRS The determination of the vector pointing from satellite to Sun is based on Eq. 5.77 in :cite:`subirana2013`. Args: time (where.lib.time.Time): Where Time object. sat_pos (numpy.ndarray): Satellite position in ITRS. Returns: numpy.ndarray: unit vectors pointing from satellite to Sun in ITRS and in unit of meters """ num_obs = len(time) unit_sat_sun_pos = np.zeros((num_obs, 3)) # Get Sun position vector in ITRS eph = apriori.get("ephemerides", time=time) # TODO: # Actually the JPL ephemeris are given in the BCRS with Solar System barycenter as origin and not Earth mass # center. So in principle the sun position vector has to be transformed from the BCRS to the GCRS. What are the # consequences, if we do not consider these corrections? sun_pos_itrs = eph.pos_itrs("sun") # Determination of vector between satellite and Sun sat_sun_pos = sun_pos_itrs - sat_pos sat_sun_pos_norm = np.linalg.norm(sat_sun_pos, axis=1) unit_sat_sun_pos = sat_sun_pos / sat_sun_pos_norm[:, None] return unit_sat_sun_pos
def solid_pole_tides_station(dset): """Calculate the solid pole tide corrections for a station Solid pole tide corrections are returned in meters in the Geocentric Celestial Reference System for each observation. Args: dset: A Dataset containing model data. version: Version number of conventional mean pole model Returns: Numpy array with ocean tide corrections in meters. """ eop = apriori.get("eop", time=dset.time) # Equation (7.24) IERS Conventions 2010 m_1 = eop.x - eop.x_mean m_2 = -(eop.y - eop.y_mean) lat, lon, _ = dset.site_pos.llh.T theta = np.pi / 2 - lat coslon, sinlon = np.cos(lon), np.sin(lon) # Equation (7.26) IERS Conventions 2010 dup = -33 * np.sin(2 * theta) * (m_1 * coslon + m_2 * sinlon) * unit.mm2m dsouth = -9 * np.cos(2 * theta) * (m_1 * coslon + m_2 * sinlon) * unit.mm2m deast = 9 * np.cos(theta) * (m_1 * sinlon + m_2 * coslon) * unit.mm2m # Correction in topocentric (east, north, up) coordinates denu = np.vstack((deast, -dsouth, dup)).T return dset.site_pos.convert_enu_to_gcrs(denu)
def itrs_pos_sun(self): """Determine unit vector pointing from given position to Sun in ITRS The determination of the vector pointing from given position to Sun is based on Eq. 5.77 in :cite:`subirana2013`. Returns: numpy.ndarray: unit vectors pointing from given position to Sun in ITRS and in unit of meters """ # Get Sun position vector in ITRS eph = apriori.get( "ephemerides", time=self._time.tdb) # TODO: is self._time.tdb correct # TODO: # Actually the JPL ephemeris are given in the BCRS with Solar System barycenter as origin and not Earth mass # center. So in principle the sun position vector has to be transformed from the BCRS to the GCRS. What are # the consequences, if we do not consider these corrections? earth_sun = eph.itrs( "earth", "sun") # vector pointing from Earth to Sun mass center # Determination of vector between given position and Sun pos_sun = earth_sun - self.itrs_pos pos_sun_unit = mathp.unit_vector(pos_sun) # +DEBUG: Use same model as gLAB for determination of sun-earth vector # glab_sun_earth = gnss.findsun(self._time.tdb) # glab_pos_sun = glab_sun_earth - self.itrs_pos # pos_sun_unit = mathp.unit_vector(glab_pos_sun) # -DEBUG return pos_sun_unit
def validate_args(rundate, session=None, **kwargs): """Validate a session for the given rundate If session is not a valid VLBI session for the given rundate, an InvalidSessionError is raised. Args: rundate (date): The model run date. session (String): Name of session. Return: String: Name of validated session. """ if session is None: raise exceptions.InvalidArgsError( "You must specify '--session=<...>' to run a VLBI analysis") master_schedule = apriori.get("vlbi_master_schedule", rundate=rundate) master_sessions = master_schedule.list_sessions(rundate) if session not in master_sessions: log.warn( f"Session '{session}' is not listed in master file for {rundate:{config.FMT_date}}. " f"Available sessions are {', '.join(master_sessions)}.") else: if not master_schedule.ready(rundate, session): status = master_schedule.status(rundate, session) log.warn( f"Session '{session}' is not ready for processing. Master file status: '{status}'" )
def write_to_dataset(self, dset): """Store DORIS data in a dataset Args: dset: The Dataset where data are stored. """ dset.num_obs = len(self.data["obs"]["time"]) dset.add_time("time", val=self.data["obs"].pop("time"), scale="utc", format="isot") dset.add_text("station", val=self.data["obs"].pop("station")) for field, value in self.data["obs"].items(): dset.add_float(field, val=np.array(value)) # Station data sta_fields = set().union(*[v.keys() for v in self.meta["station_info"].values()]) for field in sta_fields: dset.add_text(field, val=[self.meta["station_info"][s][field] for s in dset.station]) # Station positions site_pos = np.zeros((dset.num_obs, 3)) trf = apriori.get("trf", time=dset.time) for site in dset.unique("station"): idx = dset.filter(station=site) site_pos[idx, :] = trf[site].pos.trs[idx, :] log.debug(f"Using position {np.mean(site_pos[idx, :], axis=0)} for {site!r}") dset.add_position("site_pos", time="time", itrs=site_pos) # Satellite dset.add_text("satellite", val=[self.vars["sat_name"]] * dset.num_obs)
def gnss_ignore_unhealthy_satellite(dset): """Remove observations epochs for satellites, which are related to unhealthy GNSS broadcast ephemeris If a satellite is set to unhealthy in one of the given broadcast ephemeris blocks, then the related satellite observation epochs are removed. The definition of the satellite vehicle health flags depends on GNSS: - GPS: see section 20.3.3.3.1.4 in :cite:`is-gps-200h` - Galileo: see section 5.1.9.3 in :cite:`galileo-os-sis-icd` - BeiDou: see section 5.2.4.6 in :cite:`bds-sis-icd` - QZSS: see section 4.1.2.3 in :cite:`is-qzss-pnt-001` - IRNSS: see section 6.2.1.6 in :cite:`irnss-icd-sps` Args: dset (Dataset): A Dataset containing model data. Returns: numpy.ndarray: Array containing False for observations to throw away """ # Get signal health status information from GNSS broadcast orbits brdc = apriori.get( "orbit", rundate=dset.analysis["rundate"], station=dset.vars["station"], system=tuple(dset.unique("system")), apriori_orbit="broadcast", ) remove_idx = brdc.signal_health_status(dset) > 0 unhealthy_satellites = sorted(set(dset.satellite[remove_idx])) log.info(f"Discarding observations for unhealthy satellites: {', '.join(unhealthy_satellites)}") return ~remove_idx
def spv_doppler(stage, dset): """Calculate model parameters and estimate Args: stage (str): Name of current stage. dset (Dataset): A dataset containing the data. """ # to have access to orbit data, you have make a new call # to orbit # orbit = apriori.get("orbit", rundate=dset.analysis["rundate"], system=tuple(dset.unique('system')), station=dset.vars["station"],) station = dset.vars["station"] orbit = apriori.get("orbit", rundate=dset.analysis["rundate"], system=tuple(dset.unique("system")), station=station) orbit.dset_raw.write_as(stage=stage, session=station, dataset_name="raw") orbit.dset_edit.write_as(stage=stage, session=station, dataset_name="edit") import IPython IPython.embed() # get the observed time MJD = sorted(set(dset.time.gps.mjd)) for epoch in MJD: idx = dset.time.gps.mjd == epoch valid_sats = dset.satellite[idx] dop_obs = dset.D1X[idx] AZ = dset.sat_posvel.azimuth[idx] EL = dset.sat_posvel.elevation[idx] SAT_ENU = dset.sat_posvel.enu_up[idx] SAT_POS = dset.sat_posvel.itrs_pos[idx] SAT_VEL = dset.sat_posvel.itrs_vel[idx]
def _ignore_satellites(dset: "Dataset", orbit_flag: str) -> None: """Remove GNSS observations with unavailable apriori satellite orbits from Dataset The remover can be used for precise and broadcast ephemeris and also for the GNSS and SISRE technique/analysis. - GNSS: The apriori orbit is chosen via the configuration. The available satellites of the apriori orbits are compared against the satellites given in the GNSS observation files. - SISRE: Both apriori orbits, broadcast and precise, has to be checked against a set of satellites defined in the configuration file. Args: dset: A Dataset containing model data, which is decimated by unavailable satellite observations. orbit_flag: Specification of which apriori orbit is used ("broadcast" or "precise") """ orbit = apriori.get( "orbit", apriori_orbit=orbit_flag, rundate=dset.analysis["rundate"], station=dset.vars["station"], system=tuple(dset.unique("system")), day_offset= 0, # check satellite availability only for current rundate and not for the days before/after # rundate in addition. TODO: This does not work for broadcast ephemeris at the moment. ) not_available_sat = set(dset.satellite) - set(orbit.dset_raw.satellite) file_paths = orbit.dset_raw.meta["parser"]["file_path"] if not_available_sat: log.warn( f"The following satellites are not given in apriori {orbit_flag} orbit file {', '.join(file_paths)}: " f"{', '.join(sorted(not_available_sat))}") cleaners.apply_remover("ignore_satellite", dset, satellites=not_available_sat)
def data_quality(dset): """Edits data based on data quality Args: dset: A Dataset containing model data. Returns: Array containing False for observations to throw away """ session = dset.dataset_name handling_str = config.session[ session].slr_handling.str # TODO: this is not used ... handling = apriori.get("slr_handling_file", time=dset.time) remove_idx = np.zeros(dset.num_obs, dtype=bool) for station in dset.unique("station"): intervals = handling.get(station, {}).get("X", []) for interval in intervals: start_x = interval[0][0] end_x = interval[0][1] int_idx = np.logical_and( dset.filter(station=station), np.logical_and(dset.date_list >= start_x, dset.date_list <= end_x)) if np.any(int_idx): log.warn("Removed data for station {} in interval {}-{}", station, start_x, end_x) remove_idx = np.logical_or(remove_idx, int_idx) return np.logical_not(remove_idx)
def eclipse_satellites(fid, dsets): """Write overview over satellites in eclipse Args: fid: dsets: """ stations = sorted(dsets["orbit"].keys()) if not stations: return 0 fid.write("\n# Overview over satellites in eclipse\n\n") fid.write("| Satellites |\n") # fid.write('| Sat. | From | To |\n') #TODO: time period of eclipting satellites needed fid.write("|------------------------------------------------:|\n") dset = dsets["orbit"][stations[0]] brdc = apriori.get( "orbit", rundate=dset.rundate, time=dset.time, satellite=tuple(dset.satellite), system=tuple(dset.system), station=dset.dataset_name.upper(), apriori_orbit="broadcast", ) fid.write( "| {sats:47s} |\n" "".format(sats=" ".join(gnss.check_satellite_eclipse(brdc.dset))))
def vmf1_gridded_pressure(dset): """Calulates VMF1 gridded atmospheric pressure Equation (9.11) in IERS conventions 2010 :cite:`iers2010` is converted to 'pressure' and the VMF1 gridded atmospheric pressure can be determined with help of the gridded zenith hydrostatic delays from VMF1. The gridded VMF1 pressure values needs to be rescaled to actual station height. The method is described with Eq. (4) in Kouba :cite:`kouba2007`. Args: dset (Dataset): Model data. Returns: numpy.ndarray: Atmospheric pressure for each observation in [hPa]. """ # Get gridded VMF1 data vmf1 = apriori.get("vmf1", time=dset.time) lat, lon, height = dset.site_pos.llh.T grid_zhd = vmf1["zh"](dset.time, lon, lat) # Interpolation in time and space in VMF1 grid grid_height = vmf1["ell"](lon, lat, grid=False) grid_pressure = pressure_zhd(grid_zhd, lat, grid_height) # Rescale gridded pressure to station pressure pressure = pressure_height_correction(grid_pressure, grid_height, height) return pressure
def unhealthy_satellites(fid, dsets): """Write overview over unhealthy satellites Args: fid: dsets: """ stations = sorted(dsets["orbit"].keys()) if not stations: return 0 fid.write("\n# Overview over unhealthy satellites\n\n") fid.write("| Sta. | Satellites |\n") fid.write("|-------|-----------------------------------------:|\n") for station in stations: dset = dsets["orbit"][station] brdc = apriori.get( "orbit", rundate=dset.rundate, time=dset.time, satellite=tuple(dset.satellite), system=tuple(dset.system), station=dset.dataset_name.upper(), apriori_orbit="broadcast", ) fid.write("| {sta:5s} | {sats:40s} |\n" "".format(sta=station, sats=" ".join(brdc.unhealthy_satellites())))
def validate_session(rundate, session): """Validate a session for the given rundate If session is not a valid VLBI session for the given rundate, an InvalidSessionError is raised. Args: rundate (date): The model run date. session (String): Name of session. Return: String: Name of validated session. """ if not session: if util.check_options("--session"): return session # Explicitly specified blank session, typically to open timeseries interactively raise exceptions.InvalidSessionError( "You must specify '--session=<...>' to run a VLBI analysis") # TODO: Can we use master files to validate sessions? What about intensives? master_schedule = apriori.get("vlbi_master_schedule", rundate=rundate) master_sessions = master_schedule.list_sessions(rundate) if session not in master_sessions: log.warn( f"Session '{session}' is not listed in master file for {rundate:{config.FMT_date}}. " f"Available sessions are {', '.join(master_sessions)}.") return session
def Y(time): """Total Y coordinate of CIP Returns: model + celestial pole offset """ eop = apriori.get("eop", time=time) return sofa.Y_model(time) + eop.dy * Unit.arcsec2rad
def X(time): """Total X coordinate of CIP Returns: model + celestial pole offset """ eop = apriori.get("eop", time=time) return X_model(time) + eop.dx * unit.arcsec2rad
def initial_posvel(self, initial_dt): """Calculate initial position and velocity for GPS satellites """ orb = apriori.get("gnss_orbit", rundate=initial_dt)[self.name]["pos"] pos = orb.gcrs[0] vel = (orb.gcrs[1] - orb.gcrs[0]) / (orb.utc_dt[1] - orb.utc_dt[0]).total_seconds() return np.hstack((pos, vel))
def delta_ut1_utc(time: "TimeArray", models=None) -> "np_float": if time.scale == "ut1": # pretend that ut1 is utc to get an approximate delta value eop = apriori.get("eop", time=Time(time.mjd, fmt="mjd", scale="utc"), models=models) tmp_utc_mjd = time.mjd - eop.ut1_utc * Unit.second2day # get a better delta value with a new computed utc value eop = apriori.get("eop", time=Time(tmp_utc_mjd, fmt="mjd", scale="utc"), models=models) return -eop.ut1_utc * Unit.second2day else: # time scale is utc eop = apriori.get("eop", time=time, models=models) return eop.ut1_utc * Unit.second2day
def calculate_temperature_functions(dset): """Estimates a temperature sine function based on the air temperature recorded at each site If the number of data points is too small to estimate a sine function a simple constant function based on the mean value of the datapoints is used instead. Args: dset (Dataset): Model data. Returns: Dict: One temperature function for each station. """ temperature_funcs = dict() for ivsname in dset.unique("ivsname"): try: site_id = dset.meta[ivsname]["site_id"] except KeyError: site_id = "" idx_1 = dset.filter(ivsname_1=ivsname) time_1 = dset.time.utc[idx_1].mjd temp_1 = dset.temperature_1[idx_1] idx_2 = dset.filter(ivsname_2=ivsname) time_2 = dset.time.utc[idx_2].mjd temp_2 = dset.temperature_2[idx_2] time = np.append(time_1, time_2) temp = np.append(temp_1, temp_2) if any(np.isnan(temp)): vmf1_sta = apriori.get("vmf1_station", time=dset.time) try: temperature_funcs[ivsname] = vmf1_sta[ivsname]["temp"] log.warn( f"Missing temperature data for {ivsname} ({site_id}). Getting temperature from VMF1 station files" ) continue except KeyError: log.warn( f"Missing temperature data for {ivsname} ({site_id}). No backup data source available" ) # Amplitude, phase, offset, trend guess = [3 * np.std(temp) / (2**0.5), 0, np.mean(temp), 0] optimize_func = lambda x: (x[0] * np.sin(2 * np.pi * time + x[1]) + x[ 2] + x[3] * time - temp) if len(temp) >= 4: coeff = optimize.leastsq(optimize_func, guess)[0] else: log.warn( f"Few datapoints, applying constant temperature for {ivsname} ({site_id})." ) coeff = [0, 0, np.mean(temp), 0] temperature_funcs[ivsname] = _get_temperature_func(*coeff) return temperature_funcs
def aberrated_src_dir(site_pos): """See IERS2010 Conventions, equation 11.15""" site_vel_gcrs = site_pos.gcrs.vel.val eph = apriori.get("ephemerides", time=dset.time) vel = eph.vel_bcrs("earth") + site_vel_gcrs return ( dset.src_dir.unit_vector + vel / constant.c - dset.src_dir.unit_vector * (dset.src_dir.unit_vector[:, None, :] @ vel[:, :, None])[:, :, 0] / constant.c)