diff --git a/.gitignore b/.gitignore index b76b751df..64a407117 100644 --- a/.gitignore +++ b/.gitignore @@ -119,4 +119,10 @@ docs/examples/*.ipynb docs/examples-rendered/*.py # VSCode wants to put virtualenvs here -.env \ No newline at end of file +.env + +# pintpublish output +*.tex +*.aux +*.log +*.pdf \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml index 391de3894..4bfc807fc 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -17,7 +17,8 @@ build: # Build documentation in the docs/ directory with Sphinx sphinx: - configuration: docs/conf.py + builder: html + configuration: docs/conf.py # If using Sphinx, optionally build your docs in additional formats such as PDF formats: @@ -29,4 +30,4 @@ python: - requirements: requirements_dev.txt - requirements: requirements.txt - method: pip - path: . \ No newline at end of file + path: . diff --git a/CHANGELOG-unreleased.md b/CHANGELOG-unreleased.md index f00179747..f6d1c4cad 100644 --- a/CHANGELOG-unreleased.md +++ b/CHANGELOG-unreleased.md @@ -13,7 +13,9 @@ the released changes. - Moved design matrix normalization code from `pint.fitter` to the new `pint.utils.normalize_designmatrix()` function. - Made `Residuals` independent of `GLSFitter` (GLS chi2 is now computed using the new function `Residuals._calc_gls_chi2()`). ### Added -- Added WaveX model as DelayComponent with wave amplitudes as fitted parameters +- Added `WaveX` model as a `DelayComponent` with Fourier amplitudes as fitted parameters +- `Parameter.as_latex` method for latex representation of a parameter. +- `pint.output.publish` module and `pintpublish` script for generating publication (LaTeX) output. - Added radial velocity methods for binary models ### Fixed - Wave model `validate()` can correctly use PEPOCH to assign WAVEEPOCH parameter diff --git a/setup.cfg b/setup.cfg index 80a0c6408..52fa9ec59 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,6 +61,7 @@ console_scripts = convert_parfile = pint.scripts.convert_parfile:main compare_parfiles = pint.scripts.compare_parfiles:main tcb2tdb = pint.scripts.tcb2tdb:main + pintpublish = pint.scripts.pintpublish:main # See the docstring in versioneer.py for instructions. Note that you must diff --git a/src/pint/models/absolute_phase.py b/src/pint/models/absolute_phase.py index 9d319e2eb..940bea474 100644 --- a/src/pint/models/absolute_phase.py +++ b/src/pint/models/absolute_phase.py @@ -30,19 +30,21 @@ def __init__(self): super().__init__() self.add_param( MJDParameter( - name="TZRMJD", description="Epoch of the zero phase.", time_scale="utc" + name="TZRMJD", + description="Epoch of the zero phase TOA.", + time_scale="utc", ) ) self.add_param( strParameter( - name="TZRSITE", description="Observatory of the zero phase measured." + name="TZRSITE", description="Observatory of the zero phase TOA." ) ) self.add_param( floatParameter( name="TZRFRQ", units=u.MHz, - description="The frequency of the zero phase measured.", + description="The frequency of the zero phase TOA.", ) ) self.tz_cache = None diff --git a/src/pint/models/binary_ell1.py b/src/pint/models/binary_ell1.py index f23c1b51e..8e3c93b4e 100644 --- a/src/pint/models/binary_ell1.py +++ b/src/pint/models/binary_ell1.py @@ -117,7 +117,7 @@ def __init__(self): floatParameter( name="EPS1", units="", - description="First Laplace-Lagrange parameter, ECC x sin(OM) for ELL1 model", + description="First Laplace-Lagrange parameter, ECC*sin(OM)", long_double=True, ) ) @@ -126,7 +126,7 @@ def __init__(self): floatParameter( name="EPS2", units="", - description="Second Laplace-Lagrange parameter, ECC x cos(OM) for ELL1 model", + description="Second Laplace-Lagrange parameter, ECC*cos(OM)", long_double=True, ) ) diff --git a/src/pint/models/frequency_dependent.py b/src/pint/models/frequency_dependent.py index 00afaeb8e..3094a8a9a 100644 --- a/src/pint/models/frequency_dependent.py +++ b/src/pint/models/frequency_dependent.py @@ -36,7 +36,7 @@ def __init__(self): name="FD1", units="second", value=0.0, - description="Coefficient of delay as a polynomial function of log-frequency", + description="Polynomial coefficient of log-frequency-dependent delay", # descriptionTplt=lambda x: ( # "%d term of frequency" " dependent coefficients" % x # ), diff --git a/src/pint/models/jump.py b/src/pint/models/jump.py index 17e231d25..b133040d2 100644 --- a/src/pint/models/jump.py +++ b/src/pint/models/jump.py @@ -93,7 +93,7 @@ def __init__(self): maskParameter( name="JUMP", units="second", - description="Amount to jump the selected TOAs by.", + description="Phase jump for selection.", ) ) self.phase_funcs_component += [self.jump_phase] diff --git a/src/pint/models/noise_model.py b/src/pint/models/noise_model.py index 02a5d291a..49036fd8a 100644 --- a/src/pint/models/noise_model.py +++ b/src/pint/models/noise_model.py @@ -58,8 +58,7 @@ def __init__( name="EFAC", units="", aliases=["T2EFAC", "TNEF"], - description="A multiplication factor on" - " the measured TOA uncertainties,", + description="A multiplication factor for the measured TOA uncertainties,", ) ) @@ -68,9 +67,7 @@ def __init__( name="EQUAD", units="us", aliases=["T2EQUAD"], - description="An error term added in " - "quadrature to the scaled (by" - " EFAC) TOA uncertainty.", + description="An error term added in quadrature to the TOA uncertainty.", ) ) @@ -78,10 +75,7 @@ def __init__( maskParameter( name="TNEQ", units=u.LogUnit(physical_unit=u.second), - description="An error term added in " - "quadrature to the scaled (by" - " EFAC) TOA uncertainty in " - " the unit of log10(second).", + description="A log10-scale error term added in quadrature to the TOA uncertainty", ) ) self.covariance_matrix_funcs += [self.sigma_scaled_cov_matrix] @@ -307,9 +301,7 @@ def __init__( name="ECORR", units="us", aliases=["TNECORR"], - description="An error term added that" - " correlated all TOAs in an" - " observing epoch.", + description="An error term that is correlated among all TOAs in an observing epoch.", ) ) diff --git a/src/pint/models/parameter.py b/src/pint/models/parameter.py index 016687563..5bf0e9d48 100644 --- a/src/pint/models/parameter.py +++ b/src/pint/models/parameter.py @@ -571,6 +571,26 @@ def from_parfile_line(self, line): self.uncertainty = self._set_uncertainty(ucty) return True + def value_as_latex(self): + return f"${self.as_ufloat():.1uSL}$" if not self.frozen else f"{self.value:f}" + + def as_latex(self): + try: + unit_latex = ( + "" + if self.units == "" or self.units is None + else f" ({self.units.to_string(format='latex', fraction=False)})" + ) + except TypeError: + # to deal with old astropy + unit_latex = ( + "" + if self.units == "" or self.units is None + else f" ({self.units.to_string(format='latex')})" + ) + value_latex = self.value_as_latex() + return f"{self.name}, {self.description}{unit_latex}", value_latex + def add_alias(self, alias): """Add a name to the list of aliases for this parameter.""" self.aliases.append(alias) @@ -868,6 +888,9 @@ def _set_quantity(self, val): """Convert to string.""" return str(val) + def value_as_latex(self): + return self.value + class boolParameter(Parameter): """Boolean-valued parameter. @@ -935,6 +958,9 @@ def _set_quantity(self, val): return bool(float(val)) return bool(val) + def value_as_latex(self): + return "Y" if self.value else "N" + class intParameter(Parameter): """Integer parameter values. @@ -1003,6 +1029,9 @@ def _set_quantity(self, val): return ival + def value_as_latex(self): + return str(self.value) + class MJDParameter(Parameter): """Parameters for MJD quantities. @@ -1166,6 +1195,18 @@ def as_ufloats(self): error = self.uncertainty.to_value(u.d) if self.uncertainty is not None else 0 return ufloat(value1, 0), ufloat(value2, error) + def as_ufloat(self): + """Return the parameter as a :class:`uncertainties.ufloat` + value. + + If the uncertainty is not set will be returned as 0 + + Returns + ------- + uncertainties.ufloat + """ + return ufloat(self.value, self.uncertainty_value) + class AngleParameter(Parameter): """Parameter in angle units. @@ -1300,6 +1341,32 @@ def _print_uncertainty(self, unc): angle_arcsec /= 15.0 return angle_arcsec.to_string(decimal=True, precision=20) + def as_ufloat(self, units=None): + """Return the parameter as a :class:`uncertainties.ufloat` + + Will cast to the specified units, or the default + If the uncertainty is not set will be returned as 0 + + Parameters + ---------- + units : astropy.units.core.Unit, optional + Units to cast the value + + Returns + ------- + uncertainties.ufloat + + Notes + ----- + Currently :class:`~uncertainties.ufloat` does not support double precision values, + so some precision may be lost. + """ + if units is None: + units = self.units + value = self.quantity.to_value(units) if self.quantity is not None else 0 + error = self.uncertainty.to_value(units) if self.uncertainty is not None else 0 + return ufloat(value, error) + class prefixParameter: """Families of parameters identified by a prefix like ``DMX_0123``. @@ -1547,6 +1614,9 @@ def name_matches(self, name): def as_parfile_line(self, format="pint"): return self.param_comp.as_parfile_line(format=format) + def as_latex(self): + return self.param_comp.as_latex() + def help_line(self): return self.param_comp.help_line() @@ -1892,6 +1962,25 @@ def as_parfile_line(self, format="pint"): line += " 1" return line + "\n" + def as_latex(self): + try: + unit_latex = ( + "" + if self.units == "" or self.units is None + else f" ({self.units.to_string(format='latex', fraction=False)})" + ) + except TypeError: + # `fraction` option is not available in old astropy versions. + unit_latex = ( + "" + if self.units == "" or self.units is None + else f" ({self.units.to_string(format='latex')})" + ) + return ( + f"{self.prefix} {self.key} {' '.join(self.key_value)}, {self.description}{unit_latex}", + self.value_as_latex(), + ) + def new_param(self, index, copy_all=False): """Create a new but same style mask parameter""" return ( diff --git a/src/pint/models/pulsar_binary.py b/src/pint/models/pulsar_binary.py index 5e32079ad..b27dc923c 100644 --- a/src/pint/models/pulsar_binary.py +++ b/src/pint/models/pulsar_binary.py @@ -102,7 +102,9 @@ def __init__(self): ) self.add_param( floatParameter( - name="A1", units=ls, description="Projected semi-major axis, a*sin(i)" + name="A1", + units=ls, + description="Projected semi-major axis of pulsar orbit, ap*sin(i)", ) ) # NOTE: the DOT here takes the value and times 1e-12, tempo/tempo2 can @@ -112,7 +114,7 @@ def __init__(self): name="A1DOT", aliases=["XDOT"], units=ls / u.s, - description="Derivative of projected semi-major axis, da*sin(i)/dt", + description="Derivative of projected semi-major axis, d[ap*sin(i)]/dt", unit_scale=True, scale_factor=1e-12, scale_threshold=1e-7, @@ -158,7 +160,7 @@ def __init__(self): floatParameter( name="M2", units=u.M_sun, - description="Mass of companion in the unit Sun mass", + description="Companion mass", ) ) self.add_param( diff --git a/src/pint/models/spindown.py b/src/pint/models/spindown.py index 78bbba8cf..ff9972968 100644 --- a/src/pint/models/spindown.py +++ b/src/pint/models/spindown.py @@ -108,7 +108,7 @@ def F_terms(self): def F_description(self, n): """Template function for description""" - return "Spin-frequency %d derivative" % n # if n else "Spin-frequency" + return "Spin-frequency derivative %d" % n if n > 0 else "Spin-frequency" def F_unit(self, n): """Template function for unit""" diff --git a/src/pint/models/timing_model.py b/src/pint/models/timing_model.py index 6cc845412..b6abe6270 100644 --- a/src/pint/models/timing_model.py +++ b/src/pint/models/timing_model.py @@ -309,7 +309,7 @@ def __init__(self, name="", components=[]): self.add_param_from_top( strParameter( name="BINARY", - description="The Pulsar System/Binary model to use.", + description="Pulsar System/Binary model", value=None, ), "", @@ -3003,6 +3003,9 @@ def add_param(self, param, deriv_func=None, setup=False): param.name = prefix + str(idx) param.index = idx + if hasattr(self, f"{prefix}1"): + param.description = getattr(self, f"{prefix}1").description + # A more general check if param.name in self.params: exist_par = getattr(self, param.name) diff --git a/src/pint/output/publish.py b/src/pint/output/publish.py new file mode 100644 index 000000000..2cb82b0ac --- /dev/null +++ b/src/pint/output/publish.py @@ -0,0 +1,320 @@ +"""Generate LaTeX summary of a timing model and TOAs.""" +from pint.models import ( + TimingModel, + DispersionDMX, + FD, + Glitch, + PhaseJump, + SolarWindDispersionX, + AbsPhase, + Wave, +) +from pint.models.dispersion_model import DispersionJump +from pint.models.noise_model import NoiseComponent +from pint.models.parameter import ( + Parameter, + funcParameter, +) +from pint.toa import TOAs +from pint.residuals import Residuals, WidebandTOAResiduals +from io import StringIO +import numpy as np + + +def publish_param(param: Parameter): + """Return LaTeX line for a parameter""" + label, value = param.as_latex() + return f"{label}\\dotfill & {value} \\\\ \n" + + +def publish( + model: TimingModel, + toas: TOAs, + include_dmx=False, + include_noise=False, + include_jumps=False, + include_zeros=False, + include_fd=False, + include_glitches=False, + include_swx=False, + include_tzr=False, + include_prefix_summary=True, + include_set_params=True, + include_derived_params=True, + include_fit_summary=True, +): + """Generate LaTeX summary of a given timing model and TOAs. + + Parameters + ---------- + model: pint.model.timing_model.TimingModel + Input timing model + toas: TOAs + Input TOAs + include_dmx: bool + Whether to include DMX paremeters (default is False) + include_noise: bool + Whether to include noise paremeters (default is False) + include_jumps: bool + Whether to include jump paremeters (JUMPs, DMJUMPs) (default is False) + include_zeros: bool + Whether to include paremeters which are zero (default is False) + include_fd: bool + Whether to include FD paremeters (default is False) + include_glitches: bool + Whether to include glitch paremeters (default is False) + include_swx: bool + Whether to include SWX paremeters (default is False) + include_tzr: bool + Whether to include TZR paremeters (default is False) + include_prefix_summary: bool + Whether to include a summary of prefix and mask parameters (default is True) + include_set_params: bool + Whether to include set params (default is True) + include_derived_params: bool + Whether to include derived params (default is True) + include_fit_summary: bool + Whether to include fit summary params (default is True) + + Returns + ------- + latex_summary: str + The LaTeX summary + """ + mjds = toas.get_mjds() + mjd_start, mjd_end = int(min(mjds.value)), int(max(mjds.value)) + data_span_yr = (mjd_end - mjd_start) / 365.25 + + fit_method = ( + "GLS" + if np.any([nc.introduces_correlated_errors for nc in model.NoiseComponent_list]) + else "WLS" + ) + + if toas.is_wideband(): + res = WidebandTOAResiduals(toas, model) + toares = res.toa + dmres = res.dm + else: + res = Residuals(toas, model) + toares = res + + exclude_params = [ + "START", + "FINISH", + "NTOA", + "CHI2", + "DMDATA", + "PSR", + "EPHEM", + "CLOCK", + "UNITS", + "TIMEEPH", + "T2CMETHOD", + "DILATEFREQ", + "INFO", + "ECL", + "BINARY", + ] + + exclude_components = [Wave] + if not include_dmx: + exclude_components.append(DispersionDMX) + if not include_jumps: + exclude_components.extend([PhaseJump, DispersionJump]) + if not include_fd: + exclude_components.append(FD) + if not include_noise: + exclude_components.append(NoiseComponent) + if not include_glitches: + exclude_components.append(Glitch) + if not include_swx: + exclude_components.append(SolarWindDispersionX) + if not include_tzr: + exclude_components.append(AbsPhase) + + with StringIO("w") as tex: + tex.write("\\documentclass{article}\n") + tex.write("\\begin{document}\n") + + tex.write("\\begin{table}\n") + tex.write("\\caption{Parameters for PSR %s}\n" % model.PSR.value) + tex.write("\\begin{tabular}{ll}\n") + tex.write("\\hline\\hline\n") + tex.write("\\multicolumn{2}{c}{Dataset and model summary}\\\\ \n") + tex.write("\\hline\n") + tex.write( + f"Pulsar name \\dotfill & {model.PSR.value.replace('-','$-$')} \\\\ \n" + ) + tex.write( + f"MJD range \\dotfill & {mjd_start}---{mjd_end} \\\\ \n" + ) + tex.write( + f"Data span (yr) \\dotfill & {data_span_yr:.2f} \\\\ \n" + ) + tex.write(f"Number of TOAs \\dotfill & {len(toas)} \\\\ \n") + tex.write( + f"TOA paradigm \\dotfill & {'Wideband' if toas.is_wideband() else 'Narrowband'} \\\\ \n" + ) + tex.write( + f"Solar system ephemeris \\dotfill & {model.EPHEM.value} \\\\ \n" + ) + tex.write( + f"Timescale \\dotfill & {model.CLOCK.value} \\\\ \n" + ) + tex.write( + f"Time unit \\dotfill & {model.UNITS.value} \\\\ \n" + ) + tex.write( + f"Time ephemeris \\dotfill & {model.TIMEEPH.value} \\\\ \n" + ) + + if model.BINARY.value is not None: + tex.write( + f"Binary model \\dotfill & {model.BINARY.value} \\\\ \n" + ) + + if include_prefix_summary: + if "PhaseJump" in model.components: + tex.write( + f"Number of JUMPs \\dotfill & {model.get_number_of_jumps()} \\\\ \n" + ) + + if "DispersionJump" in model.components: + tex.write( + f"Number of DMJUMPs \\dotfill & {len(model.dm_jumps)} \\\\ \n" + ) + + if "DispersionDMX" in model.components: + tex.write( + f"Number of DMX ranges \\dotfill & {len(model.components['DispersionDMX'].get_indices())} \\\\ \n" + ) + + if "SolarWindDispersionX" in model.components: + tex.write( + f"Number of SWX ranges \\dotfill & {len(model.components['SolarWindDispersionX'].get_indices())} \\\\ \n" + ) + + if "Glitch" in model.components: + tex.write( + f"Number of Glitches \\dotfill & {len(model.components['Glitch'].glitch_indices)} \\\\ \n" + ) + + if "FD" in model.components: + tex.write( + f"Number of FD parameters \\dotfill & {model.num_FD_terms} \\\\ \n" + ) + + if "ScaleToaError" in model.components: + tex.write( + f"Number of EFACs \\dotfill & {len(model.EFACs)} \\\\ \n" + ) + tex.write( + f"Number of EQUADs \\dotfill & {len(model.EQUADs)} \\\\ \n" + ) + + if "EcorrNoise" in model.components: + tex.write( + f"Number of ECORRs \\dotfill & {len(model.ECORRs)} \\\\ \n" + ) + + if "ScaleDmError" in model.components: + tex.write( + f"Number of DMEFACs \\dotfill & {len(model.DMEFACs)} \\\\ \n" + ) + tex.write( + f"Number of DMEQUADs \\dotfill & {len(model.DMEQUADs)} \\\\ \n" + ) + if "Wave" in model.components: + tex.write( + f"Number of WAVE components \\dotfill & {model.num_wave_terms} \\\\ \n" + ) + + tex.write("\\hline\n") + + if include_fit_summary: + tex.write("\\multicolumn{2}{c}{Fit summary}\\\\ \n") + tex.write("\\hline\n") + tex.write( + f"Number of free parameters \\dotfill & {len(model.free_params)} \\\\ \n" + ) + tex.write( + f"Fitting method \\dotfill & {fit_method} \\\\ \n" + ) + tex.write( + f"RMS TOA residuals ($\\mu s$) \\dotfill & {toares.calc_time_resids().to('us').value.std():.2f} \\\\ \n" + ) + if toas.is_wideband(): + tex.write( + f"RMS DM residuals (pc / cm3) \\dotfill & {dmres.calc_resids().to('pc/cm^3').value.std():.2f} \\\\ \n" + ) + tex.write( + f"$\\chi^2$ \\dotfill & {res.chi2:.2f} \\\\ \n" + ) + if toas.is_wideband(): + tex.write(f"Degrees of freedom \\dotfill & {res.dof} \\\\ \n") + else: + tex.write( + f"Reduced $\\chi^2$ \\dotfill & {res.reduced_chi2:.2f} \\\\ \n" + ) + tex.write("\\hline\n") + + tex.write("\multicolumn{2}{c}{Measured Quantities} \\\\ \n") + tex.write("\\hline\n") + for fp in model.free_params: + param = getattr(model, fp) + if ( + all([not isinstance(param._parent, exc) for exc in exclude_components]) + and fp not in exclude_params + and (param.value != 0 or include_zeros) + ): + tex.write(publish_param(param)) + + tex.write("\\hline\n") + + if include_set_params: + tex.write("\multicolumn{2}{c}{Set Quantities} \\\\ \n") + tex.write("\\hline\n") + for p in model.params: + param = getattr(model, p) + + if ( + all( + [ + not isinstance(param._parent, exc) + for exc in exclude_components + ] + ) + and param.value is not None + and param.frozen + and p not in exclude_params + and (param.value != 0 or include_zeros) + and not isinstance(param, funcParameter) + ): + tex.write(publish_param(param)) + + tex.write("\\hline\n") + + if include_derived_params: + derived_params = [ + getattr(model, p) + for p in model.params + if isinstance(getattr(model, p), funcParameter) + and getattr(model, p).quantity is not None + ] + if len(derived_params) > 0: + tex.write("\multicolumn{2}{c}{Derived Quantities} \\\\ \n") + tex.write("\\hline\n") + for param in derived_params: + tex.write(publish_param(param)) + tex.write("\\hline\n") + + tex.write("\\end{tabular}\n") + tex.write("\\end{table}\n") + tex.write("\\end{document}\n") + + output = tex.getvalue() + + output = output.replace("_", "\\_") + + return output diff --git a/src/pint/scripts/pintempo.py b/src/pint/scripts/pintempo.py index 7d21c6323..d99c772ba 100755 --- a/src/pint/scripts/pintempo.py +++ b/src/pint/scripts/pintempo.py @@ -27,7 +27,7 @@ def main(argv=None): parser = argparse.ArgumentParser( - description="Command line interfact to PINT", + description="Command line interface to PINT", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument("parfile", help="par file to read model from") diff --git a/src/pint/scripts/pintpublish.py b/src/pint/scripts/pintpublish.py new file mode 100644 index 000000000..69b551dc0 --- /dev/null +++ b/src/pint/scripts/pintpublish.py @@ -0,0 +1,83 @@ +"""Generate LaTeX summary of a timing model and TOAs.""" +from pint.models import get_model_and_toas +from pint.output.publish import publish +from pint.logging import setup as setup_log +import argparse + + +def main(argv=None): + setup_log(level="WARNING") + + parser = argparse.ArgumentParser( + description="Publication output for PINT", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + parser.add_argument("parfile", help="par file to read model from") + parser.add_argument("timfile", help="TOA file name") + parser.add_argument("--outfile", help="Output file", default=None) + parser.add_argument( + "--include_dmx", + help="Include DMX parameters", + action="store_true", + default=False, + ) + parser.add_argument( + "--include_noise", + help="Include noise parameters", + action="store_true", + default=False, + ) + parser.add_argument( + "--include_jumps", help="Include jumps", action="store_true", default=False + ) + parser.add_argument( + "--include_zeros", + help="Include parameters equal to 0", + action="store_true", + default=False, + ) + parser.add_argument( + "--include_fd", help="Include FD parameters", action="store_true", default=False + ) + parser.add_argument( + "--include_glitches", + help="Include glitches", + action="store_true", + default=False, + ) + parser.add_argument( + "--include_swx", + help="Include SWX parameters", + action="store_true", + default=False, + ) + parser.add_argument( + "--include_tzr", + help="Include TZR parameters", + action="store_true", + default=False, + ) + + args = parser.parse_args(argv) + + model, toas = get_model_and_toas(args.parfile, args.timfile) + + output = publish( + model, + toas, + include_dmx=args.include_dmx, + include_noise=args.include_noise, + include_jumps=args.include_jumps, + include_zeros=args.include_zeros, + include_fd=args.include_fd, + include_glitches=args.include_glitches, + include_swx=args.include_swx, + include_tzr=args.include_tzr, + ) + + if args.outfile is None: + print(output) + else: + with open(args.outfile, "w") as f: + f.write(output) diff --git a/tests/test_publish.py b/tests/test_publish.py new file mode 100644 index 000000000..f8663ff3d --- /dev/null +++ b/tests/test_publish.py @@ -0,0 +1,72 @@ +from pinttestdata import datadir +import pytest +from pint.models import get_model_and_toas +from pint.output.publish import publish +from pint.scripts import pintpublish +import os + +data_NGC6440E = get_model_and_toas(datadir / "NGC6440E.par", datadir / "NGC6440E.tim") + + +def test_NGC6440E(): + m, t = data_NGC6440E + output = publish(m, t) + assert "1748-2021E" in output + assert "DE421" in output + + +data_J0613m0200_NANOGrav_9yv1 = get_model_and_toas( + datadir / "J0613-0200_NANOGrav_9yv1.gls.par", + datadir / "J0613-0200_NANOGrav_9yv1.tim", +) + + +@pytest.mark.parametrize("full", [True, False]) +def test_J0613m0200_NANOGrav_9yv1(full): + m, t = data_J0613m0200_NANOGrav_9yv1 + output = publish( + m, t, include_dmx=full, include_fd=full, include_noise=full, include_jumps=full + ) + + assert "J0613-0200" in output + assert "ELL1" in output + assert "Narrowband" in output + assert "freedom" not in output + assert "Reduced" in output + assert not full or "DMX" in output + assert not full or "JUMP" in output + assert not full or "FD1" in output + assert not full or "RNAMP" in output + + +data_J1614m2230_NANOGrav_12yv3_wb = get_model_and_toas( + datadir / "J1614-2230_NANOGrav_12yv3.wb.gls.par", + datadir / "J1614-2230_NANOGrav_12yv3.wb.tim", +) + + +@pytest.mark.parametrize("full", [True, False]) +def test_J1614m2230_NANOGrav_12yv3_wb(full): + m, t = data_J1614m2230_NANOGrav_12yv3_wb + output = publish( + m, t, include_dmx=full, include_fd=full, include_noise=full, include_jumps=full + ) + + assert "DE436" in output + assert "ELL1" in output + assert "Wideband" in output + assert "freedom" in output + assert not full or "DMX" in output + assert not full or "DMJUMP" in output + assert not full or "JUMP" in output + assert "TT(BIPM2017)" in output + + +@pytest.mark.parametrize("file", [True, False]) +def test_script(file): + par, tim = str(datadir / "NGC6440E.par"), str(datadir / "NGC6440E.tim") + outfile = "--outfile=pintpublish_test.tex" if file else "" + args = f"{par} {tim} {outfile}" + pintpublish.main(args.split()) + + assert not file or os.path.isfile("pintpublish_test.tex")