def change_dmepoch(self, new_epoch): """Change DMEPOCH to a new value and update DM accordingly. Parameters ---------- new_epoch: float MJD (in TDB) or `astropy.Time` object The new DMEPOCH value. """ if isinstance(new_epoch, Time): new_epoch = Time(new_epoch, scale="tdb", precision=9) else: new_epoch = Time(new_epoch, scale="tdb", format="mjd", precision=9) dmterms = [0.0 * u.Unit("")] + self.get_DM_terms() if self.DMEPOCH.value is None: if any(d.value != 0 for d in dmterms[2:]): # Should be ruled out by validate() raise ValueError( f"DMEPOCH not set but some DM derivatives are not zero: {dmterms}" ) self.DMEPOCH.value = new_epoch dmepoch_ld = self.DMEPOCH.quantity.tdb.mjd_long dt = (new_epoch.tdb.mjd_long - dmepoch_ld) * u.day for n in range(len(dmterms) - 1): cur_deriv = self.DM if n == 0 else getattr(self, "DM{}".format(n)) cur_deriv.value = taylor_horner_deriv(dt.to(u.yr), dmterms, deriv_order=n + 1) self.DMEPOCH.value = new_epoch
def change_dmepoch(self, new_epoch): """Change DMEPOCH to a new value and update DM accordingly. Parameters ---------- new_epoch: float MJD (in TDB) or `astropy.Time` object The new DMEPOCH value. """ if isinstance(new_epoch, Time): new_epoch = Time(new_epoch, scale="tdb", precision=9) else: new_epoch = Time(new_epoch, scale="tdb", format="mjd", precision=9) if self.DMEPOCH.value is None: raise ValueError("DMEPOCH is not currently set.") dmepoch_ld = self.DMEPOCH.quantity.tdb.mjd_long dt = (new_epoch.tdb.mjd_long - dmepoch_ld) * u.day dmterms = [0.0 * u.Unit("")] + self.get_DM_terms() for n in range(len(dmterms) - 1): cur_deriv = self.DM if n == 0 else getattr(self, "DM{}".format(n)) cur_deriv.value = taylor_horner_deriv(dt.to(u.yr), dmterms, deriv_order=n + 1) self.DMEPOCH.value = new_epoch
def pbdot_orbit(self): FBXs = [0*u.Unit(""),] ii = 0 while 'FB' + str(ii) in self.orbit_params: FBXs.append(getattr(self, 'FB' + str(ii))) ii += 1 orbit_freq_dot = ut.taylor_horner_deriv(self.tt0, FBXs, 2) return -(self.pbprime() ** 2) * orbit_freq_dot
def pbprime(self): FBXs = [0*u.Unit(""),] ii = 0 while 'FB' + str(ii) in self.orbit_params: FBXs.append(getattr(self, 'FB' + str(ii))) ii += 1 orbit_freq = ut.taylor_horner_deriv(self.tt0, FBXs, 1) return 1.0 / orbit_freq
def d_pbprime_d_FBX(self, FBX): par = getattr(self, FBX) ii = 0 FBXs = [0*u.Unit(""),] while 'FB' + str(ii) in self.orbit_params: if 'FB' + str(ii) != FBX: FBXs.append(0.0 * getattr(self, 'FB' + str(ii)).unit) else: FBXs.append(1.0 * getattr(self, 'FB' + str(ii)).unit) ii += 1 d_FB = ut.taylor_horner_deriv(self.tt0, FBXs, 1) / par.unit return -(self.pbprime() ** 2) * d_FB
def get_PSR_freq(self, calctype="modelF0"): """Return pulsar rotational frequency in Hz. Parameters ---------- calctype : {'modelF0', 'numerical', 'taylor'} Type of calculation. If `calctype` == "modelF0", then simply the ``F0`` parameter from the model. If `calctype` == "numerical", then try a numerical derivative If `calctype` == "taylor", evaluate the frequency with a Taylor series Returns ------- freq : astropy.units.Quantity Either the single ``F0`` in the model or the spin frequency at the moment of each TOA. """ assert calctype.lower() in ["modelf0", "taylor", "numerical"] if calctype.lower() == "modelf0": # TODO this function will be re-write and move to timing model soon. # The following is a temproary patch. if "Spindown" in self.model.components: F0 = self.model.F0.quantity elif "P0" in self.model.params: F0 = 1.0 / self.model.P0.quantity else: raise AttributeError("No pulsar spin parameter(e.g., 'F0'," " 'P0') found.") return F0.to(u.Hz) elif calctype.lower() == "taylor": # see Spindown.spindown_phase dt = self.model.get_dt(self.toas, 0) # if the model is defined through F0, F1, ... if "F0" in self.model.params: fterms = [0.0 * u.dimensionless_unscaled ] + self.model.get_spin_terms() # otherwise assume P0, PDOT else: F0 = 1.0 / self.model.P0.quantity if "PDOT" in self.model.params: F1 = -self.model.PDOT.quantity / self.model.P0.quantity**2 else: F1 = 0 * u.Hz / u.s fterms = [0.0 * u.dimensionless_unscaled, F0, F1] return taylor_horner_deriv(dt, fterms, deriv_order=1).to(u.Hz) elif calctype.lower() == "numerical": return self.model.d_phase_d_toa(self.toas)
def change_pepoch(self, new_epoch, toas=None, delay=None): """Move PEPOCH to a new time and change the related paramters. Parameters ---------- new_epoch: float or `astropy.Time` object The new PEPOCH value. toas: `toa` object, optional. If current PEPOCH is not provided, the first pulsar frame toa will be treated as PEPOCH. delay: `numpy.array` object If current PEPOCH is not provided, it is required for computing the first pulsar frame toa. """ if isinstance(new_epoch, Time): new_epoch = Time(new_epoch, scale="tdb", precision=9) else: new_epoch = Time(new_epoch, scale="tdb", format="mjd", precision=9) # make new_epoch a toa for delay calculation. new_epoch_toa = toa.get_TOAs_list([toa.TOA(new_epoch)], ephem=toas.ephem) if self.PEPOCH.value is None: if toas is None or delay is None: raise ValueError( "`PEPOCH` is not in the model, thus, 'toa' and" " 'delay' shoule be givne.") tbl = toas.table phsepoch_ld = (tbl["tdb"][0] - delay[0]).tdb.mjd_long else: phsepoch_ld = self.PEPOCH.quantity.tdb.mjd_long dt = (new_epoch.tdb.mjd_long - phsepoch_ld) * u.day fterms = [0.0 * u.Unit("")] + self.get_spin_terms() # rescale the fterms for n in range(len(fterms) - 1): f_par = getattr(self, "F{}".format(n)) f_par.value = taylor_horner_deriv(dt.to(u.second), fterms, deriv_order=n + 1) self.PEPOCH.value = new_epoch
def change_binary_epoch(self, new_epoch): """Change the epoch for this binary model. T0 will be changed to the periapsis time closest to the supplied epoch, and the argument of periapsis (OM), eccentricity (ECC), and projected semimajor axis (A1 or X) will be updated according to the specified OMDOT, EDOT, and A1DOT or XDOT, if present. Note that derivatives of binary orbital frequency higher than the first (FB2, FB3, etc.) are ignored in computing the new T0, even if present in the model. If high-precision results are necessary, especially for models containing higher derivatives of orbital frequency, consider re-fitting the model to a set of TOAs. The use of :func:`pint.toa.make_fake_toas` and the :class:`pint.fitter.Fitter` option ``track_mode="use_pulse_number"`` can make this extremely simple. Parameters ---------- new_epoch: float MJD (in TDB) or `astropy.Time` object The new epoch value. """ if isinstance(new_epoch, Time): new_epoch = Time(new_epoch, scale="tdb", precision=9) else: new_epoch = Time(new_epoch, scale="tdb", format="mjd", precision=9) try: self.FB2.quantity log.warning( "Ignoring orbital frequency derivatives higher than FB1" "in computing new T0" ) except AttributeError: pass # Get PB and PBDOT from model if self.PB.quantity is not None: PB = self.PB.quantity if self.PBDOT.quantity is not None: PBDOT = self.PBDOT.quantity else: PBDOT = 0.0 * u.Unit("") else: PB = 1.0 / self.FB0.quantity try: PBDOT = -self.FB1.quantity / self.FB0.quantity ** 2 except AttributeError: PBDOT = 0.0 * u.Unit("") # Find closest periapsis time and reassign T0 t0_ld = self.T0.quantity.tdb.mjd_long dt = (new_epoch.tdb.mjd_long - t0_ld) * u.day d_orbits = dt / PB - PBDOT * dt ** 2 / (2.0 * PB ** 2) n_orbits = np.round(d_orbits.to(u.Unit(""))) dt_integer_orbits = PB * n_orbits + PB * PBDOT * n_orbits ** 2 / 2.0 self.T0.quantity = self.T0.quantity + dt_integer_orbits # Update PB or FB0, FB1, etc. if isinstance(self.binary_instance.orbits_cls, bo.OrbitPB): dPB = PBDOT * dt_integer_orbits self.PB.quantity = self.PB.quantity + dPB else: fbterms = [ getattr(self, k).quantity for k in self.get_prefix_mapping("FB").values() ] fbterms = [0.0 * u.Unit("")] + fbterms for n in range(len(fbterms) - 1): cur_deriv = getattr(self, "FB{}".format(n)) cur_deriv.value = taylor_horner_deriv( dt.to(u.s), fbterms, deriv_order=n + 1 ) # Update ECC, OM, and A1 dECC = self.EDOT.quantity * dt_integer_orbits self.ECC.quantity = self.ECC.quantity + dECC dOM = self.OMDOT.quantity * dt_integer_orbits self.OM.quantity = self.OM.quantity + dOM dA1 = self.A1DOT.quantity * dt_integer_orbits self.A1.quantity = self.A1.quantity + dA1
def pbdot_orbit(self): """Reported value of PBDOT.""" orbit_freq_dot = taylor_horner_deriv(self.tt0, self._FBXs(), 2) return -(self.pbprime()**2) * orbit_freq_dot
def pbprime(self): """Derivative of binary period with respect to time.""" orbit_freq = taylor_horner_deriv(self.tt0, self._FBXs(), 1) return 1.0 / orbit_freq
def test_taylor_horner_equals_deriv(x, coeffs): assert_allclose(taylor_horner(x, coeffs), taylor_horner_deriv(x, coeffs, 0))
def d_spindown_phase_d_delay(self, toas, delay): dt = self.get_dt(toas, delay) fterms = [0.0] + self.get_spin_terms() d_pphs_d_delay = taylor_horner_deriv(dt.to(u.second), fterms) return -d_pphs_d_delay.to(1 / u.second)
def d_spindown_phase_d_delay(self, toas, delay): dt = self.get_dt(toas, delay) fterms = [0.0] + self.get_spin_terms() with u.set_enabled_equivalencies(dimensionless_cycles): d_pphs_d_delay = taylor_horner_deriv(dt.to(u.second), fterms) return -d_pphs_d_delay.to(u.cycle / u.second)
def change_binary_epoch(self, new_epoch): """Change the epoch for this binary model. TASC will be changed to the epoch of the ascending node closest to the supplied epoch, and the Laplace parameters (EPS1, EPS2) and projected semimajor axis (A1 or X) will be updated according to the specified EPS1DOT, EPS2DOT, and A1DOT or XDOT, if present. Note that derivatives of binary orbital frequency higher than the first (FB2, FB3, etc.) are ignored in computing the new T0, even if present in the model. If high-precision results are necessary, especially for models containing higher derivatives of orbital frequency, consider re-fitting the model to a set of TOAs. Parameters ---------- new_epoch: float MJD (in TDB) or `astropy.Time` object The new epoch value. """ if isinstance(new_epoch, Time): new_epoch = Time(new_epoch, scale="tdb", precision=9) else: new_epoch = Time(new_epoch, scale="tdb", format="mjd", precision=9) try: FB2 = self.FB2.quantity log.warning( "Ignoring orbital frequency derivatives higher than FB1" "in computing new TASC") except AttributeError: pass # Get PB and PBDOT from model if self.PB.quantity is not None: PB = self.PB.quantity if self.PBDOT.quantity is not None: PBDOT = self.PBDOT.quantity else: PBDOT = 0.0 * u.Unit("") else: PB = 1.0 / self.FB0.quantity try: PBDOT = -self.FB1.quantity / self.FB0.quantity**2 except AttributeError: PBDOT = 0.0 * u.Unit("") # Find closest periapsis time and reassign T0 tasc_ld = self.TASC.quantity.tdb.mjd_long dt = (new_epoch.tdb.mjd_long - tasc_ld) * u.day d_orbits = dt / PB - PBDOT * dt**2 / (2.0 * PB**2) n_orbits = np.round(d_orbits.to(u.Unit(""))) dt_integer_orbits = PB * n_orbits + PB * PBDOT * n_orbits**2 / 2.0 self.TASC.quantity = self.TASC.quantity + dt_integer_orbits # Update PB or FB0, FB1, etc. if isinstance(self.binary_instance.orbits_cls, bo.OrbitPB): dPB = PBDOT * dt_integer_orbits self.PB.quantity = self.PB.quantity + dPB else: fbterms = [ getattr(self, k).quantity for k in self.get_prefix_mapping("FB").values() ] fbterms = [0.0 * u.Unit("")] + fbterms for n in range(len(fbterms) - 1): cur_deriv = getattr(self, "FB{}".format(n)) cur_deriv.value = taylor_horner_deriv(dt.to(u.s), fbterms, deriv_order=n + 1) # Update EPS1, EPS2, and A1 if self.EPS1DOT.quantity is not None: dEPS1 = self.EPS1DOT.quantity * dt_integer_orbits self.EPS1.quantity = self.EPS1.quantity + dEPS1 if self.EPS2DOT.quantity is not None: dEPS2 = self.EPS2DOT.quantity * dt_integer_orbits self.EPS2.quantity = self.EPS2.quantity + dEPS2 if self.A1DOT.quantity is not None: dA1 = self.A1DOT.quantity * dt_integer_orbits self.A1.quantity = self.A1.quantity + dA1
def test_taylor_horner_deriv(x, coeffs, order): def f(x): return taylor_horner(x, coeffs) df = Derivative(f, n=order) assert_allclose(df(x), taylor_horner_deriv(x, coeffs, order), atol=1e-11)