diff --git a/docs/examples/debyemodel.py b/docs/examples/debyemodel.py index 1529d848..6e220c60 100644 --- a/docs/examples/debyemodel.py +++ b/docs/examples/debyemodel.py @@ -36,7 +36,7 @@ import numpy from gaussianrecipe import scipyOptimize -from diffpy.srfit.fitbase import ( +from diffpy.srfit import ( FitContribution, FitRecipe, FitResults, diff --git a/docs/examples/debyemodelII.py b/docs/examples/debyemodelII.py index c75d9cf7..97f50ca6 100644 --- a/docs/examples/debyemodelII.py +++ b/docs/examples/debyemodelII.py @@ -36,7 +36,7 @@ from debyemodel import makeRecipe, scipyOptimize -from diffpy.srfit.fitbase import FitRecipe, FitResults +from diffpy.srfit import FitRecipe, FitResults ###### # Example Code diff --git a/docs/examples/gaussiangenerator.py b/docs/examples/gaussiangenerator.py index b7d3e007..2fa41684 100644 --- a/docs/examples/gaussiangenerator.py +++ b/docs/examples/gaussiangenerator.py @@ -43,7 +43,7 @@ from numpy import exp -from diffpy.srfit.fitbase import ( +from diffpy.srfit import ( FitContribution, FitRecipe, Profile, diff --git a/docs/examples/gaussianrecipe.py b/docs/examples/gaussianrecipe.py index 61f00384..039003ae 100644 --- a/docs/examples/gaussianrecipe.py +++ b/docs/examples/gaussianrecipe.py @@ -47,7 +47,7 @@ from pathlib import Path -from diffpy.srfit.fitbase import ( +from diffpy.srfit import ( FitContribution, FitRecipe, FitResults, diff --git a/docs/examples/interface.py b/docs/examples/interface.py index 96466808..d8f90eb0 100644 --- a/docs/examples/interface.py +++ b/docs/examples/interface.py @@ -20,7 +20,7 @@ from pathlib import Path -from diffpy.srfit.fitbase import ( +from diffpy.srfit import ( FitContribution, FitRecipe, FitResults, diff --git a/docs/examples/simplerecipe.py b/docs/examples/simplerecipe.py index 0ff981f4..2d705fb4 100644 --- a/docs/examples/simplerecipe.py +++ b/docs/examples/simplerecipe.py @@ -21,7 +21,7 @@ from pathlib import Path -from diffpy.srfit.fitbase import SimpleRecipe +from diffpy.srfit import SimpleRecipe ###### # Example Code diff --git a/docs/examples/threedoublepeaks.py b/docs/examples/threedoublepeaks.py index 935aa715..a6de86a8 100644 --- a/docs/examples/threedoublepeaks.py +++ b/docs/examples/threedoublepeaks.py @@ -20,7 +20,7 @@ import numpy -from diffpy.srfit.fitbase import ( +from diffpy.srfit import ( FitContribution, FitRecipe, FitResults, diff --git a/news/export-fitbase-api.rst b/news/export-fitbase-api.rst new file mode 100644 index 00000000..4a2a531c --- /dev/null +++ b/news/export-fitbase-api.rst @@ -0,0 +1,26 @@ +**Added:** + +* The public ``fitbase`` classes can now be imported directly from + ``diffpy.srfit``, for example ``from diffpy.srfit import FitRecipe``. + +**Changed:** + +* Importing ``diffpy.srfit`` no longer imports ``matplotlib.pyplot`` or + changes the global matplotlib style. ``FitRecipe.plot_recipe`` still uses + the ``bg-style`` stylesheet, but only for the figures it creates. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/srfit/__init__.py b/src/diffpy/srfit/__init__.py index 7a70bbf2..7e4a3206 100644 --- a/src/diffpy/srfit/__init__.py +++ b/src/diffpy/srfit/__init__.py @@ -16,6 +16,32 @@ ############################################################################## """Generalized code base for modeling problems.""" +__all__ = [ + "Calculator", + "FitContribution", + "FitHook", + "FitRecipe", + "FitResults", + "PlotFitHook", + "Profile", + "ProfileGenerator", + "ProfileParser", + "SimpleRecipe", +] + +from diffpy.srfit.fitbase import ( + Calculator, + FitContribution, + FitHook, + FitRecipe, + FitResults, + PlotFitHook, + Profile, + ProfileGenerator, + ProfileParser, + SimpleRecipe, +) + # package version from diffpy.srfit.version import __version__ # noqa diff --git a/src/diffpy/srfit/fitbase/fitrecipe.py b/src/diffpy/srfit/fitbase/fitrecipe.py index cc6f6924..c23f8ca5 100644 --- a/src/diffpy/srfit/fitbase/fitrecipe.py +++ b/src/diffpy/srfit/fitbase/fitrecipe.py @@ -37,8 +37,6 @@ from collections import OrderedDict from pathlib import Path -import matplotlib.pyplot as plt -from bg_mpl_stylesheets.styles import all_styles from numpy import array, concatenate, dot, sqrt import diffpy.srfit.util.inpututils as utils @@ -49,8 +47,6 @@ from diffpy.srfit.util.tagmanager import TagManager from diffpy.utils._deprecator import build_deprecation_message, deprecated -plt.style.use(all_styles["bg-style"]) - base = "diffpy.srfit.fitbase.FitRecipe" removal_version = "4.0.0" addcontrib_dep_msg = build_deprecation_message( @@ -1695,107 +1691,115 @@ def plot_recipe(self, ax=None, return_fig=False, **kwargs): "No contributions found in recipe. " "Add contributions before plotting." ) + import matplotlib.pyplot as plt + from bg_mpl_stylesheets.styles import all_styles + figures = [] axes_list = [] shared_axes = ax is not None and len(self._contributions) > 1 - for name, contrib in self._contributions.items(): - profile = contrib.profile - x = profile.x - yobs = profile.y - ycalc = profile.ycalc - show_fit = plot_params["show_fit"] - show_diff = plot_params["show_diff"] - if ycalc is None: - if show_fit or show_diff: - print( - f"Contribution '{name}' has no calculated values " - "(ycalc is None). " - "Only observed data will be plotted." + with plt.style.context(all_styles["bg-style"]): + for name, contrib in self._contributions.items(): + profile = contrib.profile + x = profile.x + yobs = profile.y + ycalc = profile.ycalc + show_fit = plot_params["show_fit"] + show_diff = plot_params["show_diff"] + if ycalc is None: + if show_fit or show_diff: + print( + f"Contribution '{name}' has no calculated values " + "(ycalc is None). " + "Only observed data will be plotted." + ) + show_fit = False + show_diff = False + else: + diff = yobs - ycalc + y_min = min(yobs.min(), ycalc.min()) + y_max = max(yobs.max(), ycalc.max()) + y_range = y_max - y_min + base_offset = y_min - 0.1 * y_range + offset = base_offset * plot_params["offset_scale"] + if ax is None: + fig = plt.figure(figsize=plot_params["figsize"]) + current_ax = fig.add_subplot(111) + else: + current_ax = ax + fig = current_ax.figure + if plot_params["show_observed"]: + current_ax.plot( + x, + yobs, + plot_params["data_style"], + label=self._format_plot_label( + plot_params["data_label"], name, shared_axes + ), + color=plot_params["data_color"], + markersize=plot_params["markersize"], + alpha=plot_params["alpha"], ) - show_fit = False - show_diff = False - else: - diff = yobs - ycalc - y_min = min(yobs.min(), ycalc.min()) - y_max = max(yobs.max(), ycalc.max()) - y_range = y_max - y_min - base_offset = y_min - 0.1 * y_range - offset = base_offset * plot_params["offset_scale"] - if ax is None: - fig = plt.figure(figsize=plot_params["figsize"]) - current_ax = fig.add_subplot(111) - else: - current_ax = ax - fig = current_ax.figure - if plot_params["show_observed"]: - current_ax.plot( - x, - yobs, - plot_params["data_style"], - label=self._format_plot_label( - plot_params["data_label"], name, shared_axes - ), - color=plot_params["data_color"], - markersize=plot_params["markersize"], - alpha=plot_params["alpha"], - ) - if show_fit: - current_ax.plot( - x, - ycalc, - plot_params["fit_style"], - label=self._format_plot_label( - plot_params["fit_label"], name, shared_axes - ), - color=plot_params["fit_color"], - linewidth=plot_params["linewidth"], - alpha=plot_params["alpha"], - ) - if show_diff: - current_ax.plot( - x, - diff + offset, - plot_params["diff_style"], - label=self._format_plot_label( - plot_params["diff_label"], name, shared_axes - ), - color=plot_params["diff_color"], - linewidth=plot_params["linewidth"], - alpha=plot_params["alpha"], - ) - current_ax.axhline( - offset, - color="black", - ) - meta = getattr(profile, "meta", None) - if meta: - self._set_axes_labels_from_metadata(meta, plot_params) - if plot_params["xlabel"] is not None: - current_ax.set_xlabel(plot_params["xlabel"]) - if plot_params["ylabel"] is not None: - current_ax.set_ylabel(plot_params["ylabel"]) - if plot_params["title"] is not None: - current_ax.set_title( - self._format_plot_label(plot_params["title"], name, False) - ) - elif ax is None: - current_ax.set_title(name) - if plot_params["legend"]: - current_ax.legend(loc=plot_params["legend_loc"], frameon=True) - if plot_params["grid"]: - current_ax.grid(True) - if ( - plot_params["xmin"] is not None - or plot_params["xmax"] is not None - ): - current_ax.set_xlim( - left=plot_params["xmin"], right=plot_params["xmax"] - ) - fig.tight_layout() - figures.append(fig) - axes_list.append(current_ax) - if plot_params["show"] and ax is None: - plt.show() + if show_fit: + current_ax.plot( + x, + ycalc, + plot_params["fit_style"], + label=self._format_plot_label( + plot_params["fit_label"], name, shared_axes + ), + color=plot_params["fit_color"], + linewidth=plot_params["linewidth"], + alpha=plot_params["alpha"], + ) + if show_diff: + current_ax.plot( + x, + diff + offset, + plot_params["diff_style"], + label=self._format_plot_label( + plot_params["diff_label"], name, shared_axes + ), + color=plot_params["diff_color"], + linewidth=plot_params["linewidth"], + alpha=plot_params["alpha"], + ) + current_ax.axhline( + offset, + color="black", + ) + meta = getattr(profile, "meta", None) + if meta: + self._set_axes_labels_from_metadata(meta, plot_params) + if plot_params["xlabel"] is not None: + current_ax.set_xlabel(plot_params["xlabel"]) + if plot_params["ylabel"] is not None: + current_ax.set_ylabel(plot_params["ylabel"]) + if plot_params["title"] is not None: + current_ax.set_title( + self._format_plot_label( + plot_params["title"], name, False + ) + ) + elif ax is None: + current_ax.set_title(name) + if plot_params["legend"]: + current_ax.legend( + loc=plot_params["legend_loc"], frameon=True + ) + if plot_params["grid"]: + current_ax.grid(True) + if ( + plot_params["xmin"] is not None + or plot_params["xmax"] is not None + ): + current_ax.set_xlim( + left=plot_params["xmin"], right=plot_params["xmax"] + ) + fig.tight_layout() + figures.append(fig) + axes_list.append(current_ax) + if plot_params["show"] and ax is None: + plt.show() if return_fig: if len(figures) == 1: return figures[0], axes_list[0] diff --git a/tests/conftest.py b/tests/conftest.py index 41d9146e..45687a21 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ from numpy import linspace, pi, sin import diffpy.srfit.equation.literals as literals -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile +from diffpy.srfit import FitContribution, FitRecipe, Profile logger = logging.getLogger(__name__) diff --git a/tests/test_contribution.py b/tests/test_contribution.py index 6c2f3059..eb63cd10 100644 --- a/tests/test_contribution.py +++ b/tests/test_contribution.py @@ -20,11 +20,9 @@ import pytest from numpy import arange, array_equal, dot, sin +from diffpy.srfit import FitContribution, Profile, ProfileGenerator from diffpy.srfit.exceptions import SrFitError -from diffpy.srfit.fitbase.fitcontribution import FitContribution from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.fitbase.profile import Profile -from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator class TestContribution(unittest.TestCase): diff --git a/tests/test_fitrecipe.py b/tests/test_fitrecipe.py index 228ae928..cbb92284 100644 --- a/tests/test_fitrecipe.py +++ b/tests/test_fitrecipe.py @@ -20,15 +20,18 @@ import matplotlib.pyplot as plt import numpy as np import pytest +from bg_mpl_stylesheets.styles import all_styles from numpy import array_equal, dot, linspace, ones_like, pi, sin from scipy.optimize import leastsq -from diffpy.srfit.fitbase import FitResults -from diffpy.srfit.fitbase.fitcontribution import FitContribution -from diffpy.srfit.fitbase.fitrecipe import FitRecipe +from diffpy.srfit import ( + FitContribution, + FitRecipe, + FitResults, + Profile, + ProfileParser, +) from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.fitbase.profile import Profile -from diffpy.srfit.fitbase.profileparser import ProfileParser matplotlib.use("Agg") @@ -686,6 +689,24 @@ def test_plot_recipe_after_refinement(build_recipes_one_contribution): assert len(new_figs) == 1 +def test_plot_recipe_styles_only_its_own_figure( + build_recipes_one_contribution, +): + # C1: A refined recipe is plotted. + # Expected: The srfit figure uses the bg-style stylesheet, and the + # global matplotlib style is the same after plotting as before. + recipe, _ = build_recipes_one_contribution + optimize_recipe(recipe) + plt.close("all") + expected_global_linewidth = plt.rcParams["axes.linewidth"] + _, ax = recipe.plot_recipe(show=False, return_fig=True) + actual_figure_linewidth = ax.spines["left"].get_linewidth() + expected_figure_linewidth = all_styles["bg-style"]["axes.linewidth"] + actual_global_linewidth = plt.rcParams["axes.linewidth"] + assert actual_figure_linewidth == expected_figure_linewidth + assert actual_global_linewidth == expected_global_linewidth + + def test_plot_recipe_two_contributions(build_recipe_two_contributions): # Case: Two contributions in recipe # expected: two figures created diff --git a/tests/test_fitresults.py b/tests/test_fitresults.py index d9be7317..ca52192d 100644 --- a/tests/test_fitresults.py +++ b/tests/test_fitresults.py @@ -20,12 +20,8 @@ import pytest from scipy.optimize import leastsq -from diffpy.srfit.fitbase.fitrecipe import FitRecipe -from diffpy.srfit.fitbase.fitresults import ( - FitResults, - initializeRecipe, - resultsDictionary, -) +from diffpy.srfit import FitRecipe, FitResults +from diffpy.srfit.fitbase.fitresults import initializeRecipe, resultsDictionary # The fit results from the recipe fixture in conftest.py expected_fitresults = """\ diff --git a/tests/test_profile.py b/tests/test_profile.py index 59ab81d1..3d7ca91b 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -21,9 +21,8 @@ import pytest from numpy import allclose, arange, array, array_equal, ones_like +from diffpy.srfit import FitContribution, Profile, ProfileParser from diffpy.srfit.exceptions import SrFitError -from diffpy.srfit.fitbase import FitContribution, ProfileParser -from diffpy.srfit.fitbase.profile import Profile class TestProfile(unittest.TestCase): diff --git a/tests/test_profilegenerator.py b/tests/test_profilegenerator.py index e4ea0fa3..02dd317c 100644 --- a/tests/test_profilegenerator.py +++ b/tests/test_profilegenerator.py @@ -19,8 +19,7 @@ from numpy import arange, array_equal -from diffpy.srfit.fitbase.profile import Profile -from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator +from diffpy.srfit import Profile, ProfileGenerator class TestProfileGenerator(unittest.TestCase): diff --git a/tests/test_profileparser.py b/tests/test_profileparser.py index 008482bb..7930c8a5 100644 --- a/tests/test_profileparser.py +++ b/tests/test_profileparser.py @@ -3,8 +3,8 @@ import pytest +from diffpy.srfit import ProfileParser from diffpy.srfit.exceptions import ParseError -from diffpy.srfit.fitbase.profileparser import ProfileParser EXPECTED_META = { "wavelength": 0.1, diff --git a/tests/test_recipeorganizer.py b/tests/test_recipeorganizer.py index 26597a0b..36b4288a 100644 --- a/tests/test_recipeorganizer.py +++ b/tests/test_recipeorganizer.py @@ -20,8 +20,8 @@ import numpy import pytest +from diffpy.srfit import Calculator from diffpy.srfit.equation.builder import EquationFactory -from diffpy.srfit.fitbase.calculator import Calculator from diffpy.srfit.fitbase.parameter import Parameter from diffpy.srfit.fitbase.recipeorganizer import ( RecipeContainer, diff --git a/tests/test_weakrefcallable.py b/tests/test_weakrefcallable.py index e27c029a..947fc8d8 100644 --- a/tests/test_weakrefcallable.py +++ b/tests/test_weakrefcallable.py @@ -17,7 +17,7 @@ import pickle import unittest -from diffpy.srfit.fitbase import FitContribution +from diffpy.srfit import FitContribution from diffpy.srfit.fitbase.parameter import Parameter from diffpy.srfit.util.weakrefcallable import WeakBoundMethod, weak_ref