diff --git a/CHANGELOG-unreleased.md b/CHANGELOG-unreleased.md index 75494bb11..d2a93fa95 100644 --- a/CHANGELOG-unreleased.md +++ b/CHANGELOG-unreleased.md @@ -9,8 +9,10 @@ the released changes. ## Unreleased ### Changed +- Command line scripts now automatically do `allow_tcb` and `allow_T2` while reading par files. - Updated the `plot_chains` function in `event_optimize` so that the subplots are a fixed size to prevent the subplots from being condensed in the case of many fit parameters. ### Added +- Added an option `nbin` to `photonphase` to decide how many phase bins to use for the phaseogram - Added an option `linearize_model` to speed up the photon phases calculation within `event_optimize` through the designmatrix. - Added AIC and BIC calculation to be written in the post fit parfile from `event_optimize` - When TCB->TDB conversion info is missing, will print parameter name diff --git a/src/pint/__init__.py b/src/pint/__init__.py index 5706dff7e..7906be01f 100644 --- a/src/pint/__init__.py +++ b/src/pint/__init__.py @@ -105,6 +105,9 @@ "hourangle_second": hourangle_second, } +# define a units equivalency for gauss in cgs +gauss_equiv = [u.Gauss, u.Hz * (u.g / u.cm) ** (1 / 2), lambda x: x, lambda x: x] + import astropy.version if astropy.version.major < 4: diff --git a/src/pint/derived_quantities.py b/src/pint/derived_quantities.py index d5852ae4b..24dcb1934 100644 --- a/src/pint/derived_quantities.py +++ b/src/pint/derived_quantities.py @@ -136,6 +136,14 @@ def pferrs( return (forp, forperr, fdorpd, fdorpderr) +def _to_gauss(B: u.Quantity) -> u.G: + """Convert quantity with mass, length, and time units to Gauss. + + In cgs units, magnetic field is has units (mass/length)^(1/2) / time. + """ + return B.to(u.Gauss, equivalencies=[pint.gauss_equiv]) + + @u.quantity_input(f=u.Hz, fdot=u.Hz / u.s, fo=u.Hz) def pulsar_age( f: u.Quantity, fdot: u.Quantity, n: int = 3, fo: u.Quantity = 1e99 * u.Hz @@ -219,12 +227,16 @@ def pulsar_edot( return (-4.0 * np.pi**2 * I * f * fdot).to(u.erg / u.s) -@u.quantity_input(f=u.Hz, fdot=u.Hz / u.s) -def pulsar_B(f: u.Quantity, fdot: u.Quantity) -> u.G: +@u.quantity_input(f=u.Hz, fdot=u.Hz / u.s, I=u.g * u.cm**2, R=u.km) +def pulsar_B( + f: u.Quantity, + fdot: u.Quantity, + I: u.Quantity = 1.0e45 * u.g * u.cm**2, + R: u.Quantity = 10 * u.km, +) -> u.G: r"""Compute pulsar surface magnetic field - Return the estimated pulsar surface magnetic field strength - given the spin frequency and frequency derivative. + Return the pulsar surface magnetic field strength given the spin frequency `f` and frequency derivative `fdot`. Parameters ---------- @@ -232,6 +244,10 @@ def pulsar_B(f: u.Quantity, fdot: u.Quantity) -> u.G: pulsar frequency fdot : astropy.units.Quantity frequency derivative :math:`\dot f` + I : astropy.units.Quantity, optional + pulsar moment of inertia, default of 1e45 g*cm**2 + R : astropy.units.Quantity, optional + pulsar radius, default of 10 km Returns ------- @@ -247,15 +263,19 @@ def pulsar_B(f: u.Quantity, fdot: u.Quantity) -> u.G: Notes ----- - Calculates :math:`B=3.2\times 10^{19}\,{\rm G}\sqrt{ f \dot f^{-3}}` + Calculates :math:`B=\sqrt{\frac{3\,I\,c^3}{8\pi^2\,R^6}\times\frac{-\dot{f}}{f^3}}` """ - # This is a hack to use the traditional formula by stripping the units. - # It would be nice to improve this to a proper formula with units - return 3.2e19 * u.G * np.sqrt(-fdot.to_value(u.Hz / u.s) / f.to_value(u.Hz) ** 3.0) + factor = (3.0 * I * const.c**3) / (8.0 * np.pi**2 * R**6) + return _to_gauss((factor * (-fdot) / f**3) ** 0.5) -@u.quantity_input(f=u.Hz, fdot=u.Hz / u.s) -def pulsar_B_lightcyl(f: u.Quantity, fdot: u.Quantity) -> u.G: +@u.quantity_input(f=u.Hz, fdot=u.Hz / u.s, I=u.g * u.cm**2, R=u.km) +def pulsar_B_lightcyl( + f: u.Quantity, + fdot: u.Quantity, + I: u.Quantity = 1.0e45 * u.g * u.cm**2, + R=10 * u.km, +) -> u.G: r"""Compute pulsar magnetic field at the light cylinder Return the estimated pulsar magnetic field strength at the @@ -268,6 +288,10 @@ def pulsar_B_lightcyl(f: u.Quantity, fdot: u.Quantity) -> u.G: pulsar frequency fdot : astropy.units.Quantity frequency derivative :math:`\dot f` + I : astropy.units.Quantity, optional + pulsar moment of inertia, default of 1e45 g*cm**2 + R : astropy.units.Quantity, optional + pulsar radius, default of 10 km Returns ------- @@ -283,17 +307,10 @@ def pulsar_B_lightcyl(f: u.Quantity, fdot: u.Quantity) -> u.G: Notes ----- - Calculates :math:`B_{LC} = 2.9\times 10^8\,{\rm G} P^{-5/2} \dot P^{1/2}` + Calculates :math:`B_{LC} = \sqrt{\frac{-24\pi^4\,I}{c^3}\dot{f}f^3}` """ - p, pd = p_to_f(f, fdot) - # This is a hack to use the traditional formula by stripping the units. - # It would be nice to improve this to a proper formula with units - return ( - 2.9e8 - * u.G - * p.to_value(u.s) ** (-5.0 / 2.0) - * np.sqrt(pd.to(u.dimensionless_unscaled).value) - ) + factor = 24.0 * np.pi**4.0 * I / const.c**3.0 + return _to_gauss((factor * (-fdot) * f**3.0) ** 0.5) @u.quantity_input(pb=u.d, x=u.cm) diff --git a/src/pint/pintk/pulsar.py b/src/pint/pintk/pulsar.py index 65d8048df..ce6aa8560 100644 --- a/src/pint/pintk/pulsar.py +++ b/src/pint/pintk/pulsar.py @@ -151,7 +151,9 @@ def __contains__(self, key): return key in self.prefit_model.params def reset_model(self): - self.prefit_model = pint.models.get_model(self.parfile) + self.prefit_model = pint.models.get_model( + self.parfile, allow_T2=True, allow_tcb=True + ) self.add_model_params() self.postfit_model = None self.postfit_resids = None @@ -172,7 +174,9 @@ def reset_TOAs(self): self.update_resids() def resetAll(self): - self.prefit_model = pint.models.get_model(self.parfile) + self.prefit_model = pint.models.get_model( + self.parfile, allow_T2=True, allow_tcb=True + ) self.postfit_model = None self.postfit_resids = None self.fitted = False diff --git a/src/pint/scripts/compare_parfiles.py b/src/pint/scripts/compare_parfiles.py index 0c4ca5da2..d65785041 100644 --- a/src/pint/scripts/compare_parfiles.py +++ b/src/pint/scripts/compare_parfiles.py @@ -83,14 +83,25 @@ def main(argv=None): parser.add_argument( "-q", "--quiet", default=0, action="count", help="Decrease output verbosity" ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) args = parser.parse_args(argv) pint.logging.setup( level=pint.logging.get_level(args.loglevel, args.verbosity, args.quiet) ) - m1 = get_model(args.input1) - m2 = get_model(args.input2) + m1 = get_model(args.input1, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb) + m2 = get_model(args.input2, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb) + print( m1.compare( m2, diff --git a/src/pint/scripts/convert_parfile.py b/src/pint/scripts/convert_parfile.py index 55b45415b..2975d5acc 100644 --- a/src/pint/scripts/convert_parfile.py +++ b/src/pint/scripts/convert_parfile.py @@ -73,6 +73,16 @@ def main(argv=None): parser.add_argument( "-q", "--quiet", default=0, action="count", help="Decrease output verbosity" ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) args = parser.parse_args(argv) pint.logging.setup( @@ -83,7 +93,9 @@ def main(argv=None): return log.info(f"Reading '{args.input}'") - model = get_model(args.input) + + model = get_model(args.input, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb) + if hasattr(model, "BINARY") and args.binary is not None: log.info(f"Converting from {model.BINARY.value} to {args.binary}") if args.binary == "ELL1H": diff --git a/src/pint/scripts/event_optimize.py b/src/pint/scripts/event_optimize.py index 071860a8d..6e26ba784 100755 --- a/src/pint/scripts/event_optimize.py +++ b/src/pint/scripts/event_optimize.py @@ -706,6 +706,16 @@ def main(argv=None): action="store_true", dest="linearize_model", ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) args = parser.parse_args(argv) pint.logging.setup( @@ -739,7 +749,9 @@ def main(argv=None): ncores = args.ncores # Read in initial model - modelin = pint.models.get_model(parfile) + modelin = pint.models.get_model( + parfile, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb + ) # File name setup and clobber file check filepath = args.filepath or os.getcwd() diff --git a/src/pint/scripts/event_optimize_MCMCFitter.py b/src/pint/scripts/event_optimize_MCMCFitter.py index bbab8ccee..7357b1028 100755 --- a/src/pint/scripts/event_optimize_MCMCFitter.py +++ b/src/pint/scripts/event_optimize_MCMCFitter.py @@ -128,6 +128,17 @@ def main(argv=None): help="Logging level", dest="loglevel", ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) + global nwalkers, nsteps, ftr args = parser.parse_args(argv) @@ -164,7 +175,9 @@ def main(argv=None): wgtexp = args.wgtexp # Read in initial model - modelin = pint.models.get_model(parfile) + modelin = pint.models.get_model( + parfile, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb + ) # The custom_timing version below is to manually construct the TimingModel # class, which allows it to be pickled. This is needed for parallelizing diff --git a/src/pint/scripts/event_optimize_multiple.py b/src/pint/scripts/event_optimize_multiple.py index 41316a000..0a3dc7336 100755 --- a/src/pint/scripts/event_optimize_multiple.py +++ b/src/pint/scripts/event_optimize_multiple.py @@ -228,6 +228,16 @@ def main(argv=None): help="Logging level", dest="loglevel", ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) global nwalkers, nsteps, ftr @@ -261,7 +271,9 @@ def main(argv=None): wgtexp = args.wgtexp # Read in initial model - modelin = pint.models.get_model(parfile) + modelin = pint.models.get_model( + parfile, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb + ) # Set the target coords for automatic weighting if necessary if "ELONG" in modelin.params: diff --git a/src/pint/scripts/fermiphase.py b/src/pint/scripts/fermiphase.py index b1427e61f..c5e43bb93 100755 --- a/src/pint/scripts/fermiphase.py +++ b/src/pint/scripts/fermiphase.py @@ -77,6 +77,16 @@ def main(argv=None): parser.add_argument( "-q", "--quiet", default=0, action="count", help="Decrease output verbosity" ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) args = parser.parse_args(argv) pint.logging.setup( @@ -88,7 +98,10 @@ def main(argv=None): args.addphase = True # Read in model - modelin = pint.models.get_model(args.parfile) + modelin = pint.models.get_model( + args.parfile, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb + ) + if "ELONG" in modelin.params: tc = SkyCoord( modelin.ELONG.quantity, diff --git a/src/pint/scripts/photonphase.py b/src/pint/scripts/photonphase.py index 7d72eec18..2798e0a7f 100755 --- a/src/pint/scripts/photonphase.py +++ b/src/pint/scripts/photonphase.py @@ -106,12 +106,25 @@ def main(argv=None): help="Logging level", dest="loglevel", ) + parser.add_argument( + "--nbin", help="Number of phase bins in the phaseogram", default=100, type=int + ) parser.add_argument( "-v", "--verbosity", default=0, action="count", help="Increase output verbosity" ) parser.add_argument( "-q", "--quiet", default=0, action="count", help="Decrease output verbosity" ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) args = parser.parse_args(argv) pint.logging.setup( @@ -153,7 +166,10 @@ def main(argv=None): "Please barycenter the event file using the official mission tools before processing with PINT" ) # Read in model - modelin = pint.models.get_model(args.parfile) + modelin = pint.models.get_model( + args.parfile, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb + ) + use_planets = False if "PLANET_SHAPIRO" in modelin.params: if modelin.PLANET_SHAPIRO.value: @@ -254,7 +270,7 @@ def main(argv=None): print("Htest : {0:.2f} ({1:.2f} sigma)".format(h, h2sig(h))) if args.plot: - phaseogram_binned(mjds, phases, bins=100, plotfile=args.plotfile) + phaseogram_binned(mjds, phases, bins=args.nbin, plotfile=args.plotfile) # Compute orbital phases for each photon TOA if args.addorbphase: diff --git a/src/pint/scripts/pintbary.py b/src/pint/scripts/pintbary.py index 4876474d8..61cdcd0a0 100755 --- a/src/pint/scripts/pintbary.py +++ b/src/pint/scripts/pintbary.py @@ -74,6 +74,16 @@ def main(argv=None): parser.add_argument( "-q", "--quiet", default=0, action="count", help="Decrease output verbosity" ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) args = parser.parse_args(argv) pint.logging.setup( @@ -105,7 +115,9 @@ def main(argv=None): ) if args.parfile is not None: - m = pint.models.get_model(args.parfile) + m = pint.models.get_model( + args.parfile, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb + ) else: # Construct model by hand m = pint.models.StandardTimingModel diff --git a/src/pint/scripts/pintempo.py b/src/pint/scripts/pintempo.py index d99c772ba..ac9099008 100755 --- a/src/pint/scripts/pintempo.py +++ b/src/pint/scripts/pintempo.py @@ -62,6 +62,16 @@ def main(argv=None): parser.add_argument( "-q", "--quiet", default=0, action="count", help="Decrease output verbosity" ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) args = parser.parse_args(argv) pint.logging.setup( @@ -69,7 +79,9 @@ def main(argv=None): ) log.info("Reading model from {0}".format(args.parfile)) - m = pint.models.get_model(args.parfile) + m = pint.models.get_model( + args.parfile, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb + ) log.warning(m.params) diff --git a/src/pint/scripts/pintpublish.py b/src/pint/scripts/pintpublish.py index 6d5b0142f..1828a05c6 100644 --- a/src/pint/scripts/pintpublish.py +++ b/src/pint/scripts/pintpublish.py @@ -59,10 +59,22 @@ def main(argv=None): action="store_true", default=False, ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) args = parser.parse_args(argv) - model, toas = get_model_and_toas(args.parfile, args.timfile) + model, toas = get_model_and_toas( + args.parfile, args.timfile, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb + ) output = publish( model, diff --git a/src/pint/scripts/t2binary2pint.py b/src/pint/scripts/t2binary2pint.py index 42d70f89a..bab43eef4 100644 --- a/src/pint/scripts/t2binary2pint.py +++ b/src/pint/scripts/t2binary2pint.py @@ -45,12 +45,17 @@ def main(argv=None): default=True, help="Whether to drop SINI if the model is DDK (True)", ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) args = parser.parse_args(argv) mb = ModelBuilder() - model = mb(args.input_par, allow_T2=True, allow_tcb=True) + model = mb(args.input_par, allow_T2=True, allow_tcb=args.allow_tcb) model.write_parfile(args.output_par) print(f"Output written to {args.output_par}") diff --git a/src/pint/scripts/tcb2tdb.py b/src/pint/scripts/tcb2tdb.py index 4c427d21a..02a485d3c 100644 --- a/src/pint/scripts/tcb2tdb.py +++ b/src/pint/scripts/tcb2tdb.py @@ -30,11 +30,16 @@ def main(argv=None): ) parser.add_argument("input_par", help="Input par file name (TCB)") parser.add_argument("output_par", help="Output par file name (TDB)") + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) args = parser.parse_args(argv) mb = ModelBuilder() - model = mb(args.input_par, allow_tcb=True) + model = mb(args.input_par, allow_tcb=True, allow_T2=args.allow_T2) model.write_parfile(args.output_par) log.info(f"Output written to {args.output_par}.") diff --git a/src/pint/scripts/zima.py b/src/pint/scripts/zima.py index 3305129f7..29b3e354c 100755 --- a/src/pint/scripts/zima.py +++ b/src/pint/scripts/zima.py @@ -115,6 +115,16 @@ def main(argv=None): parser.add_argument( "-q", "--quiet", default=0, action="count", help="Decrease output verbosity" ) + parser.add_argument( + "--allow_tcb", + action="store_true", + help="Convert TCB par files to TDB automatically", + ) + parser.add_argument( + "--allow_T2", + action="store_true", + help="Guess the underlying binary model when T2 is given", + ) args = parser.parse_args(argv) pint.logging.setup( @@ -122,7 +132,9 @@ def main(argv=None): ) log.info("Reading model from {0}".format(args.parfile)) - m = pint.models.get_model(args.parfile) + m = pint.models.get_model( + args.parfile, allow_T2=args.allow_T2, allow_tcb=args.allow_tcb + ) out_format = args.format error = args.error * u.microsecond diff --git a/tests/test_derived_quantities.py b/tests/test_derived_quantities.py index cf5e845b0..833e98a86 100644 --- a/tests/test_derived_quantities.py +++ b/tests/test_derived_quantities.py @@ -67,7 +67,7 @@ def test_Edot(): def test_Bfield(): # B assert np.isclose( - pulsar_B(0.033 * u.Hz, -2.0e-15 * u.Hz / u.s), 238722891596281.66 * u.G + pulsar_B(0.033 * u.Hz, -2.0e-15 * u.Hz / u.s), 238693670891966.22 * u.G ) @@ -75,7 +75,7 @@ def test_Blc(): # B_lc assert np.isclose( pulsar_B_lightcyl(0.033 * u.Hz, -2.0e-15 * u.Hz / u.s), - 0.07774704753236616 * u.G, + 0.07896965114785195 * u.G, )