diff --git a/news/tolerance.rst b/news/tolerance.rst new file mode 100644 index 00000000..7d35607b --- /dev/null +++ b/news/tolerance.rst @@ -0,0 +1,23 @@ +**Added:** + +* Option to set tolerance for the morph refinement (default 1e-08). + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/morph/morphapp.py b/src/diffpy/morph/morphapp.py index ad7ca73c..3847bf9f 100755 --- a/src/diffpy/morph/morphapp.py +++ b/src/diffpy/morph/morphapp.py @@ -104,6 +104,13 @@ def custom_error(self, msg): type="float", help="Maximum r-value to use for PDF comparisons.", ) + parser.add_option( + "--tolerance", + "-t", + type="float", + metavar="TOL", + help="Specify refiner tolerance as TOL. Default: 10e-8.", + ) parser.add_option( "--pearson", action="store_true", @@ -420,6 +427,11 @@ def single_morph(parser, opts, pargs, stdout_flag=True): if y_target is None: parser.error(f"No data table found in file: {pargs[1]}.") + # Get tolerance + tolerance = 1e-08 + if opts.tolerance is not None: + tolerance = opts.tolerance + # Get configuration values scale_in = "None" stretch_in = "None" @@ -519,7 +531,9 @@ def single_morph(parser, opts, pargs, stdout_flag=True): refpars = list(set(refpars) - set(opts.exclude)) # Refine or execute the morph - refiner = refine.Refiner(chain, x_morph, y_morph, x_target, y_target) + refiner = refine.Refiner( + chain, x_morph, y_morph, x_target, y_target, tolerance=tolerance + ) if opts.pearson: refiner.residual = refiner._pearson if opts.addpearson: diff --git a/src/diffpy/morph/refine.py b/src/diffpy/morph/refine.py index 9514f8d8..c0c5eb60 100644 --- a/src/diffpy/morph/refine.py +++ b/src/diffpy/morph/refine.py @@ -42,12 +42,15 @@ class Refiner(object): to other functions. """ - def __init__(self, chain, x_morph, y_morph, x_target, y_target): + def __init__( + self, chain, x_morph, y_morph, x_target, y_target, tolerance=1e-08 + ): self.chain = chain self.x_morph = x_morph self.y_morph = y_morph self.x_target = x_target self.y_target = y_target + self.tolerance = tolerance self.pars = [] self.residual = self._residual self.flat_to_grouped = {} @@ -143,7 +146,11 @@ def refine(self, *args, **kw): self.flat_to_grouped[len(initial) - 1] = (p, None) sol, cov_sol, infodict, emesg, ier = leastsq( - self.residual, initial, full_output=1 + self.residual, + initial, + full_output=1, + ftol=self.tolerance, + xtol=self.tolerance, ) fvec = infodict["fvec"] diff --git a/tests/test_refine.py b/tests/test_refine.py index 33d8d4ac..e7105a3e 100644 --- a/tests/test_refine.py +++ b/tests/test_refine.py @@ -46,7 +46,7 @@ def test_refine_morph(self, setup): assert (x_morph == x_target).all() assert numpy.allclose(y_morph, y_target) - pytest.approx(config["scale"], 3.0) + assert pytest.approx(config["scale"]) == 3.0 return def test_refine_chain(self, setup): @@ -74,8 +74,44 @@ def test_refine_chain(self, setup): err = 15.0 * 2 res = sum(numpy.fabs(y_target - y_morph)) assert res < err - pytest.approx(chain.scale, 3, 2) - pytest.approx(chain.stretch, 0.1, 2) + assert pytest.approx(chain.scale, 0.01, 0.01) == 3.0 + assert pytest.approx(chain.stretch, 0.01, 0.01) == 0.1 + return + + def test_refine_tolerance(self, setup): + # Check that small tolerance gives good result + stol = 1e-16 + config = { + "scale": 1.0, + } + mscale = MorphScale(config) + refiner = Refiner( + mscale, + self.x_morph, + self.y_morph, + self.x_target, + self.y_target, + tolerance=stol, + ) + refiner.refine() + assert pytest.approx(config["scale"], stol, stol) == 3.0 + + # Check that larger tolerance does not give as good of result + ltol = 100 + config = { + "scale": 1.0, + } + mscale = MorphScale(config) + refiner = Refiner( + mscale, + self.x_morph, + self.y_morph, + self.x_target, + self.y_target, + tolerance=ltol, + ) + refiner.refine() + assert not pytest.approx(config["scale"], stol, stol) == 3.0 return