def TimingModel(coefficients=False, name="linear_timing_model", use_svd=False, normed=True): """Class factory for marginalized linear timing model signals.""" if normed is True: basis = utils.normed_tm_basis() elif isinstance(normed, np.ndarray): basis = utils.normed_tm_basis(norm=normed) elif use_svd is True: if normed is not True: msg = "use_svd == True is incompatible with normed != True" raise ValueError(msg) basis = utils.svd_tm_basis() else: basis = utils.unnormed_tm_basis() prior = utils.tm_prior() BaseClass = BasisGP(prior, basis, coefficients=coefficients, name=name) class TimingModel(BaseClass): signal_type = "basis" signal_name = "linear timing model" signal_id = name + "_svd" if use_svd else name if coefficients: def _get_coefficient_logprior(self, key, c, **params): # MV: probably better to avoid this altogether # than to use 1e40 as in get_phi return 0 return TimingModel
def TimingModel(coefficients=False, name="linear_timing_model", use_svd=False, normed=True): """Class factory for marginalized linear timing model signals.""" basis = get_timing_model_basis(use_svd, normed) prior = utils.tm_prior() BaseClass = BasisGP(prior, basis, coefficients=coefficients, name=name) class TimingModel(BaseClass): signal_type = "basis" signal_name = "linear timing model" signal_id = name + "_svd" if use_svd else name if coefficients: def _get_coefficient_logprior(self, key, c, **params): # MV: probably better to avoid this altogether # than to use 1e40 as in get_phi return 0 return TimingModel
def WidebandTimingModel( dmefac=parameter.Uniform(pmin=0.1, pmax=10.0), log10_dmequad=parameter.Uniform(pmin=-7.0, pmax=0.0), dmjump=parameter.Uniform(pmin=-0.01, pmax=0.01), dmefac_selection=Selection(selections.no_selection), log10_dmequad_selection=Selection(selections.no_selection), dmjump_selection=Selection(selections.no_selection), dmjump_ref=None, name="wideband_timing_model", ): """Class factory for marginalized linear timing model signals that take wideband TOAs and DMs. Currently assumes DMX for DM model.""" basis = utils.unnormed_tm_basis() # will need to normalize phi otherwise prior = utils.tm_prior() # standard BaseClass = BasisGP(prior, basis, coefficients=False, name=name) class WidebandTimingModel(BaseClass): signal_type = "basis" signal_name = "wideband timing model" signal_id = name basis_combine = False # should never need to be True def __init__(self, psr): super(WidebandTimingModel, self).__init__(psr) self.name = self.psrname + "_" + self.signal_id # make selection for DMEFACs dmefac_select = dmefac_selection(psr) self._dmefac_keys = list(sorted(dmefac_select.masks.keys())) self._dmefac_masks = [dmefac_select.masks[key] for key in self._dmefac_keys] # make selection for DMEQUADs log10_dmequad_select = log10_dmequad_selection(psr) self._log10_dmequad_keys = list(sorted(log10_dmequad_select.masks.keys())) self._log10_dmequad_masks = [log10_dmequad_select.masks[key] for key in self._log10_dmequad_keys] # make selection for DMJUMPs dmjump_select = dmjump_selection(psr) self._dmjump_keys = list(sorted(dmjump_select.masks.keys())) self._dmjump_masks = [dmjump_select.masks[key] for key in self._dmjump_keys] if self._dmjump_keys == [""] and dmjump is not None: raise ValueError("WidebandTimingModel: can only do DMJUMP with more than one selection.") # collect parameters self._params = {} self._dmefacs = [] for key in self._dmefac_keys: pname = "_".join([n for n in [psr.name, key, "dmefac"] if n]) param = dmefac(pname) self._dmefacs.append(param) self._params[param.name] = param self._log10_dmequads = [] for key in self._log10_dmequad_keys: pname = "_".join([n for n in [psr.name, key, "log10_dmequad"] if n]) param = log10_dmequad(pname) self._log10_dmequads.append(param) self._params[param.name] = param self._dmjumps = [] if dmjump is not None: for key in self._dmjump_keys: pname = "_".join([n for n in [psr.name, key, "dmjump"] if n]) if dmjump_ref is not None: if pname == psr.name + "_" + dmjump_ref + "_dmjump": fixed_dmjump = parameter.Constant(val=0.0) param = fixed_dmjump(pname) else: param = dmjump(pname) else: param = dmjump(pname) self._dmjumps.append(param) self._params[param.name] = param # copy psr quantities self._ntoas = len(psr.toas) self._npars = len(psr.fitpars) self._freqs = psr.freqs # collect DMX information (will be used to make phi and delay) self._dmpar = psr.dm self._dm = np.array(psr.flags["pp_dm"], "d") self._dmerr = np.array(psr.flags["pp_dme"], "d") check = np.zeros_like(psr.toas, "i") # assign TOAs to DMX bins self._dmx, self._dmindex, self._dmwhich = [], [], [] for index, key in enumerate(sorted(psr.dmx)): dmx = psr.dmx[key] if not dmx["fit"]: raise ValueError("WidebandTimingModel: all DMX parameters must be estimated.") self._dmx.append(dmx["DMX"]) self._dmindex.append(psr.fitpars.index(key)) self._dmwhich.append((dmx["DMXR1"] <= psr.stoas / 86400) & (psr.stoas / 86400 < dmx["DMXR2"])) check += self._dmwhich[-1] if np.sum(check) != self._ntoas: raise ValueError("WidebandTimingModel: cannot account for all TOAs in DMX intervals.") if "DM" in psr.fitpars: raise ValueError("WidebandTimingModel: DM must not be estimated.") self._ndmx = len(self._dmx) @property def delay_params(self): # cache parameters are all DMEFACS, DMEQUADS, and DMJUMPS return ( [p.name for p in self._dmefacs] + [p.name for p in self._log10_dmequads] + [p.name for p in self._dmjumps] ) @signal_base.cache_call(["delay_params"]) def get_phi(self, params): """Return wideband timing-model prior.""" # get DMEFAC- and DMEQUAD-adjusted DMX errors dme = self.get_dme(params) # initialize the timing-model "infinite" prior phi = KernelMatrix(1e40 * np.ones(self._npars, "d")) # fill the DMX slots with weighted errors for index, which in zip(self._dmindex, self._dmwhich): phi.set(1.0 / np.sum(1.0 / dme[which] ** 2), index) return phi def get_phiinv(self, params): """Return inverse prior (using KernelMatrix inv).""" return self.get_phi(params).inv() @signal_base.cache_call(["delay_params"]) def get_delay(self, params): """Return the weighted-mean DM correction that applies for each residual. (Will be the same across each DM bin, before measurement-frequency weighting.)""" dm_delay = np.zeros(self._ntoas, "d") avg_dm = self.get_mean_dm(params) for dmx, which in zip(self._dmx, self._dmwhich): dm_delay[which] = avg_dm[which] - (self._dmpar + dmx) return dm_delay / (2.41e-4 * self._freqs ** 2) @signal_base.cache_call(["delay_params"]) def get_dm(self, params): """Return DMJUMP-adjusted DM measurements.""" return ( sum( (params[jump.name] if jump.name in params else jump.value) * mask for jump, mask in zip(self._dmjumps, self._dmjump_masks) ) + self._dm ) @signal_base.cache_call(["delay_params"]) def get_dme(self, params): """Return EFAC- and EQUAD-weighted DM errors.""" return ( sum( (params[efac.name] if efac.name in params else efac.value) * mask for efac, mask in zip(self._dmefacs, self._dmefac_masks) ) ** 2 * self._dmerr ** 2 + ( 10 ** sum( (params[equad.name] if equad.name in params else equad.value) * mask for equad, mask in zip(self._log10_dmequads, self._log10_dmequad_masks) ) ) ** 2 ) ** 0.5 @signal_base.cache_call(["delay_params"]) def get_mean_dm(self, params): """Get weighted DMX estimates (distributed to TOAs).""" mean_dm = np.zeros(self._ntoas, "d") # DMEFAC- and DMJUMP-adjusted dm, dme = self.get_dm(params), self.get_dme(params) for which in self._dmwhich: mean_dm[which] = np.sum(dm[which] / dme[which] ** 2) / np.sum(1.0 / dme[which] ** 2) return mean_dm @signal_base.cache_call(["delay_params"]) def get_mean_dme(self, params): """Get weighted DMX uncertainties (distributed to TOAs). Note that get_phi computes these variances directly.""" mean_dme = np.zeros(self._ntoas, "d") # DMEFAC- and DMJUMP-adjusted dme = self.get_dme(params) for which in self._dmwhich: mean_dme[which] = np.sqrt(1.0 / np.sum(1.0 / dme[which] ** 2)) return mean_dme @signal_base.cache_call(["delay_params"]) def get_logsignalprior(self, params): """Get an additional likelihood/prior term to cover terms that would not affect optimization, were they not dependent on DMEFAC, DMEQUAD, and DMJUMP.""" dm, dme = self.get_dm(params), self.get_dme(params) mean_dm, mean_dme = self.get_mean_dm(params), self.get_mean_dme(params) # now this is a bit wasteful, because it makes copies of the mean DMX and DMXERR # and only uses the first value, but it shouldn't cost us too much expterm = -0.5 * np.sum(dm ** 2 / dme ** 2) expterm += 0.5 * sum(mean_dm[which][0] ** 2 / mean_dme[which][0] ** 2 for which in self._dmwhich) # sum_i [-0.5 * log(dmerr**2)] = -sum_i log dmerr; same for mean_dmerr logterm = -np.sum(np.log(dme)) + sum(np.log(mean_dme[which][0]) for which in self._dmwhich) return expterm + logterm # these are for debugging, but should not enter the likelihood computation def get_delta_dm(self, params, use_mean_dm=False): # DM - DMX delta_dm = np.zeros(self._ntoas, "d") if use_mean_dm: dm = self.get_mean_dm(params) else: dm = self.get_dm(params) # DMJUMP-adjusted for dmx, which in zip(self._dmx, self._dmwhich): delta_dm[which] = dm[which] - (self._dmpar + dmx) return delta_dm def get_dm_chi2(self, params, use_mean_dm=False): # 'DM' chi-sqaured delta_dm = self.get_delta_dm(params, use_mean_dm=use_mean_dm) if use_mean_dm: dme = self.get_mean_dme(params) chi2 = 0.0 for idmx, which in enumerate(self._dmwhich): chi2 += (delta_dm[which][0] / dme[which][0]) ** 2 else: dme = self.get_dme(params) # DMEFAC- and DMEQUAD-adjusted chi2 = np.sum((delta_dm / dme) ** 2) return chi2 return WidebandTimingModel