@@ -54,6 +54,8 @@ def test_morphsqueeze(x_morph, x_target, squeeze_coeffs):
5454 x_target_expected = x_target
5555 y_target_expected = y_target
5656 # actual output
57+ # turn the coefficients into a list for passing to Polynomial
58+ # the morphsqueeze function itself requires a dictionary
5759 coeffs = [squeeze_coeffs [f"a{ i } " ] for i in range (len (squeeze_coeffs ))]
5860 squeeze_polynomial = Polynomial (coeffs )
5961 x_squeezed = x_morph + squeeze_polynomial (x_morph )
@@ -139,16 +141,16 @@ def test_morphsqueeze_extrapolate(user_filesystem, squeeze_coeffs, wmsg_gen):
139141 coeffs = [squeeze_coeffs [f"a{ i } " ] for i in range (len (squeeze_coeffs ))]
140142 squeeze_polynomial = Polynomial (coeffs )
141143 x_squeezed = x_morph + squeeze_polynomial (x_morph )
142- with pytest .warns () as w :
144+ with pytest .warns () as warning :
143145 morphpy .morph_arrays (
144146 np .array ([x_morph , y_morph ]).T ,
145147 np .array ([x_target , y_target ]).T ,
146148 squeeze = coeffs ,
147149 apply = True ,
148150 )
149- assert len (w ) == 1
150- assert w [0 ].category is UserWarning
151- actual_wmsg = str (w [0 ].message )
151+ assert len (warning ) == 1
152+ assert warning [0 ].category is UserWarning
153+ actual_wmsg = str (warning [0 ].message )
152154 expected_wmsg = wmsg_gen ([min (x_squeezed ), max (x_squeezed )])
153155 assert actual_wmsg == expected_wmsg
154156
@@ -170,3 +172,134 @@ def test_morphsqueeze_extrapolate(user_filesystem, squeeze_coeffs, wmsg_gen):
170172 )
171173 with pytest .warns (UserWarning , match = expected_wmsg ):
172174 single_morph (parser , opts , pargs , stdout_flag = False )
175+
176+
177+ def test_non_unique_grid ():
178+ # Test giving morphsqueeze a non-unique grid
179+ # Expect it to return a unique grid
180+ squeeze_coeffs = {"a0" : 0.01 , "a1" : 0.01 , "a2" : - 0.1 }
181+ x_grid = np .linspace (0 , 10 , 101 )
182+
183+ coeffs = [squeeze_coeffs [f"a{ i } " ] for i in range (len (squeeze_coeffs ))]
184+ squeeze_polynomial = Polynomial (coeffs )
185+ x_morph = x_grid + squeeze_polynomial (x_grid )
186+ x_gradient = np .diff (x_morph )
187+ x_gradient_sign = np .sign (x_gradient )
188+ # non-strictly increasing means the gradient becomes 0 or negative
189+ assert not np .all (x_gradient_sign > 0 )
190+
191+ x_target = np .linspace (min (x_morph ), max (x_morph ), len (x_morph ))
192+ y_target = np .sin (x_target )
193+
194+ y_morph = np .sin (x_morph )
195+ # apply no squeeze, but the morph should sort the function
196+ _ , table = morphpy .morph_arrays (
197+ np .array ([x_morph , y_morph ]).T ,
198+ np .array ([x_target , y_target ]).T ,
199+ squeeze = [0 , 0 , 0 ],
200+ apply = True ,
201+ )
202+ x_refined , _ = table [:, 0 ], table [:, 1 ]
203+
204+ # grid should be properly sorted
205+ assert np .allclose (x_refined , x_target )
206+ # note that the function itself may be distorted
207+
208+
209+ @pytest .mark .parametrize (
210+ "squeeze_coeffs, x_morph" ,
211+ [
212+ # The following squeezes make the function non-monotonic.
213+ # Expect code to work but issue the correct warning.
214+ ([- 1 , - 1 , 2 ], np .linspace (- 1 , 1 , 101 )),
215+ (
216+ [- 1 , - 1 , 0 , 0 , 2 ],
217+ np .linspace (- 1 , 1 , 101 ),
218+ ),
219+ ],
220+ )
221+ def test_squeeze_warnings (user_filesystem , squeeze_coeffs , x_morph ):
222+ # call in .py
223+ x_target = x_morph
224+ y_target = np .sin (x_target )
225+ squeeze_polynomial = Polynomial (squeeze_coeffs )
226+ x_squeezed = x_morph + squeeze_polynomial (x_morph )
227+ y_morph = np .sin (x_squeezed )
228+ morph = MorphSqueeze ()
229+ morph .squeeze = squeeze_coeffs
230+ with pytest .warns () as warning :
231+ morphpy .morph_arrays (
232+ np .array ([x_morph , y_morph ]).T ,
233+ np .array ([x_target , y_target ]).T ,
234+ squeeze = squeeze_coeffs ,
235+ apply = True ,
236+ )
237+ assert len (warning ) == 1
238+ assert warning [0 ].category is UserWarning
239+ actual_wmsg = str (warning [0 ].message )
240+ expected_wmsg = (
241+ "Warning: The squeeze morph has interpolated your morphed "
242+ "function from a non-monotonically increasing grid. "
243+ "\n This may not be an issue, but please check for your "
244+ "particular case. "
245+ "\n To avoid squeeze making your grid non-monotonic, "
246+ "here are some suggested fixes: "
247+ "\n (1) Please decrease the order of your polynomial and try again. "
248+ "\n (2) If you are using initial guesses of all 0, please ensure "
249+ "your objective function only requires a small polynomial "
250+ "squeeze to match your reference. "
251+ "(In other words, there is good agreement between the two "
252+ "functions.) "
253+ "\n (3) If you expect a large polynomial squeeze to be needed, "
254+ "please ensure your initial parameters for the polynomial "
255+ "morph result in good agreement between your reference and "
256+ "objective functions. One way to obtain such parameters is to "
257+ "first apply a --hshift and --stretch morph. "
258+ "Then, use the hshift parameter for a0 and stretch parameter for a1."
259+ )
260+ assert expected_wmsg in actual_wmsg
261+
262+ # call in CLI
263+ morph_file , target_file = create_morph_data_file (
264+ user_filesystem / "cwd_dir" , x_morph , y_morph , x_target , y_target
265+ )
266+ parser = create_option_parser ()
267+ (opts , pargs ) = parser .parse_args (
268+ [
269+ "--squeeze" ,
270+ "," .join (map (str , squeeze_coeffs )),
271+ f"{ morph_file .as_posix ()} " ,
272+ f"{ target_file .as_posix ()} " ,
273+ "--apply" ,
274+ "-n" ,
275+ ]
276+ )
277+ with pytest .warns (UserWarning ) as warning :
278+ single_morph (parser , opts , pargs , stdout_flag = False )
279+ assert len (warning ) == 1
280+ actual_wmsg = str (warning [0 ].message )
281+ assert expected_wmsg in actual_wmsg
282+
283+
284+ @pytest .mark .parametrize (
285+ "x_sampled" ,
286+ [
287+ # Expected output: all repeated datapoints are removed
288+ # Test one duplicate per number
289+ np .array ([0 , 0 , 1 , 1 , 2 , 2 , 3 , 3 ]),
290+ # Test more than one duplicates per number
291+ np .array ([0 , 0 , 0 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 ]),
292+ # Test with only one grid number
293+ np .array ([0 , 0 , 0 , 0 ]),
294+ # Test no duplicates
295+ np .array ([0 , 1 , 2 , 3 , 4 ]),
296+ ],
297+ )
298+ def test_handle_duplicates (x_sampled ):
299+ morph = MorphSqueeze ()
300+ y_sampled = np .sin (x_sampled )
301+ x_handled , y_handled = morph ._handle_duplicates (x_sampled , y_sampled )
302+ x_target = np .unique (x_sampled )
303+ y_target = np .array ([y_sampled [x_sampled == x ].mean () for x in x_target ])
304+ assert np .allclose (x_handled , x_target )
305+ assert np .allclose (y_handled , y_target )
0 commit comments