From 0e02565c95fbb317e8cc84126cfb70aa84b5557e Mon Sep 17 00:00:00 2001 From: Matthew Kerr Date: Thu, 15 Dec 2022 14:09:54 -0500 Subject: [PATCH 1/2] Fix numerical slop reading in ASCII profiles, add tests. --- src/pint/templates/lcfitters.py | 3 +++ src/pint/templates/lctemplate.py | 46 +++++++++++++++++++++++++------- tests/test_templates.py | 38 ++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/pint/templates/lcfitters.py b/src/pint/templates/lcfitters.py index 073a5be96..bd5cd4d53 100644 --- a/src/pint/templates/lcfitters.py +++ b/src/pint/templates/lcfitters.py @@ -405,6 +405,9 @@ def fit_position(self, unbinned=True, track=False, skip_coarse=False): """ + if not self.template.check_bounds(): + raise ValueError("Template does not satisfy parameter bounds.") + self._set_unbinned(unbinned) ph0 = self.template.get_location() diff --git a/src/pint/templates/lctemplate.py b/src/pint/templates/lctemplate.py index cef2e5fb9..db2a92de5 100644 --- a/src/pint/templates/lctemplate.py +++ b/src/pint/templates/lctemplate.py @@ -1029,7 +1029,7 @@ def gradient(self, parameters): return rvals -def prim_io(template): +def prim_io(template, bound_eps=1e-5): """Read files and build LCPrimitives.""" def read_gaussian(toks): @@ -1047,15 +1047,41 @@ def read_gaussian(toks): g.errors[0] = float(tok[4]) / 2.3548200450309493 elif tok[0].startswith("ampl"): norms.append(float(tok[2])) - return primitives, norms - - toks = [line.strip().split() for line in open(template) if len(line.strip()) > 0] - if "gauss" in toks[0]: - return read_gaussian(toks[1:]) - elif "kernel" in toks[0]: - return [LCKernelDensity(input_file=toks[1:])], None - elif "fourier" in toks[0]: - return [LCEmpiricalFourier(input_file=toks[1:])], None + # check to that bounds are OK + for iprim, prim in enumerate(primitives): + if prim.check_bounds(): + continue + for ip, p in enumerate(prim.p): + lo, hi = prim.bounds[ip] + if (p < lo) and (abs(p - lo) < bound_eps): + prim.p[ip] = lo + if (p > hi) and (abs(p - hi) < bound_eps): + prim.p[ip] = hi + if not prim.check_bounds(): + raise ValueError("Unrecoverable bounds errors on input.") + # check norms + norms = np.asarray(norms) + n = norms.sum() + if (n > 1) and (abs(n - 1) < bounds_eps): + norms *= 1.0 / n + return primitives, list(norms) + + lines = None + try: + with open(template, "r") as f: + lines = f.readlines() + except FileNotFoundError: + lines = template.split("\n") + if lines is None: + raise ValueError("Could not load lines from template.") + toks = [line.strip().split() for line in lines if len(line.strip()) > 0] + label, toks = toks[0], toks[1:] + if "gauss" in label: + return read_gaussian(toks) + elif "kernel" in label: + return [LCKernelDensity(input_file=toks)], None + elif "fourier" in label: + return [LCEmpiricalFourier(input_file=toks)], None raise ValueError("Template format not recognized!") diff --git a/tests/test_templates.py b/tests/test_templates.py index 35713aec1..596097e6e 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -43,6 +43,32 @@ def test_prim_gauss_definition(): assert abs(g1(0.48) - expected_val) < 1e-6 +def test_prim_io(): + """Test functionality to save a template and have it obey founds.""" + # This will fail unless we use loose bounds checking + template1 = """ + # gauss + ------------------------- + const = 0.95915 +/- 0.00000 + phas1 = 0.00000 +/- 0.00000 + fwhm1 = 0.02514 +/- 0.00000 + ampl1 = 0.03810 +/- 0.00000 + phas2 = 0.53059 +/- 0.00000 + fwhm2 = 0.01177 +/- 0.00000 + ampl2 = 0.00275 +/- 0.00000 + ------------------------- + """ + with pytest.raises(ValueError): + prims, norms = lctemplate.prim_io(template1, bound_eps=0) + + # this should work and set the lower bound to 0.005 + prims, norms = lctemplate.prim_io(template1) + assert prims[1].p[0] == prims[1].bounds[0][0] + + template2 = template1.replace("1177", "1178") + prims, norms = lctemplate.prim_io(template2) + + def test_prim_gauss_wrapping(): # default is to wrap a function by 10 times; make a fat pulse here and @@ -465,6 +491,18 @@ def test_simple_fit_unbinned(): """ assert expected_val.strip() == str(lcf).strip() + # check position fitting + offset, error = lcf.fit_position() + assert abs(offset < 1e-7) + assert abs(error - 0.00055513) < 1e-7 + + # check that fitting with a bad parameter boundary fails + p = lcf.template.primitives[0] + p.p[0] = p.bounds[0][0] - 1e-4 + assert not (p.check_bounds()) + with pytest.raises(ValueError): + lcf.fit_position() + def test_simple_fit_binned(): """Make sure objects adequately implement mathematical intent.""" From 4351d8f91539667243baae0a8dafcf6f54695a23 Mon Sep 17 00:00:00 2001 From: Matthew Kerr Date: Thu, 15 Dec 2022 17:44:22 -0500 Subject: [PATCH 2/2] Add to CHANGELOG. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d77700bfc..da95c9607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project, at least loosely, adheres to [Semantic Versioning](https://sem ### Added - method to identify mask parameters with no TOAs and optionally freeze them ### Fixed +- corrected a precision issue with reading ASCII representations of pulse profiles ### Removed - termios import for solar_wind_dispersion