diff --git a/news/res-dict.rst b/news/res-dict.rst new file mode 100644 index 00000000..6ebe11fc --- /dev/null +++ b/news/res-dict.rst @@ -0,0 +1,23 @@ +**Added:** + +* Added ``FitResults.get_results_dictionary`` in replace of ``resultsDictionary``. + +**Changed:** + +* + +**Deprecated:** + +* Deprecated ``resultsDictionary`` for removal in 4.0.0. + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/srfit/fitbase/fitresults.py b/src/diffpy/srfit/fitbase/fitresults.py index dabebded..d1627fa3 100644 --- a/src/diffpy/srfit/fitbase/fitresults.py +++ b/src/diffpy/srfit/fitbase/fitresults.py @@ -57,6 +57,14 @@ removal_version, ) +resultsDictionary_dep_msg = build_deprecation_message( + "diffpy.srfit.fitbase", + "resultsDictionary", + "get_results_dictionary", + removal_version, + new_base="diffpy.srfit.fitbase.FitResults", +) + class FitResults(object): """Class for processing, presenting and storing results of a fit. @@ -619,6 +627,31 @@ def saveResults(self, filename, header="", footer="", update=False): self.save_results(filename, header, footer, update) return + def get_results_dictionary(self): + """Get a dictionary of results, with variable names and values, and + overall metrics. + + Returns + ------- + results_dict : dict + A dictionary containing the variable names and values, and overall + metrics, from the FitResults. + """ + parameter_names = self.varnames + parameter_values = self.varvals + results_dict = dict(zip(parameter_names, parameter_values)) + results_dict.update( + { + "Residual": self.residual, + "Contributions": self.residual - self.penalty, + "Restraints": self.penalty, + "Chi2": self.chi2, + "Reduced Chi2": self.rchi2, + "Rw": self.rw, + } + ) + return results_dict + # End class FitResults @@ -750,8 +783,15 @@ def _calculate_metrics(self): # End class ContributionResults +@deprecated(resultsDictionary_dep_msg) def resultsDictionary(results): - """Get dictionary of results from file. + """**This function has been deprecated and will be** **removed in version + 4.0.0.** + + **Please use** + **diffpy.srfit.fitbase.FitResults.get_results_dictionary instead.** + + Get dictionary of results from file. This reads the results from file and stores them in a dictionary to be returned to the caller. The dictionary may contain non-result entries. diff --git a/tests/conftest.py b/tests/conftest.py index 0417ad17..61bfa490 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -223,4 +223,32 @@ def temp_data_files(tmp_path): cgr_file = tmp_path / "cgr_file.cgr" cgr_file.write_text("1.0 2.0\n" "1.1 2.1\n" "1.2 2.2\n") + + results_file = tmp_path / "fit_results.res" + results_file.write_text( + """ +Results written: Wed Feb 25 15:14:58 2026 +produced by cadenmyers + +Some quantities invalid due to missing profile uncertainty +Overall (Chi2 and Reduced Chi2 invalid) +------------------------------------------------------------------------------ +Residual 0.00000000 +Contributions 0.00000000 +Restraints 0.00000000 +Chi2 0.00000000 +Reduced Chi2 0.00000000 +Rw 0.00000000 + +Variables (Uncertainties invalid) +------------------------------------------------------------------------------ +amplitude 1.00000000e+00 +/- 4.82804000e-01 +phase_shift -1.61291146e-18 +/- 1.00000000e+00 +wave_number 1.00000000e+00 +/- 2.17496687e-01 + +Variable Correlations greater than 25% (Correlations invalid) +------------------------------------------------------------------------------ +No correlations greater than 25% +""" + ) yield tmp_path diff --git a/tests/test_fitresults.py b/tests/test_fitresults.py index 761a30ff..b9fb7492 100644 --- a/tests/test_fitresults.py +++ b/tests/test_fitresults.py @@ -16,11 +16,16 @@ import unittest +import numpy as np import pytest from scipy.optimize import leastsq from diffpy.srfit.fitbase.fitrecipe import FitRecipe -from diffpy.srfit.fitbase.fitresults import FitResults, initializeRecipe +from diffpy.srfit.fitbase.fitresults import ( + FitResults, + initializeRecipe, + resultsDictionary, +) # The fit results from the recipe fixture in conftest.py expected_fitresults = """\ @@ -138,6 +143,69 @@ def test_save_results(build_recipe_one_contribution, tmp_path): assert expected_var in actual_results.strip() +def test_get_results_dictionary(build_recipe_one_contribution): + # Case: user gets results dictionary after optimization + # expected: results dictionary contains expected keys and values + recipe = build_recipe_one_contribution + optimize_recipe(recipe) + results = FitResults(recipe) + actual_results_dict = results.get_results_dictionary() + expected_results_dict = { + "amplitude": 1.000000000060171, + "wave_number": 1.00000000012548, + "phase_shift": -1.6129114631049646e-18, + "Residual": 3.3284672708760557e-19, + "Contributions": 3.3284672708760557e-19, + "Restraints": 0, + "Chi2": 3.3284672708760557e-19, + "Reduced Chi2": 4.7549532441086507e-20, + "Rw": 2.7196679825449506e-10, + } + actual_values = np.round(np.array(list(actual_results_dict.values())), 5) + actual_keys = set(actual_results_dict.keys()) + expected_values = np.round( + np.array(list(expected_results_dict.values())), 5 + ) + expected_keys = set(expected_results_dict.keys()) + assert expected_keys == actual_keys + assert list(expected_values == list(actual_values)) + + +def test_resultsDictionary(temp_data_files): + # Case: user gets results dictionary from a results file + # expected: results dictionary contains expected keys and values + actual_results_dict = resultsDictionary( + temp_data_files / "fit_results.res" + ) + # bad behavior: values are stored as strings + expected_results_dict = { + "than": "25", # bad behavior: shouldn't be here + "wave_number": "1.00000000e+00", + "phase_shift": "-1.61291146e-18", + "amplitude": "1.00000000e+00", + "Rw": "0.00000000", + "Chi2": "0.00000000", + "Restraints": "0.00000000", + "Contributions": "0.00000000", + "Residual": "0.00000000", + "Feb": "25", # bad behavior: shouldn't be here + } + # convert values to float for comparison (with rounding) + for key in expected_results_dict: + expected_results_dict[key] = float(expected_results_dict[key]) + for key in actual_results_dict: + actual_results_dict[key] = float(actual_results_dict[key]) + + actual_keys = set(actual_results_dict.keys()) + actual_values = np.round(np.array(list(actual_results_dict.values())), 5) + expected_keys = set(expected_results_dict.keys()) + expected_values = np.round( + np.array(list(expected_results_dict.values())), 5 + ) + assert expected_keys == actual_keys + assert list(expected_values == list(actual_values)) + + def testInitializeFromFileName(datafile): recipe = FitRecipe("recipe") recipe.create_new_variable("A", 0)