class PINTPulsar: """ Pulsar object class with an interface similar to tempopulsar of libstempo """ def __init__( self, parfile, timfile=None, warnings=False, fixprefiterrors=True, dofit=False, maxobs=None, units=False, ): """ The same init function as used in libstempo :param parfile: Name of the parfile :param timfile: Name of the timfile, if we want to load it :param warnings: Whether we are shoing warnings :param fixprefiterrors: TODO: check what this should do :param maxobs: PINT has no need for a maxobs parameter. Included here for compatibility :param units: Whether or not we are using the 'units' interface of libstempo """ if warnings: logging.getLogger().setLevel("INFO") else: logging.getLogger().setLevel("ERROR") self.loadparfile(parfile) if timfile is not None: self.loadtimfile(timfile) else: self.t = None self.deleted = None if dofit and self.t is not None: self.fit() self._units = units def loadparfile(self, parfile): """ Load a parfile with pint :param parfile: Name of the parfile """ self.model = pm.get_model(parfile) log.info("model.as_parfile():\n%s" % self.model.as_parfile()) try: self.planets = self.model.PLANET_SHAPIRO.value except AttributeError: self.planets = False self._readparams() def _readparams(self): """Process the timing model parameters, libstempo style""" self.pardict = OrderedDict() for par in self.model.params: self.pardict[par] = PINTPar(getattr(self.model, par), par) def loadtimfile(self, timfile): """ Load a pulsar with pint :param parfile: Name of the parfile :param timfile: Name of the timfile, if we want to load it """ t0 = time.time() self.t = pt.get_TOAs(timfile, planets=self.planets, usepickle=False) time_toa = time.time() - t0 log.info("Read/corrected TOAs in %.3f sec" % time_toa) if log.level < 25: self.t.print_summary() self.deleted = np.zeros(self.t.ntoas, dtype=bool) self._readflags() def _readflags(self): """Process the pint flags to be in the same format as in libstempo""" self.flagnames_ = [] self.flags_ = dict() for ii, obsflags in enumerate(self.t.get_flags()): for jj, flag in enumerate(obsflags): if flag not in self.flagnames_: self.flagnames_.append(flag) self.flags_[flag] = [""] * self.t.ntoas self.flags_[flag][ii] = obsflags[flag] # As is done in libstempo # for flag in self.flags_: # self.flags_[flag].flags.writeable = False def toas(self, updatebats=False): """Return TDB arrival times in MJDs""" # TODO: do high-precision as long double return np.array(self.t.table["tdbld"])[~self.deleted] @property def stoas(self): """Return site arrival times""" return np.array(self.t.get_mjds())[~self.deleted] @property def toaerrs(self): """Return the TOA uncertainty in microseconds""" return np.array(self.t.get_errors().to(u.us))[~self.deleted] @property def freqs(self): """Returns observation frequencies in units of MHz as a numpy.double array.""" return np.array(self.t.get_freqs()) def ssbfreqs(self): """Return SSB frequencies""" log.warning("Not using freqSSB just yet.") return np.array(self.t.get_freqs()) def deletedmask(self): """tempopulsar.deletedmask() Returns a numpy.bool array of the delection station of observations. You get a copy of the current values.""" return self.deleted def flags(self): """Returns the list of flags defined in this dataset (for at least some observations).""" return self.flagnames_ # TO DO: setting flags def flagvals(self, flagname, values=None): """Returns (or sets, if `values` are given) a numpy unicode-string array containing the values of flag `flagname` for every observation.""" if values is None: return self.flags_[flagname] else: raise NotImplementedError( "Flag-setting capabilities are coming soon.") def formresiduals(self): """Form the residuals""" log.info("Computing residuals...") t0 = time.time() self.resids_us = Residuals(self.t, self.model).time_resids.to(u.us) time_phase = time.time() - t0 log.info("Computed phases and residuals in %.3f sec" % time_phase) # resids in (approximate) us: log.info("RMS PINT residuals are %.3f us" % self.resids_us.std().value) def residuals(self, updatebats=True, formresiduals=True, removemean=True): """Returns residuals""" self.formresiduals() return np.array(self.resids_us.to(u.s))[~self.deleted] @property def name(self): """Get or set pulsar name.""" return self.model.PSR.value @property def binarymodel(self): """Return the binary model""" # Obtain the binary model from PINT """ def __get__(self): return self.psr[0].binaryModel.decode('ascii') def __set__(self,value): model_bytes = value.encode('ascii') if len(model_bytes) < 100: stdio.sprintf(self.psr[0].binaryModel,"%s",<char *>model_bytes) else: raise ValueError """ return None def pars(self, which="fit"): """Return parameter keys""" if which == "fit": return [ par for par in self.model.params if not getattr(self.model, par).frozen ] elif which == "set": return [par for par in self.model.params] if which == "frozen": return [ par for par in self.model.params if getattr(self.model, par).frozen ] elif which == "all": raise NotImplementedError("PINT does not track 'all' parameters") @property def nobs(self): """Number of observations""" return self.t.ntoas - np.sum(self.deleted) @property def ndim(self, incoffset=True): """Number of dimensions.""" return int(incoffset) + len(self.pars(which="fit")) # --- dictionary access to parameters def __contains__(self, key): return key in self.model.params def __getitem__(self, key): # return getattr(self.model, key).value return self.pardict[key] # --- bulk access to parameter values def vals(self, values=None, which="fit"): """tempopulsar.vals(values=None,which='fit') Get (if no `values` provided) or set the parameter values, depending on `which`: - if `which` is 'fit' (default), fitted parameters; - if `which` is 'set', all parameters with a defined value; - if `which` is 'all', all parameters; - if `which` is a sequence, all parameters listed there. Parameter values are returned as a numpy long double array. Values to be set can be passed as a numpy array, sequence (in which case they are taken to correspond to parameters in the order given by `pars(which=which)`), or dict (in which case which will be ignored). Notes: - Passing values as anything else than numpy long double may result in loss of precision. - Not all parameters in the selection need to be set. - Setting an unset parameter sets its `set` flag (obviously). - Unlike in earlier libstempo versions, setting a parameter does not set its error to zero.""" if values is None: return np.fromiter((self[par].val for par in self.pars(which)), np.longdouble) elif isinstance(values, collections.Mapping): for par in values: self[par].val = values[par] elif isinstance(values, collections.Iterable): for par, val in zip(self.pars(which), values): self[par].val = val else: raise TypeError def errs(self, values=None, which="fit"): """tempopulsar.errs(values=None,which='fit') Same as `vals()`, but for parameter errors.""" if values is None: # return np.fromiter((getattr(self.model, par).uncertainty for par in # self.pars(which)),np.longdouble) return np.fromiter((self[par].err for par in self.pars(which)), np.longdouble) elif isinstance(values, collections.Mapping): for par in values: self[par].err = values[par] elif isinstance(values, collections.Iterable): for par, val in zip(self.pars(which), values): self[par].err = val else: raise TypeError def designmatrix(self, updatebats=True, fixunits=True, fixsigns=True, incoffset=True): """tempopulsar.designmatrix(updatebats=True,fixunits=True,incoffset=True) Returns the design matrix [nobs x (ndim+1)] as a long double array for current fit-parameter values. If fixunits=True, adjust the units of the design-matrix columns so that they match the tempo2 parameter units. If fixsigns=True, adjust the sign of the columns corresponding to FX (F0, F1, ...) and JUMP parameters, so that they match finite-difference derivatives. If incoffset=False, the constant phaseoffset column is not included in the designmatrix.""" M, params, units = self.model.designmatrix(self.t.table, incfrozen=False, incoffset=incoffset) return M def telescope(self): """tempopulsar.telescope() Returns a numpy character array of the telescope for each observation, mapping tempo2 `telID` values to names by way of the tempo2 runtime file `observatory/aliases`.""" pass def binarydelay(self): """tempopulsar.binarydelay() Return a long-double numpy array of the delay introduced by the binary model. Does not reform residuals.""" pass def elevation(self): """tempopulsar.elevation() Return a numpy double array of the elevation of the pulsar at the time of the observations.""" pass def phasejumps(self): """tempopulsar.phasejumps() Return an array of phase-jump tuples: (MJD, phase). These are copies. NOTE: As in tempo2, we refer the phase-jumps to site arrival times. The tempo2 definition of a phasejump is such that it is applied when observation_SAT > phasejump_SAT """ pass def add_phasejump(self, mjd, phasejump): """tempopulsar.add_phasejump(mjd,phasejump) Add a phase jump of value `phasejump` at time `mjd`. Note: due to the comparison observation_SAT > phasejump_SAT in tempo2, the exact MJD itself where the jump was added is not affected. """ pass def remove_phasejumps(self): """tempopulsar.remove_phasejumps() Remove all phase jumps.""" pass @property def nphasejumps(self): """Return the number of phase jumps.""" pass @property def pulse_number(self): """Return the pulse number relative to PEPOCH, as detected by tempo2 WARNING: Will be deprecated in the future. Use `pulsenumbers`. """ return self.model.phase(self.t.table).int.quantity.value def pulsenumbers(self, updatebats=True, formresiduals=True, removemean=True): """Return the pulse number relative to PEPOCH, as detected by tempo2 Returns the pulse numbers as a numpy array. Will update the TOAs/recompute residuals if `updatebats`/`formresiduals` is True (default for both). If that is requested, the residual mean is removed `removemean` is True. All this just like in `residuals`. """ return self.model.phase(self.t.table).int.quantity.value def fit(self, iters=1): """tempopulsar.fit(iters=1) Runs `iters` iterations of the tempo2 fit, recomputing barycentric TOAs and residuals each time.""" f = fitter.wls_fitter(toas=self.t, model=self.model) for ii in range(iters + 1): f.call_minimize() fitp = f.model.get_params_dict("free", "quantity") # TODO: handle these units correctly for p, val in zip(fitp.keys(), fitp.values()): modval = getattr(f.model, p).value if (not has_astropy_unit(val)) and has_astropy_unit(modval): if type(modval) is ang.Angle: val = ang.Angle(val, unit=modval.unit) else: val = val * modval.unit self[p].val = val def chisq(self, removemean="weighted"): """tempopulsar.chisq(removemean='weighted') Computes the chisq of current residuals vs errors, removing the noise-weighted residual, unless specified otherwise.""" res, err = self.residuals(removemean=removemean), self.toaerrs return np.sum(res * res / (1e-12 * err * err)) def rms(self, removemean="weighted"): """tempopulsar.rms(removemean='weighted') Computes the current residual rms, removing the noise-weighted residual, unless specified otherwise.""" err = self.toaerrs norm = np.sum(1.0 / (1e-12 * err * err)) return np.sqrt(self.chisq(removemean=removemean) / norm) def savepar(self, parfile): """tempopulsar.savepar(parfile) Save current par file (calls tempo2's `textOutput(...)`).""" pass def savetim(self, timfile): """tempopulsar.savetim(timfile) Save current par file (calls tempo2's `writeTim(...)`).""" pass
def test_model(): log.setLevel("ERROR") # for nice output info, set the following instead # log.setLevel('INFO') os.chdir(datadir) parfile = "J1744-1134.basic.par" t1_parfile = "J1744-1134.t1.par" timfile = "J1744-1134.Rcvr1_2.GASP.8y.x.tim" m = tm.get_model(parfile) log.info("model.as_parfile():\n%s" % m.as_parfile()) try: planets = m.PLANET_SHAPIRO.value except AttributeError: planets = False t0 = time.time() t = toa.get_TOAs(timfile, planets=planets, include_bipm=False, usepickle=False) time_toa = time.time() - t0 if log.level < 25: t.print_summary() log.info("Read/corrected TOAs in %.3f sec" % time_toa) mjds = t.get_mjds() errs = t.get_errors() log.info("Computing residuals...") t0 = time.time() resids_us = Residuals(t, m, use_weighted_mean=False).time_resids.to(u.us) time_phase = time.time() - t0 log.info("Computed phases and residuals in %.3f sec" % time_phase) # resids in (approximate) us: log.info("RMS PINT residuals are %.3f us" % resids_us.std().value) # Get some general2 stuff log.info("Running TEMPO2...") # Old tempo2 values output # tempo2_vals = tempo2_utils.general2(parfile, timfile, # ['tt2tb', 'roemer', 'post_phase', # 'shapiro', 'shapiroJ']) tempo2_vals = np.genfromtxt(parfile + ".tempo2_test", names=True, comments="#", dtype=np.longdouble) t2_resids = tempo2_vals["post_phase"] / float(m.F0.value) * 1e6 * u.us diff_t2 = (resids_us - t2_resids).to(u.ns) diff_t2 -= diff_t2.mean() log.info("Max resid diff between PINT and T2: %.2f ns" % np.fabs(diff_t2).max().value) log.info("Std resid diff between PINT and T2: %.2f ns" % diff_t2.std().value) assert np.fabs(diff_t2).max() < 10.0 * u.ns # run tempo1 also, if the tempo_utils module is available did_tempo1 = False try: import tempo_utils log.info("Running TEMPO1...") t1_result = np.genfromtxt(t1_parfile + ".tempo_test", names=True, comments="#", dtype=np.longdouble) t1_resids = t1_result["residuals_phase"] / float( m.F0.value) * 1e6 * u.us did_tempo1 = True diff_t1 = (resids_us - t1_resids).to(u.ns) diff_t1 -= diff_t1.mean() log.info("Max resid diff between PINT and T1: %.2f ns" % np.fabs(diff_t1).max().value) log.info("Std resid diff between PINT and T1: %.2f ns" % diff_t1.std().value) diff_t2_t1 = (t2_resids - t1_resids).to(u.ns) diff_t2_t1 -= diff_t2_t1.mean() log.info("Max resid diff between T1 and T2: %.2f ns" % np.fabs(diff_t2_t1).max().value) log.info("Std resid diff between T1 and T2: %.2f ns" % diff_t2_t1.std().value) except: pass if did_tempo1 and not planets: assert np.fabs(diff_t1).max() < 32.0 * u.ns def do_plot(): plt.clf() plt.subplot(211) plt.hold(False) plt.errorbar(mjds.value, resids_us.value, errs.to(u.us).value, fmt=None, label="PINT") plt.title("J1744-1134 GBT/GASP timing") plt.xlabel("MJD") plt.ylabel("Residual (us)") plt.legend() plt.grid() plt.subplot(212) plt.plot(mjds, diff_t2, label="PINT - T2") plt.hold(True) if did_tempo1: plt.plot(mjds, diff_t1, label="PINT - T1") plt.grid() plt.xlabel("MJD") plt.ylabel("Residual diff (ns)") plt.legend() if log.level < 25: do_plot() plt.show()