def test_add_all_components(): # models_by_category = defaultdict(list) # for k, c_type in Component.component_types.items(): # print(k, c_type) # models_by_category[c_type.category].append(c_type) comps = [x() for x in Component.component_types.values()] tm = TimingModel(name="test_manual", components=comps) tm.setup() # This should not have any parameter check. assert len(tm.components) == len(comps)
def test_timing_model(): ac = AllComponents() timing_model = TimingModel( name="Test", components=[ ac.components["AstrometryEquatorial"], ac.components["Spindown"], ac.components["DispersionDMX"], ac.components["PhaseJump"], ac.components["ScaleToaError"], ], ) return timing_model
def test_simple_manual(): tm = TimingModel( name="test_manual", components=[AstrometryEquatorial(), Spindown()] ) tm.setup() assert "F0" in tm.phase_deriv_funcs.keys() assert "F1" in tm.phase_deriv_funcs.keys() assert "RAJ" in tm.delay_deriv_funcs.keys() assert "DECJ" in tm.delay_deriv_funcs.keys() with pytest.raises(MissingParameter): # No RA and DEC input tm.validate() tm.RAJ.value = "19:59:48" tm.DECJ.value = "20:48:36" tm.F0.value = 622.122030511927 * u.Hz tm.validate() # This should work.
def test_simple_manual(): tm = TimingModel( name="test_manual", components=[AstrometryEquatorial(), Spindown()] ) tm.setup() assert "F0" in tm.phase_deriv_funcs.keys() assert "RAJ" in tm.delay_deriv_funcs.keys() assert "DECJ" in tm.delay_deriv_funcs.keys() with pytest.raises(MissingParameter): # No RA and DEC input tm.validate() tm.RAJ.value = "19:59:48" tm.DECJ.value = "20:48:36" tm.F0.quantity = 622.122030511927 * u.Hz tm.PEPOCH.value = 48196.0 tm.validate() # This should work. # When there is no POSEPOCH set, it should just be unset assert tm.POSEPOCH.value is None
def __call__(self, parfile, allow_name_mixing=False): """Callable object for making a timing model from .par file. Parameter --------- parfile: str or file-like object Input .par file name or string contents allow_name_mixing : bool, optional Flag for allowing the input to have mixing aliases names for the same parameter. For example, if this flag is true, one can have T2EFAC and EFAC, both of them maps to PINT parameter EFAC, present in the parfile at the same time. Return ------ pint.models.timing_model.TimingModel The result timing model based on the input .parfile or file object. """ pint_param_dict, original_name, unknown_param = self._pintify_parfile( parfile, allow_name_mixing) selected, conflict, param_not_in_pint = self.choose_model( pint_param_dict) selected.update(set(self.default_components)) # Report conflict if len(conflict) != 0: self._report_conflict(conflict) # Make timing model cps = [self.all_components.components[c] for c in selected] tm = TimingModel(components=cps) self._setup_model(tm, pint_param_dict, original_name, setup=True, validate=True) # Report unknown line for k, v in unknown_param.items(): p_line = " ".join([k] + v) warnings.warn(f"Unrecognized parfile line '{p_line}'", UserWarning) # log.warning(f"Unrecognized parfile line '{p_line}'") return tm
def choose_model(parfile, category_order=None, name=None, check_for_missing_parameters=False): """Determine which model components are appropriate for parfile.""" if name is None: if isinstance(parfile, str): name = os.path.basename(parfile) else: name = "" if category_order is None: category_order = DEFAULT_ORDER models_by_category = defaultdict(list) for k, c_type in Component.component_types.items(): models_by_category[c_type.category].append(c_type) par_dict = {} par_lines = [] multi_tags = set([ "JUMP", "ECORR", "T2EFAC", "T2EQUAD", "EQUAD", "EFAC", "DMJUMP", "DMEFAC", "DMEQUAD", ]) multi_line = Counter() for l in interesting_lines(lines_of(parfile), comments=("#", "C ")): ll = l.split() k = ll[0] if k in multi_tags: multi_line[k] += 1 k = k + str(multi_line[k]) if k in par_dict: # FIXME: what happens with JUMPs? log.info("Lines with duplicate keys in par file: {} and {}".format( [k] + par_dict[k], ll)) par_dict[k] = ll[1:] par_lines.append(l) models_to_use = {} for category, models in models_by_category.items(): acceptable = [] for m_type in models: m = m_type() if m.is_in_parfile(par_dict): acceptable.append(m) if len(acceptable) > 1: raise ValueError( "Multiple models are compatible with this par file: {}".format( acceptable)) if acceptable: models_to_use[category] = acceptable[0] if "BINARY" in par_dict: vals = par_dict["BINARY"] if len(vals) != 1: raise ValueError("Mal-formed binary model selection: {}".format( repr(" ".join(["BINARY"] + vals)))) (bm, ) = vals if "pulsar_system" not in models_to_use: # Either we're missing parameters or the model is bogus # FIXME: distinguish raise UnknownBinaryModel( "Unknown binary model requested in par file: {}".format(bm)) # FIXME: consistency check - the componens actually chosen should know the name bm models_in_order = [] for category in category_order: try: models_in_order.append(models_to_use.pop(category)) except KeyError: pass models_in_order.extend(v for k, v in sorted(models_to_use.items())) tm = TimingModel(name, models_in_order) # FIXME: this should go in TimingModel for when you try to # add conflicting components alias_map = {} for prefix_type in ["prefixParameter", "maskParameter"]: for pn in tm.get_params_of_type_top(prefix_type): par = getattr(tm, pn) for a in [par.prefix] + par.prefix_aliases: if a in alias_map: raise ValueError( "Two prefix/mask parameters have the same " "alias {}: {} and {}".format(a, alias_map[a], par)) alias_map[a] = par leftover_params = par_dict.copy() for k in tm.get_params_mapping(): leftover_params.pop(k, None) for a in getattr(tm, k).aliases: leftover_params.pop(a, None) for p in leftover_params: try: pre, idxstr, idxV = split_prefixed_name(p) try: par = alias_map[pre] except KeyError: if pre in ignore_prefix: # log.warning("Ignoring unhandled prefix {}".format(pre)) continue else: raise ValueError( "Mystery parameter {}, prefix {} with number {}". format(p, pre, idxV)) component = tm.get_params_mapping()[par.name] new_parameter = par.new_param(idxV) if hasattr(tm, new_parameter.name): raise ValueError("Received duplicate parameter {}".format( new_parameter.name)) tm.add_param_from_top(new_parameter, component) # print("added", new_parameter) except PrefixError: pass return tm
class ModelBuilder: """A class for model construction interface. Parameters ---------- name : str Name for the model. parfile : str optional The .par file input for select components. If the parfile is provided the self.model_instance will be put model instance with .par file read in. If it is not provided, self.model_instance will return as None. Returns ------- A class contains the result model instance if parfile is provided and method to build the model. """ def __init__(self, parfile=None, name=""): self.timing_model = None self.name = name self.param_inparF = None self.param_unrecognized = {} self.param_inModel = [] self.param_prefix = {} self.select_comp = {} self.control_params = ["EPHEM", "CLK"] if parfile is not None: self.parfile = parfile self.build_model(self.parfile, self.name) def __str__(self): result = "Model name : " + self.name + "\n" result += "Components in the model : \n" for c in self.select_comp: result += " " + str(c) + "\n" if self.model_instance is not None: result += "Read parameters from : " + self.parfile + "\n" result += "The model instance is :\n" + str(self.model_instance) return result def preprocess_parfile(self, parfile): """Preprocess the par file. Return ------ A dictionary with all the parfile parameters with values in string """ param = {} repeat_par = {} pfile = open(parfile, "r") for l in [pl.strip() for pl in pfile.readlines()]: # Skip blank lines if not l: continue # Skip commented lines if l.startswith("#") or l[:2] == "C ": continue k = l.split() if (k[0] in param.keys() ): # repeat parameter TODO: add JUMP1 even there is only one if k[0] in repeat_par.keys(): repeat_par[k[0]] += 1 else: repeat_par[k[0]] = 2 param[k[0] + str(repeat_par[k[0]])] = k[1:] else: param[k[0]] = k[1:] self.param_inparF = param for key in repeat_par.keys(): self.param_inparF[key + str(1)] = self.param_inparF.pop(key) pfile.close() return self.param_inparF def get_all_categories(self, ): """Obtain a dictionary from category to a list of instances.""" comp_category = defaultdict(list) for k, cp in Component.component_types.items(): comp_category[cp.category].append(cp()) return dict(comp_category) def get_comp_from_parfile(self, parfile): """Right now we only have one component on each category.""" params_inpar = self.preprocess_parfile(parfile) for cat, cmps in self.get_all_categories().items(): selected_c = None for cpi in cmps: if cpi.component_special_params: if any(par in params_inpar for par in cpi.component_special_params): selected_c = cpi # Once have match, stop searching break else: continue else: if cpi.is_in_parfile(params_inpar): selected_c = cpi if selected_c is not None: self.select_comp[cat] = selected_c def sort_components(self, category_order=DEFAULT_ORDER): """Sort the components into order. Parameters ---------- category_order: list, optional The order for the order sensitive component categories. Note ---- If a category is not listed in the category_order, it will be treated as order non-sensitive category and put in the end of sorted order list. """ sorted_components = [] for cat in self.get_all_categories(): # FIXME, I am not sure adding orders here is a good idea. if cat not in category_order: category_order.append(cat) for co in category_order: if co not in self.select_comp: continue cp = self.select_comp[co] sorted_components.append(cp) return sorted_components def search_prefix_param(self, paramList, model, prefix_type): """Check if the Unrecognized parameter has prefix parameter""" prefixs = {} prefix_inModel = model.get_params_of_type_top(prefix_type) for pn in prefix_inModel: par = getattr(model, pn) prefixs[par.prefix] = [] for p in paramList: try: pre, idxstr, idxV = split_prefixed_name(p) if pre in [par.prefix] + par.prefix_aliases: prefixs[par.prefix].append(p) except ValueError: # FIXME: is this meant to catch KeyErrors? continue return prefixs def build_model(self, parfile=None, name=""): """Read parfile using the model_instance attribute. Throws error if mismatched coordinate systems detected. Parameters --------- name: str, optional The name for the timing model parfile : str optional The parfile name """ if parfile is not None: self.get_comp_from_parfile(parfile) # ensure coordinate systems match for POS and PM if "RAJ" in self.preprocess_parfile(parfile).keys(): if "PMELONG" in self.preprocess_parfile(parfile): raise AttributeError( "Cannot have Ecliptic proper motion parameters (PMELONG/PMELAT) with Equatorial position parameters (RAJ/DECJ) in par file." ) elif "PMELAT" in self.preprocess_parfile(parfile): raise AttributeError( "Cannot have Ecliptic proper motion parameters (PMELONG/PMELAT) with Equatorial position parameters (RAJ/DECJ) in par file." ) elif "ELONG" in self.preprocess_parfile(parfile).keys(): if "PMRA" in self.preprocess_parfile(parfile): raise AttributeError( "Cannot have Equatorial proper motion parameters (PMRA/PMDEC) with Ecliptic position parameters (ELONG/ELAT) in par file." ) elif "PMDEC" in self.preprocess_parfile(parfile): raise AttributeError( "Cannot have Equatorial proper motion parameters (PMRA/PMDEC) with Ecliptic position parameters (ELONG/ELAT) in par file." ) sorted_comps = self.sort_components() self.timing_model = TimingModel(name, sorted_comps) param_inModel = self.timing_model.get_params_mapping() # Find unrecognised parameters in par file. if self.param_inparF is not None: parName = [] # add aliases for p in list(param_inModel.keys()): parName += getattr(self.timing_model, p).aliases parName += param_inModel.keys() for pp in self.param_inparF.keys(): if pp not in parName: self.param_unrecognized[pp] = self.param_inparF[pp] for ptype in ["prefixParameter", "maskParameter"]: prefix_param = self.search_prefix_param( self.param_unrecognized, self.timing_model, ptype) prefix_in_model = self.timing_model.get_params_of_type_top( ptype) for key in prefix_param: ppnames = [x for x in prefix_in_model if x.startswith(key)] for ppn in ppnames: pfx, idxs, idxv = split_prefixed_name(ppn) if pfx == key: exm_par = getattr(self.timing_model, ppn) else: continue exm_par_comp = param_inModel[exm_par.name] for parname in prefix_param[key]: pre, idstr, idx = split_prefixed_name(parname) if idx == exm_par.index: continue if hasattr(exm_par, "new_param"): new_par = exm_par.new_param(idx) self.timing_model.add_param_from_top( new_par, exm_par_comp) if "BINARY" in self.param_inparF: vals = self.param_inparF["BINARY"] if len(vals) != 1: raise ValueError( "Mal-formed binary model selection: {}".format( repr(" ".join(["BINARY"] + vals)))) (bm, ) = vals cats = self.timing_model.get_components_by_category() if "pulsar_system" not in cats: raise UnknownBinaryModel( "Unknown binary model requested in par file: {}". format(bm)) # FIXME: consistency check - the componens actually chosen should know the name bm for p in self.timing_model.params: if isinstance(self.timing_model[p], maskParameter): # maskParameters need a bogus alias for parfile parsing # remove this bogus alias try: ix = self.timing_model[p].aliases.index( self.timing_model[p].prefix) except ValueError: pass else: del self.timing_model[p].aliases[ix] if parfile is not None: self.timing_model.read_parfile(parfile) def get_control_info(self): info = {} if not self.param_unrecognized == {}: for ctrlp in self.control_params: if ctrlp in self.param_unrecognized: info[ctrlp] = self.param_unrecognized[ctrlp] else: # Check if the prefix match for p in self.control_params.keys(): if p.startswith(ctrlp): info[ctrlp] = self.param_unrecognized[ctrlp] return info
def build_model(self, parfile=None, name=""): """Read parfile using the model_instance attribute. Throws error if mismatched coordinate systems detected. Parameters --------- name: str, optional The name for the timing model parfile : str optional The parfile name """ if parfile is not None: self.get_comp_from_parfile(parfile) # ensure coordinate systems match for POS and PM if "RAJ" in self.preprocess_parfile(parfile).keys(): if "PMELONG" in self.preprocess_parfile(parfile): raise AttributeError( "Cannot have Ecliptic proper motion parameters (PMELONG/PMELAT) with Equatorial position parameters (RAJ/DECJ) in par file." ) elif "PMELAT" in self.preprocess_parfile(parfile): raise AttributeError( "Cannot have Ecliptic proper motion parameters (PMELONG/PMELAT) with Equatorial position parameters (RAJ/DECJ) in par file." ) elif "ELONG" in self.preprocess_parfile(parfile).keys(): if "PMRA" in self.preprocess_parfile(parfile): raise AttributeError( "Cannot have Equatorial proper motion parameters (PMRA/PMDEC) with Ecliptic position parameters (ELONG/ELAT) in par file." ) elif "PMDEC" in self.preprocess_parfile(parfile): raise AttributeError( "Cannot have Equatorial proper motion parameters (PMRA/PMDEC) with Ecliptic position parameters (ELONG/ELAT) in par file." ) sorted_comps = self.sort_components() self.timing_model = TimingModel(name, sorted_comps) param_inModel = self.timing_model.get_params_mapping() # Find unrecognised parameters in par file. if self.param_inparF is not None: parName = [] # add aliases for p in list(param_inModel.keys()): parName += getattr(self.timing_model, p).aliases parName += param_inModel.keys() for pp in self.param_inparF.keys(): if pp not in parName: self.param_unrecognized[pp] = self.param_inparF[pp] for ptype in ["prefixParameter", "maskParameter"]: prefix_param = self.search_prefix_param( self.param_unrecognized, self.timing_model, ptype) prefix_in_model = self.timing_model.get_params_of_type_top( ptype) for key in prefix_param: ppnames = [x for x in prefix_in_model if x.startswith(key)] for ppn in ppnames: pfx, idxs, idxv = split_prefixed_name(ppn) if pfx == key: exm_par = getattr(self.timing_model, ppn) else: continue exm_par_comp = param_inModel[exm_par.name] for parname in prefix_param[key]: pre, idstr, idx = split_prefixed_name(parname) if idx == exm_par.index: continue if hasattr(exm_par, "new_param"): new_par = exm_par.new_param(idx) self.timing_model.add_param_from_top( new_par, exm_par_comp) if "BINARY" in self.param_inparF: vals = self.param_inparF["BINARY"] if len(vals) != 1: raise ValueError( "Mal-formed binary model selection: {}".format( repr(" ".join(["BINARY"] + vals)))) (bm, ) = vals cats = self.timing_model.get_components_by_category() if "pulsar_system" not in cats: raise UnknownBinaryModel( "Unknown binary model requested in par file: {}". format(bm)) # FIXME: consistency check - the componens actually chosen should know the name bm for p in self.timing_model.params: if isinstance(self.timing_model[p], maskParameter): # maskParameters need a bogus alias for parfile parsing # remove this bogus alias try: ix = self.timing_model[p].aliases.index( self.timing_model[p].prefix) except ValueError: pass else: del self.timing_model[p].aliases[ix] if parfile is not None: self.timing_model.read_parfile(parfile)
component_instances = [] # Initiate the component instances for cp_name in selected_components: component_class = all_components[cp_name] # Get the component class component_instance = component_class() # Instantiate a component object component_instances.append(component_instance) # %% [markdown] # ### Construct timing model (i.e., `TimingModel` instance) # # `TimingModel` class provides the storage and interface for the components. It also manages the components internally. # %% # Construct timing model instance, given a name and a list of components to include (that we just created above) tm = TimingModel("NGC6400E", component_instances) # %% [markdown] # ### View the components in the timing model instance # # To view all the components in `TimingModel` instance, we can use the property `.components`, which returns a dictionary (name as the key, component instance as the value). # # Internally, the components are stored in a list(ordered list, you will see why this is important below) according to their types. All the delay type of components (subclasses of `DelayComponent` class) are stored in the `DelayComponent_list`, and the phase type of components (subclasses of `PhaseComponent` class) in the `PhaseComponent_list`. # %% # print the components in the timing model for (cp_name, cp_instance) in tm.components.items(): print(cp_name, cp_instance) # %% [markdown] # ### Useful methods of `TimingModel`
def build_model(self, parfile=None, name=""): """Read parfile using the model_instance attribute. Parameters --------- name: str, optional The name for the timing model parfile : str optional The parfile name """ if parfile is not None: self.get_comp_from_parfile(parfile) sorted_comps = self.sort_components() self.timing_model = TimingModel(name, sorted_comps) param_inModel = self.timing_model.get_params_mapping() # Find unrecognised parameters in par file. if self.param_inparF is not None: parName = [] # add aliases for p in list(param_inModel.keys()): parName += getattr(self.timing_model, p).aliases parName += param_inModel.keys() for pp in self.param_inparF.keys(): if pp not in parName: self.param_unrecognized[pp] = self.param_inparF[pp] for ptype in ["prefixParameter", "maskParameter"]: prefix_param = self.search_prefix_param( self.param_unrecognized, self.timing_model, ptype) prefix_in_model = self.timing_model.get_params_of_type(ptype) for key in prefix_param: ppnames = [x for x in prefix_in_model if x.startswith(key)] for ppn in ppnames: pfx, idxs, idxv = split_prefixed_name(ppn) if pfx == key: exm_par = getattr(self.timing_model, ppn) else: continue exm_par_comp = param_inModel[exm_par.name] for parname in prefix_param[key]: pre, idstr, idx = split_prefixed_name(parname) if idx == exm_par.index: continue if hasattr(exm_par, "new_param"): new_par = exm_par.new_param(idx) self.timing_model.add_param_from_top( new_par, exm_par_comp) if "BINARY" in self.param_inparF: vals = self.param_inparF["BINARY"] if len(vals) != 1: raise ValueError( "Mal-formed binary model selection: {}".format( repr(" ".join(["BINARY"] + vals)))) (bm, ) = vals cats = self.timing_model.get_component_of_category() if "pulsar_system" not in cats: raise UnknownBinaryModel( "Unknown binary model requested in par file: {}". format(bm)) # FIXME: consistency check - the componens actually chosen should know the name bm if parfile is not None: self.timing_model.read_parfile(parfile)
from pint.models.binary_dd import BinaryDD from pint.models.binary_ddk import BinaryDDK from pint.models.binary_ell1 import BinaryELL1, BinaryELL1H from pint.models.dispersion_model import DispersionDM, DispersionDMX from pint.models.frequency_dependent import FD from pint.models.glitch import Glitch from pint.models.jump import DelayJump, PhaseJump from pint.models.model_builder import get_model from pint.models.noise_model import EcorrNoise, PLRedNoise, ScaleToaError from pint.models.solar_system_shapiro import SolarSystemShapiro from pint.models.solar_wind_dispersion import SolarWindDispersion from pint.models.spindown import Spindown # Import the main timing model classes from pint.models.timing_model import TimingModel, DEFAULT_ORDER from pint.models.wave import Wave from pint.models.ifunc import IFunc # Define a standard basic model StandardTimingModel = TimingModel( "StandardTimingModel", [AstrometryEquatorial(), Spindown(), DispersionDM(), SolarSystemShapiro()], ) # BTTimingModel = generate_timing_model("BTTimingModel", # (Astrometry, Spindown, Dispersion, SolarSystemShapiro, BT)) # DDTimingModel = generate_timing_model("DDTimingModel", # (Astrometry, Spindown, Dispersion, SolarSystemShapiro, DD))
def model(): """Make a simple model.""" return TimingModel( components=[AstrometryEquatorial(), DispersionDM(), DispersionDMX()] )
def test_forgot_name(): """Check argument validation in case 'name' is forgotten.""" with pytest.raises(ValueError): TimingModel(AstrometryEquatorial()) with pytest.raises(ValueError): TimingModel([AstrometryEquatorial(), DispersionDM()])