From 40ecd002a1afd83d33caafda657036ed70a3f192 Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Mon, 28 Apr 2025 21:08:57 -0600 Subject: [PATCH 1/7] test/func: modifying refinement to allow lists and dicts and adding a test of refinement for squeeze and funcy --- src/diffpy/morph/morph_api.py | 18 +++++++++- src/diffpy/morph/morphs/__init__.py | 4 +++ src/diffpy/morph/morphs/morphfuncy.py | 1 + src/diffpy/morph/refine.py | 47 ++++++++++++++++++++++----- tests/test_morph_func.py | 42 ++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 9 deletions(-) diff --git a/src/diffpy/morph/morph_api.py b/src/diffpy/morph/morph_api.py index 65b0fa66..2cb5309d 100644 --- a/src/diffpy/morph/morph_api.py +++ b/src/diffpy/morph/morph_api.py @@ -39,9 +39,17 @@ morph_helpers.TransformXtalRDFtoPDF, ], qdamp=morphs.MorphResolutionDamping, + squeeze=morphs.MorphSqueeze, + parameters=morphs.MorphFuncy, ) _default_config = dict( - scale=None, stretch=None, smear=None, baselineslope=None, qdamp=None + scale=None, + stretch=None, + smear=None, + baselineslope=None, + qdamp=None, + squeeze=None, + parameters=None, ) @@ -197,6 +205,14 @@ def morph( if k == "smear": [chain.append(el()) for el in morph_cls] refpars.append("baselineslope") + elif k == "parameters": + morph_inst = morph_cls() + morph_inst.function = rv_cfg.get("function", None) + if morph_inst.function is None: + raise ValueError( + "Must provide a 'function' when using 'parameters'" + ) + chain.append(morph_inst) else: chain.append(morph_cls()) refpars.append(k) diff --git a/src/diffpy/morph/morphs/__init__.py b/src/diffpy/morph/morphs/__init__.py index 3cc0fdc9..0322e98f 100644 --- a/src/diffpy/morph/morphs/__init__.py +++ b/src/diffpy/morph/morphs/__init__.py @@ -19,6 +19,7 @@ from diffpy.morph.morphs.morph import Morph # noqa: F401 from diffpy.morph.morphs.morphchain import MorphChain # noqa: F401 +from diffpy.morph.morphs.morphfuncy import MorphFuncy from diffpy.morph.morphs.morphishape import MorphISphere, MorphISpheroid from diffpy.morph.morphs.morphresolution import MorphResolutionDamping from diffpy.morph.morphs.morphrgrid import MorphRGrid @@ -26,6 +27,7 @@ from diffpy.morph.morphs.morphshape import MorphSphere, MorphSpheroid from diffpy.morph.morphs.morphshift import MorphShift from diffpy.morph.morphs.morphsmear import MorphSmear +from diffpy.morph.morphs.morphsqueeze import MorphSqueeze from diffpy.morph.morphs.morphstretch import MorphStretch # List of morphs @@ -40,6 +42,8 @@ MorphISpheroid, MorphResolutionDamping, MorphShift, + MorphSqueeze, + MorphFuncy, ] # End of file diff --git a/src/diffpy/morph/morphs/morphfuncy.py b/src/diffpy/morph/morphs/morphfuncy.py index b9d65f2f..929380f1 100644 --- a/src/diffpy/morph/morphs/morphfuncy.py +++ b/src/diffpy/morph/morphs/morphfuncy.py @@ -11,6 +11,7 @@ class MorphFuncy(Morph): yinlabel = LABEL_GR xoutlabel = LABEL_RA youtlabel = LABEL_GR + parnames = ["parameters"] def morph(self, x_morph, y_morph, x_target, y_target): """General morph function that applies a user-supplied function to the diff --git a/src/diffpy/morph/refine.py b/src/diffpy/morph/refine.py index e1f55ac3..4a09a83b 100644 --- a/src/diffpy/morph/refine.py +++ b/src/diffpy/morph/refine.py @@ -26,8 +26,6 @@ class Refiner(object): """Class for refining a Morph or MorphChain. - This is provided to allow for custom residuals and refinement algorithms. - Attributes ---------- chain @@ -51,12 +49,27 @@ def __init__(self, chain, x_morph, y_morph, x_target, y_target): self.y_target = y_target self.pars = [] self.residual = self._residual + self.flat_to_grouped = {} return def _update_chain(self, pvals): """Update the parameters in the chain.""" - pairs = zip(self.pars, pvals) - self.chain.config.update(pairs) + updated = {} + for idx, val in enumerate(pvals): + p, subkey = self.flat_to_grouped[idx] + if subkey is None: + updated[p] = val + else: + if p not in updated: + updated[p] = {} if isinstance(subkey, str) else [] + if isinstance(updated[p], dict): + updated[p][subkey] = val + else: + while len(updated[p]) <= subkey: + updated[p].append(0.0) + updated[p][subkey] = val + + self.chain.config.update(updated) return def _residual(self, pvals): @@ -118,20 +131,38 @@ def refine(self, *args, **kw): if not self.pars: return 0.0 - initial = [config[p] for p in self.pars] + # Build flat list of initial parameters and flat_to_grouped mapping + initial = [] + self.flat_to_grouped = {} + + for p in self.pars: + val = config[p] + if isinstance(val, dict): + for k, v in val.items(): + initial.append(v) + self.flat_to_grouped[len(initial) - 1] = (p, k) + elif isinstance(val, list): + for i, v in enumerate(val): + initial.append(v) + self.flat_to_grouped[len(initial) - 1] = (p, i) + else: + initial.append(val) + self.flat_to_grouped[len(initial) - 1] = (p, None) + + # Perform least squares refinement sol, cov_sol, infodict, emesg, ier = leastsq( self.residual, initial, full_output=1 ) fvec = infodict["fvec"] + if ier not in (1, 2, 3, 4): - emesg raise ValueError(emesg) - # Place the fit parameters in config + # Place the fit parameters back into config vals = sol if not hasattr(vals, "__iter__"): vals = [vals] - self.chain.config.update(zip(self.pars, vals)) + self._update_chain(vals) return dot(fvec, fvec) diff --git a/tests/test_morph_func.py b/tests/test_morph_func.py index 3f62f872..f785c990 100644 --- a/tests/test_morph_func.py +++ b/tests/test_morph_func.py @@ -101,3 +101,45 @@ def test_smear_with_morph_func(): assert np.allclose(y0, y1, atol=1e-3) # numerical error -> 1e-4 # verify morphed param assert np.allclose(smear, morphed_cfg["smear"], atol=1e-1) + + +def test_squeeze_with_morph_func(): + squeeze_init = [0, -0.001, -0.0001, 0.0001] + x_morph = np.linspace(0, 10, 101) + y_morph = 2 * np.sin( + x_morph + x_morph * (-0.01) - 0.0001 * x_morph**2 + 0.0002 * x_morph**3 + ) + expected_squeeze = [0, -0.01, -0.0001, 0.0002] + expected_scale = 1 / 2 + x_target = x_morph.copy() + y_target = np.sin(x_target) + cfg = morph_default_config(scale=1.1, squeeze=squeeze_init) # off init + morph_rv = morph(x_morph, y_morph, x_target, y_target, **cfg) + morphed_cfg = morph_rv["morphed_config"] + # verified they are morphable + x1, y1, x0, y0 = morph_rv["morph_chain"].xyallout + assert np.allclose(x0, x1) + assert np.allclose(y0, y1, atol=1e-3) # numerical error -> 1e-4 + # verify morphed param + assert np.allclose(expected_squeeze, morphed_cfg["squeeze"], atol=1e-4) + assert np.allclose(expected_scale, morphed_cfg["scale"], atol=1e-4) + + +def test_funcy_with_morph_func(): + def linear_function(x, y, scale, offset): + return (scale * x) * y + offset + + x_morph = np.linspace(0, 10, 101) + y_morph = np.sin(x_morph) + x_target = x_morph.copy() + y_target = np.sin(x_target) * 2 * x_target + 0.4 + cfg = morph_default_config(parameters={"scale": 1.2, "offset": 0.1}) + cfg["function"] = linear_function + morph_rv = morph(x_morph, y_morph, x_target, y_target, **cfg) + morphed_cfg = morph_rv["morphed_config"] + x1, y1, x0, y0 = morph_rv["morph_chain"].xyallout + assert np.allclose(x0, x1) + assert np.allclose(y0, y1, atol=1e-6) + fitted_parameters = morphed_cfg["parameters"] + assert np.allclose(fitted_parameters["scale"], 2, atol=1e-6) + assert np.allclose(fitted_parameters["offset"], 0.4, atol=1e-6) From 82848b4d9eccaa50e66dee1899b3932d13157c05 Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Tue, 29 Apr 2025 10:54:36 -0600 Subject: [PATCH 2/7] test/func: modifying refinement to allow lists and dicts and adding a test of refinement for squeeze and funcy --- src/diffpy/morph/morph_api.py | 2 ++ src/diffpy/morph/refine.py | 25 ++++++++++++------------- tests/test_morph_func.py | 30 ++++++++++++++++-------------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/diffpy/morph/morph_api.py b/src/diffpy/morph/morph_api.py index 2cb5309d..6e83790a 100644 --- a/src/diffpy/morph/morph_api.py +++ b/src/diffpy/morph/morph_api.py @@ -143,6 +143,8 @@ def morph( - 'smear' - 'baselineslope' - 'qdamp' + - 'squeeze' + - 'parameters' Returns ------- diff --git a/src/diffpy/morph/refine.py b/src/diffpy/morph/refine.py index 4a09a83b..2262d955 100644 --- a/src/diffpy/morph/refine.py +++ b/src/diffpy/morph/refine.py @@ -55,20 +55,20 @@ def __init__(self, chain, x_morph, y_morph, x_target, y_target): def _update_chain(self, pvals): """Update the parameters in the chain.""" updated = {} - for idx, val in enumerate(pvals): - p, subkey = self.flat_to_grouped[idx] - if subkey is None: - updated[p] = val + for idx, value in enumerate(pvals): + param, subkey = self.flat_to_grouped[idx] + if subkey is None: # Scalar + updated[param] = value else: - if p not in updated: - updated[p] = {} if isinstance(subkey, str) else [] - if isinstance(updated[p], dict): - updated[p][subkey] = val + if param not in updated: + updated[param] = {} if isinstance(subkey, str) else [] + if isinstance(updated[param], dict): + updated[param][subkey] = value else: - while len(updated[p]) <= subkey: - updated[p].append(0.0) - updated[p][subkey] = val - + while len(updated[param]) <= subkey: + updated[param].append(0.0) + updated[param][subkey] = value + # Apply the reconstructed grouped parameter back to config self.chain.config.update(updated) return @@ -149,7 +149,6 @@ def refine(self, *args, **kw): initial.append(val) self.flat_to_grouped[len(initial) - 1] = (p, None) - # Perform least squares refinement sol, cov_sol, infodict, emesg, ier = leastsq( self.residual, initial, full_output=1 ) diff --git a/tests/test_morph_func.py b/tests/test_morph_func.py index f785c990..9f715772 100644 --- a/tests/test_morph_func.py +++ b/tests/test_morph_func.py @@ -107,22 +107,22 @@ def test_squeeze_with_morph_func(): squeeze_init = [0, -0.001, -0.0001, 0.0001] x_morph = np.linspace(0, 10, 101) y_morph = 2 * np.sin( - x_morph + x_morph * (-0.01) - 0.0001 * x_morph**2 + 0.0002 * x_morph**3 + x_morph + x_morph * 0.01 + 0.0001 * x_morph**2 + 0.001 * x_morph**3 ) - expected_squeeze = [0, -0.01, -0.0001, 0.0002] + expected_squeeze = [0, 0.01, 0.0001, 0.001] expected_scale = 1 / 2 - x_target = x_morph.copy() + x_target = np.linspace(0, 10, 101) y_target = np.sin(x_target) - cfg = morph_default_config(scale=1.1, squeeze=squeeze_init) # off init + cfg = morph_default_config(scale=1.1, squeeze=squeeze_init) morph_rv = morph(x_morph, y_morph, x_target, y_target, **cfg) morphed_cfg = morph_rv["morphed_config"] - # verified they are morphable - x1, y1, x0, y0 = morph_rv["morph_chain"].xyallout - assert np.allclose(x0, x1) - assert np.allclose(y0, y1, atol=1e-3) # numerical error -> 1e-4 - # verify morphed param - assert np.allclose(expected_squeeze, morphed_cfg["squeeze"], atol=1e-4) - assert np.allclose(expected_scale, morphed_cfg["scale"], atol=1e-4) + x_morph_out, y_morph_out, x_target_out, y_target_out = morph_rv[ + "morph_chain" + ].xyallout + assert np.allclose(x_morph_out, x_target_out) + assert np.allclose(y_morph_out, y_target_out, atol=1e-6) + assert np.allclose(expected_squeeze, morphed_cfg["squeeze"], atol=1e-6) + assert np.allclose(expected_scale, morphed_cfg["scale"], atol=1e-6) def test_funcy_with_morph_func(): @@ -137,9 +137,11 @@ def linear_function(x, y, scale, offset): cfg["function"] = linear_function morph_rv = morph(x_morph, y_morph, x_target, y_target, **cfg) morphed_cfg = morph_rv["morphed_config"] - x1, y1, x0, y0 = morph_rv["morph_chain"].xyallout - assert np.allclose(x0, x1) - assert np.allclose(y0, y1, atol=1e-6) + x_morph_out, y_morph_out, x_target_out, y_target_out = morph_rv[ + "morph_chain" + ].xyallout + assert np.allclose(x_morph_out, x_target_out) + assert np.allclose(y_morph_out, y_target_out, atol=1e-6) fitted_parameters = morphed_cfg["parameters"] assert np.allclose(fitted_parameters["scale"], 2, atol=1e-6) assert np.allclose(fitted_parameters["offset"], 0.4, atol=1e-6) From e198331fad3a0a9531e6479a2426b57a42c5e113 Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Tue, 29 Apr 2025 11:23:45 -0600 Subject: [PATCH 3/7] news: adding news for dicts and lists refinement --- news/dict_lists_refinement.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/dict_lists_refinement.rst diff --git a/news/dict_lists_refinement.rst b/news/dict_lists_refinement.rst new file mode 100644 index 00000000..4e7fcb39 --- /dev/null +++ b/news/dict_lists_refinement.rst @@ -0,0 +1,23 @@ +**Added:** + +* Functionality for refining lists and dictionaries + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 997c8c19d11713fdeb8d14d64d7375afa3ae16d8 Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Tue, 29 Apr 2025 11:28:08 -0600 Subject: [PATCH 4/7] test/func: modifying refinement to allow lists and dicts and adding a test of refinement for squeeze and funcy --- src/diffpy/morph/refine.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/diffpy/morph/refine.py b/src/diffpy/morph/refine.py index 2262d955..588befc0 100644 --- a/src/diffpy/morph/refine.py +++ b/src/diffpy/morph/refine.py @@ -155,6 +155,7 @@ def refine(self, *args, **kw): fvec = infodict["fvec"] if ier not in (1, 2, 3, 4): + emesg raise ValueError(emesg) # Place the fit parameters back into config From ca832a6f1f89f8227feabfb6ed65548c66888503 Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Tue, 29 Apr 2025 11:30:51 -0600 Subject: [PATCH 5/7] test/func: modifying refinement to allow lists and dicts and adding a test of refinement for squeeze and funcy --- src/diffpy/morph/refine.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/diffpy/morph/refine.py b/src/diffpy/morph/refine.py index 588befc0..4fbd80ed 100644 --- a/src/diffpy/morph/refine.py +++ b/src/diffpy/morph/refine.py @@ -26,6 +26,8 @@ class Refiner(object): """Class for refining a Morph or MorphChain. + This is provided to allow for custom residuals and refinement algorithms. + Attributes ---------- chain @@ -158,7 +160,7 @@ def refine(self, *args, **kw): emesg raise ValueError(emesg) - # Place the fit parameters back into config + # Place the fit parameters in config vals = sol if not hasattr(vals, "__iter__"): vals = [vals] From 655676e08269e2d57df4a53fcaa16bb3965d8021 Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Wed, 30 Apr 2025 11:47:42 -0600 Subject: [PATCH 6/7] test/func: replacing parameters name with funcy --- src/diffpy/morph/morph_api.py | 8 ++++---- src/diffpy/morph/morphs/morphfuncy.py | 8 ++++---- tests/test_morph_func.py | 4 ++-- tests/test_morphfuncy.py | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/diffpy/morph/morph_api.py b/src/diffpy/morph/morph_api.py index 6e83790a..22496546 100644 --- a/src/diffpy/morph/morph_api.py +++ b/src/diffpy/morph/morph_api.py @@ -40,7 +40,7 @@ ], qdamp=morphs.MorphResolutionDamping, squeeze=morphs.MorphSqueeze, - parameters=morphs.MorphFuncy, + funcy=morphs.MorphFuncy, ) _default_config = dict( scale=None, @@ -49,7 +49,7 @@ baselineslope=None, qdamp=None, squeeze=None, - parameters=None, + funcy=None, ) @@ -144,7 +144,7 @@ def morph( - 'baselineslope' - 'qdamp' - 'squeeze' - - 'parameters' + - 'funcy' Returns ------- @@ -207,7 +207,7 @@ def morph( if k == "smear": [chain.append(el()) for el in morph_cls] refpars.append("baselineslope") - elif k == "parameters": + elif k == "funcy": morph_inst = morph_cls() morph_inst.function = rv_cfg.get("function", None) if morph_inst.function is None: diff --git a/src/diffpy/morph/morphs/morphfuncy.py b/src/diffpy/morph/morphs/morphfuncy.py index 929380f1..e0b5fe7f 100644 --- a/src/diffpy/morph/morphs/morphfuncy.py +++ b/src/diffpy/morph/morphs/morphfuncy.py @@ -11,7 +11,7 @@ class MorphFuncy(Morph): yinlabel = LABEL_GR xoutlabel = LABEL_RA youtlabel = LABEL_GR - parnames = ["parameters"] + parnames = ["funcy"] def morph(self, x_morph, y_morph, x_target, y_target): """General morph function that applies a user-supplied function to the @@ -50,7 +50,7 @@ def morph(self, x_morph, y_morph, x_target, y_target): and target array (x_target, y_target): >>> morph = MorphFuncy() >>> morph.function = sine_function - >>> morph.parameters = parameters + >>> morph.funcy = parameters >>> x_morph_out, y_morph_out, x_target_out, y_target_out = morph.morph( ... x_morph, y_morph, x_target, y_target) @@ -59,11 +59,11 @@ def morph(self, x_morph, y_morph, x_target, y_target): >>> y_morph_in = morph.y_morph_in >>> x_target_in = morph.x_target_in >>> y_target_in = morph.y_target_in - >>> parameters_out = morph.parameters + >>> parameters_out = morph.funcy """ Morph.morph(self, x_morph, y_morph, x_target, y_target) self.y_morph_out = self.function( - self.x_morph_in, self.y_morph_in, **self.parameters + self.x_morph_in, self.y_morph_in, **self.funcy ) return self.xyallout diff --git a/tests/test_morph_func.py b/tests/test_morph_func.py index 9f715772..098a6acb 100644 --- a/tests/test_morph_func.py +++ b/tests/test_morph_func.py @@ -133,7 +133,7 @@ def linear_function(x, y, scale, offset): y_morph = np.sin(x_morph) x_target = x_morph.copy() y_target = np.sin(x_target) * 2 * x_target + 0.4 - cfg = morph_default_config(parameters={"scale": 1.2, "offset": 0.1}) + cfg = morph_default_config(funcy={"scale": 1.2, "offset": 0.1}) cfg["function"] = linear_function morph_rv = morph(x_morph, y_morph, x_target, y_target, **cfg) morphed_cfg = morph_rv["morphed_config"] @@ -142,6 +142,6 @@ def linear_function(x, y, scale, offset): ].xyallout assert np.allclose(x_morph_out, x_target_out) assert np.allclose(y_morph_out, y_target_out, atol=1e-6) - fitted_parameters = morphed_cfg["parameters"] + fitted_parameters = morphed_cfg["funcy"] assert np.allclose(fitted_parameters["scale"], 2, atol=1e-6) assert np.allclose(fitted_parameters["offset"], 0.4, atol=1e-6) diff --git a/tests/test_morphfuncy.py b/tests/test_morphfuncy.py index a73a8096..31749fd6 100644 --- a/tests/test_morphfuncy.py +++ b/tests/test_morphfuncy.py @@ -63,7 +63,7 @@ def test_funcy(function, parameters, expected_function): y_morph_expected = expected_function(x_morph, y_morph) morph = MorphFuncy() morph.function = function - morph.parameters = parameters + morph.funcy = parameters x_morph_actual, y_morph_actual, x_target_actual, y_target_actual = ( morph.morph(x_morph, y_morph, x_target, y_target) ) From a991072c28a00c65a0e7b7a352172986ce5c0082 Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Wed, 30 Apr 2025 16:06:54 -0600 Subject: [PATCH 7/7] func/test: replacing squeeze list for dictionary --- src/diffpy/morph/morphs/morphsqueeze.py | 11 ++++++----- src/diffpy/morph/refine.py | 14 +++----------- tests/test_morph_func.py | 17 ++++++++++++++--- tests/test_morphsqueeze.py | 25 +++++++++++++------------ 4 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/diffpy/morph/morphs/morphsqueeze.py b/src/diffpy/morph/morphs/morphsqueeze.py index e63adc8b..fc6493d6 100644 --- a/src/diffpy/morph/morphs/morphsqueeze.py +++ b/src/diffpy/morph/morphs/morphsqueeze.py @@ -28,11 +28,11 @@ def morph(self, x_morph, y_morph, x_target, y_target): Configuration Variables ----------------------- - squeeze : list - The polynomial coefficients [a0, a1, ..., an] for the squeeze + squeeze : Dictionary + The polynomial coefficients {a0, a1, ..., an} for the squeeze function where the polynomial would be of the form a0 + a1*x + a2*x^2 and so on. The order of the polynomial is - determined by the length of the list. + determined by the length of the dictionary. Returns ------- @@ -46,7 +46,7 @@ def morph(self, x_morph, y_morph, x_target, y_target): Import the squeeze morph function: >>> from diffpy.morph.morphs.morphsqueeze import MorphSqueeze Provide initial guess for squeezing coefficients: - >>> squeeze_coeff = [0.1, -0.01, 0.005] + >>> squeeze_coeff = {"a0":0.1, "a1":-0.01, "a2":0.005} Run the squeeze morph given input morph array (x_morph, y_morph) and target array (x_target, y_target): >>> morph = MorphSqueeze() @@ -62,7 +62,8 @@ def morph(self, x_morph, y_morph, x_target, y_target): """ Morph.morph(self, x_morph, y_morph, x_target, y_target) - squeeze_polynomial = Polynomial(self.squeeze) + coeffs = [self.squeeze[f"a{i}"] for i in range(len(self.squeeze))] + squeeze_polynomial = Polynomial(coeffs) x_squeezed = self.x_morph_in + squeeze_polynomial(self.x_morph_in) self.y_morph_out = CubicSpline(x_squeezed, self.y_morph_in)( self.x_morph_in diff --git a/src/diffpy/morph/refine.py b/src/diffpy/morph/refine.py index 4fbd80ed..11b4a2c0 100644 --- a/src/diffpy/morph/refine.py +++ b/src/diffpy/morph/refine.py @@ -63,13 +63,9 @@ def _update_chain(self, pvals): updated[param] = value else: if param not in updated: - updated[param] = {} if isinstance(subkey, str) else [] - if isinstance(updated[param], dict): - updated[param][subkey] = value - else: - while len(updated[param]) <= subkey: - updated[param].append(0.0) - updated[param][subkey] = value + updated[param] = {} + updated[param][subkey] = value + # Apply the reconstructed grouped parameter back to config self.chain.config.update(updated) return @@ -143,10 +139,6 @@ def refine(self, *args, **kw): for k, v in val.items(): initial.append(v) self.flat_to_grouped[len(initial) - 1] = (p, k) - elif isinstance(val, list): - for i, v in enumerate(val): - initial.append(v) - self.flat_to_grouped[len(initial) - 1] = (p, i) else: initial.append(val) self.flat_to_grouped[len(initial) - 1] = (p, None) diff --git a/tests/test_morph_func.py b/tests/test_morph_func.py index 098a6acb..cb0442d1 100644 --- a/tests/test_morph_func.py +++ b/tests/test_morph_func.py @@ -104,12 +104,12 @@ def test_smear_with_morph_func(): def test_squeeze_with_morph_func(): - squeeze_init = [0, -0.001, -0.0001, 0.0001] + squeeze_init = {"a0": 0, "a1": -0.001, "a2": -0.0001, "a3": 0.0001} x_morph = np.linspace(0, 10, 101) y_morph = 2 * np.sin( x_morph + x_morph * 0.01 + 0.0001 * x_morph**2 + 0.001 * x_morph**3 ) - expected_squeeze = [0, 0.01, 0.0001, 0.001] + expected_squeeze = {"a0": 0, "a1": 0.01, "a2": 0.0001, "a3": 0.001} expected_scale = 1 / 2 x_target = np.linspace(0, 10, 101) y_target = np.sin(x_target) @@ -121,7 +121,18 @@ def test_squeeze_with_morph_func(): ].xyallout assert np.allclose(x_morph_out, x_target_out) assert np.allclose(y_morph_out, y_target_out, atol=1e-6) - assert np.allclose(expected_squeeze, morphed_cfg["squeeze"], atol=1e-6) + assert np.allclose( + expected_squeeze["a0"], morphed_cfg["squeeze"]["a0"], atol=1e-6 + ) + assert np.allclose( + expected_squeeze["a1"], morphed_cfg["squeeze"]["a1"], atol=1e-6 + ) + assert np.allclose( + expected_squeeze["a2"], morphed_cfg["squeeze"]["a2"], atol=1e-6 + ) + assert np.allclose( + expected_squeeze["a3"], morphed_cfg["squeeze"]["a3"], atol=1e-6 + ) assert np.allclose(expected_scale, morphed_cfg["scale"], atol=1e-6) diff --git a/tests/test_morphsqueeze.py b/tests/test_morphsqueeze.py index 0de38e81..e5ce2a56 100644 --- a/tests/test_morphsqueeze.py +++ b/tests/test_morphsqueeze.py @@ -4,24 +4,24 @@ from diffpy.morph.morphs.morphsqueeze import MorphSqueeze -squeeze_coeffs_list = [ - # The order of coefficients is [a0, a1, a2, ..., an] +squeeze_coeffs_dic = [ + # The order of coefficients is {a0, a1, a2, ..., an} # Negative cubic squeeze coefficients - [-0.01, -0.0005, -0.0005, -1e-6], + {"a0": -0.01, "a1": -0.0005, "a2": -0.0005, "a3": -1e-6}, # Positive cubic squeeze coefficients - [0.2, 0.01, 0.001, 0.0001], + {"a0": 0.2, "a1": 0.01, "a2": 0.001, "a3": 0.0001}, # Positive and negative cubic squeeze coefficients - [0.2, -0.01, 0.002, -0.0001], + {"a0": 0.2, "a1": -0.01, "a2": 0.002, "a3": -0.0001}, # Quadratic squeeze coefficients - [-0.2, 0.005, -0.0004], + {"a0": -0.2, "a1": 0.005, "a2": -0.0004}, # Linear squeeze coefficients - [0.1, 0.3], + {"a0": 0.1, "a1": 0.3}, # 4th order squeeze coefficients - [0.2, -0.01, 0.001, -0.001, 0.0001], + {"a0": 0.2, "a1": -0.01, "a2": 0.001, "a3": -0.001, "a4": 0.0001}, # Zeros and non-zeros, the full polynomial is applied - [0, 0.03, 0, -0.0001], + {"a0": 0, "a1": 0.03, "a2": 0, "a3": -0.0001}, # Testing zeros, expect no squeezing - [0, 0, 0, 0, 0, 0], + {"a0": 0, "a1": 0, "a2": 0, "a3": 0, "a4": 0, "a5": 0}, ] morph_target_grids = [ # UCs from issue 181: https://github.com/diffpy/diffpy.morph/issues/181 @@ -41,10 +41,11 @@ @pytest.mark.parametrize("x_morph, x_target", morph_target_grids) -@pytest.mark.parametrize("squeeze_coeffs", squeeze_coeffs_list) +@pytest.mark.parametrize("squeeze_coeffs", squeeze_coeffs_dic) def test_morphsqueeze(x_morph, x_target, squeeze_coeffs): y_target = np.sin(x_target) - squeeze_polynomial = Polynomial(squeeze_coeffs) + coeffs = [squeeze_coeffs[f"a{i}"] for i in range(len(squeeze_coeffs))] + squeeze_polynomial = Polynomial(coeffs) x_squeezed = x_morph + squeeze_polynomial(x_morph) y_morph = np.sin(x_squeezed) low_extrap = np.where(x_morph < x_squeezed[0])[0]