1- import scipy .io
2-
31import numpy as np
42
53from .load_results import load_results
64
5+ _FLAG_DELTA_MIN = - 6
76
8- def _load_results_m_v2 (filename ):
9- """
10- POUNDERS/MATLAB v2 format established at commit 4336b866.
11- """
12- EXPECTED_KEYS = {"alg" , "problem" , "H" , "Fvec" , "X" , "flag" , "xk_best" }
13-
14- contents = scipy .io .loadmat (filename )
15- keys = [k for k in contents .keys () if not k .startswith ("__" )]
16- assert len (keys ) == 1
17- assert keys [0 ] == "Results"
18-
19- # Only one valid set of results across all three known hfun cases.
20- data = None
21- tmp = contents [keys [0 ]]
22- assert len (tmp ) == 3
23- for hfun in range (len (tmp )):
24- # See if we can find that one valid result for this hfun case.
25- tmp_i = [e for e in tmp [hfun ] if np .squeeze (e ).ndim == 0 ]
26- if tmp_i :
27- assert len (tmp_i ) == 1
28- assert data is None
29- data = tmp_i [0 ][0 ]
30- assert data is not None
31-
32- assert set (data .dtype .names ) == EXPECTED_KEYS
33-
34- algorithm = data ["alg" ][0 ][0 ]
35- problem = data ["problem" ][0 ][0 ]
36-
37- H = np .squeeze (data ["H" ][0 ])
38- assert H .ndim == 1
39- n_evaluations = len (H )
40- assert all (np .isreal (H ))
41-
42- Fvec = np .squeeze (data ["Fvec" ][0 ])
43- assert Fvec .ndim == 2
44- tmp , _ = Fvec .shape
45- assert tmp == n_evaluations
46- assert all (np .isreal (Fvec .flatten ()))
47-
48- X = np .squeeze (data ["X" ][0 ])
49- assert X .ndim == 2
50- tmp , _ = X .shape
51- assert tmp == n_evaluations
52- assert all (np .isreal (X .flatten ()))
53- assert all (np .isfinite (X .flatten ()))
54-
55- flag = np .squeeze (data ["flag" ][0 ])
56- assert np .isreal (flag )
57- assert np .isfinite (flag )
58- # Stored as 1-based index, but needs to be 0-based index for working with
59- # Python arrays in this code.
60- xk_best = np .squeeze (data ["xk_best" ][0 ]) - 1
61- assert xk_best in range (0 , len (H ))
62-
63- return algorithm , problem , X , Fvec , H , xk_best , flag
7+
8+ def _failed (flag ):
9+ # Having the optimization terminate due to the trust region radius
10+ # shrinking down to delta_min does not necessarily indicate a failure. If
11+ # delta_min is well-specified, it could be treated as a success.
12+ return (flag < 0 ) and (flag != _FLAG_DELTA_MIN )
6413
6514
6615def compare_results (filename_benchmark , filename_result ):
6716 """
6817 .. todo::
6918 * Allow for users to specify nonzero tolerances if the use case arises.
70- * Allow for checking Python and MATLAB results on a set of problems on
71- which we expect all optimizations to find the same local minimizer.
72- This would require nonzero tolerances.
7319
7420 :param filename_benchmark: Filename of |pounders| ``.mat``-format
7521 benchmarking result that calling code considers to be the accepted
7622 reference.
7723 :param filename_result: Filename of |pounders| ``.mat``-format benchmarking
7824 result that calling code wishes to check against the reference.
7925 :return: True if the files correspond to identical test setups and contain
80- bitwise-identical results.
26+ valid, bitwise-identical results.
8127 """
8228 # ----- HARDCODED VALUES
8329 RED = "\033 [0;91;1m" # Bright Red/bold
@@ -95,7 +41,7 @@ def error(msg):
9541 error (f"New result has different filename ({ filename_result .stem } )" )
9642 return False
9743
98- ref_alg , ref_problem , X_ref , F_ref , H_ref , x_best_ref , flag_ref = _load_results_m_v2 (filename_benchmark )
44+ ref_alg , ref_problem , X_ref , F_ref , H_ref , x_best_ref , flag_ref = load_results (filename_benchmark )
9945 if not all (np .isfinite (H_ref )):
10046 error ("Non-finite h values in benchmark" )
10147 return False
@@ -111,37 +57,27 @@ def error(msg):
11157 error ("Non-finite Fvec values in new results" )
11258 return False
11359
114- # TODO: Once we are testing v3 against v3, remove this check since
115- # load_result() error checks the algorithm name and we would like to check
116- # MATLAB results against Python results.
117- if ref_alg not in ["POUNDERs" ]:
118- error (f"Invalid algorithm name ({ ref_alg } ) for benchmark" )
119- return False
120- elif new_alg != "POUNDERS_M" :
121- msg = "Benchmark and new result used different algorithms ({} != {})"
122- error (msg .format (ref_alg , new_alg ))
123- return False
124-
12560 if new_problem != ref_problem :
12661 msg = "Benchmark and new result solve different problems ({} != {})"
12762 error (msg .format (ref_problem , new_problem ))
12863 return False
12964
13065 # ----- COMPARE NEW RESULTS AGAINST BENCHMARK
131- if len (H_new ) != len (H_ref ):
132- error (f"H arrays have different lengths ({ len (H_ref )} != { len (H_new )} )" )
133- return False
134- assert F_new .shape == F_ref .shape
135- assert X_new .shape == X_ref .shape
136-
137- # Don't fail immediately if values are different so that we can provide
138- # users with all such differences in one go.
139- msgs = []
66+ # These checks are designed under the assumption that the prime use of this
67+ # function is to detect if two results are not *identical*.
68+ #
69+ # Even so, we don't fail immediately if values are different so that we can
70+ # provide users with all such differences in one go.
71+ assert F_new .shape [1 ] == F_ref .shape [1 ]
72+ assert X_new .shape [1 ] == X_ref .shape [1 ]
73+
74+ errors = []
75+ warnings = []
14076 if x_best_new != x_best_ref :
141- msgs += [f"Best approximation indices differ ({ x_best_new } != { x_best_ref } )" ]
77+ errors += [f"Best approximation indices differ ({ x_best_new } != { x_best_ref } )" ]
14278 if flag_new != flag_ref :
143- msgs += [f"Flags differ ({ flag_new } != { flag_ref } )" ]
144- if (flag_new >= 0 ) and (flag_ref >= 0 ):
79+ errors += [f"Flags differ ({ flag_new } != { flag_ref } )" ]
80+ if (not _failed ( flag_new )) and (not _failed ( flag_ref ) ):
14581 # Only show comparison if both ran without a hard failure. For
14682 # instance, I would like to see the these comparisons if one or both
14783 # were simply nonconvergent.
@@ -153,19 +89,34 @@ def error(msg):
15389 F_best_new = F_new [x_best_new ]
15490 H_best_new = H_new [x_best_new ]
15591
92+ if flag_ref == _FLAG_DELTA_MIN :
93+ warnings += ["Benchmark reached delta_min" ]
94+ if flag_new == _FLAG_DELTA_MIN :
95+ warnings += ["New result reached delta_min" ]
96+
15697 if H_best_new != H_best_ref :
15798 abs_diff = np .fabs (H_best_new - H_best_ref )
158- msgs += [f"H absolute difference = { abs_diff } " ]
99+ errors += [f"H absolute difference = { abs_diff } " ]
159100 if any (F_best_new != F_best_ref ):
160101 max_abs_diff = np .max (np .fabs (F_best_new - F_best_ref ))
161- msgs += [f"Fvec max absolute difference = { max_abs_diff } " ]
102+ errors += [f"Fvec max absolute difference = { max_abs_diff } " ]
162103 if any (X_best_new != X_best_ref ):
163104 max_abs_diff = np .max (np .fabs (X_best_new - X_best_ref ))
164- msgs += [f"X max absolute difference = { max_abs_diff } " ]
165-
166- if msgs :
167- error ("\n \t " .join (msgs ))
105+ errors += [f"X max absolute difference = { max_abs_diff } " ]
106+ else :
107+ # We've already reported an error if the flags differ and identical
108+ # "bad" flags is not necessarily a failure.
109+ if _failed (flag_ref ):
110+ warnings += [f"Benchmark failed with flag={ flag_ref } " ]
111+ if _failed (flag_new ):
112+ warnings += [f"New result failed with flag={ flag_new } " ]
113+
114+ if errors :
115+ error ("\n \t " .join (errors + warnings ))
168116 return False
117+ elif warnings :
118+ print (f"{ BLUE } PASS{ NC } \n \t " + "\n \t " .join (warnings ))
119+ return True
169120
170121 print (f"{ BLUE } PASS{ NC } " )
171122 return True
0 commit comments