diff --git a/docs/examples/coreshellnp.py b/docs/examples/coreshellnp.py index fd54a50f..34f12e72 100644 --- a/docs/examples/coreshellnp.py +++ b/docs/examples/coreshellnp.py @@ -20,7 +20,8 @@ different phases, each with an appropriate characteristic function. """ -import numpy +from pathlib import Path + from pyobjcryst import loadCrystal from scipy.optimize import leastsq @@ -134,35 +135,18 @@ def makeRecipe(stru1, stru2, datname): return recipe -def plotResults(recipe): - """Plot the results contained within a refined FitRecipe.""" - # All this should be pretty familiar by now. - r = recipe.cdszns.profile.x - g = recipe.cdszns.profile.y - gcalc = recipe.cdszns.profile.ycalc - diffzero = -0.8 * max(g) * numpy.ones_like(g) - diff = g - gcalc + diffzero - - import pylab - - pylab.plot(r, g, "bo", label="G(r) Data") - pylab.plot(r, gcalc, "r-", label="G(r) Fit") - pylab.plot(r, diff, "g-", label="G(r) diff") - pylab.plot(r, diffzero, "k-") - pylab.xlabel(r"$r (\AA)$") - pylab.ylabel(r"$G (\AA^{-2})$") - pylab.legend(loc=1) - - pylab.show() - return +plot_styles = { + "xlabel": r"$r (\AA)$", + "ylabel": r"$G (\AA^{-2})$", +} def main(): """Set up and refine the recipe.""" # Make the data and the recipe - cdsciffile = "data/CdS.cif" - znsciffile = "data/ZnS.cif" - data = "data/CdS_ZnS_nano.gr" + cdsciffile = Path(__file__).parent / "data/CdS.cif" + znsciffile = Path(__file__).parent / "data/ZnS.cif" + data = Path(__file__).parent / "data/CdS_ZnS_nano.gr" # Make the recipe stru1 = loadCrystal(cdsciffile) @@ -206,7 +190,7 @@ def main(): res.print_results() # Plot! - plotResults(recipe) + recipe.plot_recipe(**plot_styles) return diff --git a/docs/examples/crystalpdf.py b/docs/examples/crystalpdf.py index bb6ad3cf..a5d4e61e 100644 --- a/docs/examples/crystalpdf.py +++ b/docs/examples/crystalpdf.py @@ -24,7 +24,8 @@ demonstrates only the basic configuration. """ -import numpy +from pathlib import Path + from gaussianrecipe import scipyOptimize from diffpy.srfit.fitbase import ( @@ -67,7 +68,7 @@ def makeRecipe(ciffile, datname): # Qmax value, as well as initial values for the non-structural Parameters. generator = PDFGenerator("G") stru = Structure() - stru.read(ciffile) + stru.read(str(ciffile)) generator.setStructure(stru) # The FitContribution @@ -129,34 +130,17 @@ def makeRecipe(ciffile, datname): return recipe -def plotResults(recipe): - """Plot the results contained within a refined FitRecipe.""" - # All this should be pretty familiar by now. - r = recipe.nickel.profile.x - g = recipe.nickel.profile.y - gcalc = recipe.nickel.profile.ycalc - diffzero = -0.8 * max(g) * numpy.ones_like(g) - diff = g - gcalc + diffzero - - import pylab - - pylab.plot(r, g, "bo", label="G(r) Data") - pylab.plot(r, gcalc, "r-", label="G(r) Fit") - pylab.plot(r, diff, "g-", label="G(r) diff") - pylab.plot(r, diffzero, "k-") - pylab.xlabel(r"$r (\AA)$") - pylab.ylabel(r"$G (\AA^{-2})$") - pylab.legend(loc=1) - - pylab.show() - return +plot_styles = { + "xlabel": r"$r (\AA)$", + "ylabel": r"$G (\AA^{-2})$", +} if __name__ == "__main__": # Make the data and the recipe - ciffile = "data/ni.cif" - data = "data/ni-q27r100-neutron.gr" + ciffile = Path(__file__).parent / "data/ni.cif" + data = Path(__file__).parent / "data/ni-q27r100-neutron.gr" # Make the recipe recipe = makeRecipe(ciffile, data) @@ -170,6 +154,6 @@ def plotResults(recipe): res.print_results() # Plot! - plotResults(recipe) + recipe.plot_recipe(**plot_styles) # End of file diff --git a/docs/examples/crystalpdfall.py b/docs/examples/crystalpdfall.py index dc2da591..e851b050 100644 --- a/docs/examples/crystalpdfall.py +++ b/docs/examples/crystalpdfall.py @@ -18,7 +18,8 @@ structure to all the available data. """ -import numpy +from pathlib import Path + from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal @@ -143,82 +144,20 @@ def makeRecipe( return recipe -def plotResults(recipe): - """Plot the results contained within a refined FitRecipe.""" - # All this should be pretty familiar by now. - xnickel = recipe.xnickel - xr_ni = xnickel.profile.x - xg_ni = xnickel.profile.y - xgcalc_ni = xnickel.profile.ycalc - xdiffzero_ni = -0.8 * max(xg_ni) * numpy.ones_like(xg_ni) - xdiff_ni = xg_ni - xgcalc_ni + xdiffzero_ni - - xsilicon = recipe.xsilicon - xr_si = xsilicon.profile.x - xg_si = xsilicon.profile.y - xgcalc_si = xsilicon.profile.ycalc - xdiffzero_si = -0.8 * max(xg_si) * numpy.ones_like(xg_si) - xdiff_si = xg_si - xgcalc_si + xdiffzero_si - - nnickel = recipe.nnickel - nr_ni = nnickel.profile.x - ng_ni = nnickel.profile.y - ngcalc_ni = nnickel.profile.ycalc - ndiffzero_ni = -0.8 * max(ng_ni) * numpy.ones_like(ng_ni) - ndiff_ni = ng_ni - ngcalc_ni + ndiffzero_ni - - xsini = recipe.xsini - xr_sini = xsini.profile.x - xg_sini = xsini.profile.y - xgcalc_sini = xsini.profile.ycalc - xdiffzero_sini = -0.8 * max(xg_sini) * numpy.ones_like(xg_sini) - xdiff_sini = xg_sini - xgcalc_sini + xdiffzero_sini - - import pylab - - pylab.subplot(2, 2, 1) - pylab.plot(xr_ni, xg_ni, "bo", label="G(r) x-ray nickel Data") - pylab.plot(xr_ni, xgcalc_ni, "r-", label="G(r) x-ray nickel Fit") - pylab.plot(xr_ni, xdiff_ni, "g-", label="G(r) x-ray nickel diff") - pylab.plot(xr_ni, xdiffzero_ni, "k-") - pylab.xlabel(r"$r (\AA)$") - pylab.ylabel(r"$G (\AA^{-2})$") - pylab.legend(loc=1) - - pylab.subplot(2, 2, 2) - pylab.plot(xr_si, xg_si, "bo", label="G(r) x-ray silicon Data") - pylab.plot(xr_si, xgcalc_si, "r-", label="G(r) x-ray silicon Fit") - pylab.plot(xr_si, xdiff_si, "g-", label="G(r) x-ray silicon diff") - pylab.plot(xr_si, xdiffzero_si, "k-") - pylab.legend(loc=1) - - pylab.subplot(2, 2, 3) - pylab.plot(nr_ni, ng_ni, "bo", label="G(r) neutron nickel Data") - pylab.plot(nr_ni, ngcalc_ni, "r-", label="G(r) neutron nickel Fit") - pylab.plot(nr_ni, ndiff_ni, "g-", label="G(r) neutron nickel diff") - pylab.plot(nr_ni, ndiffzero_ni, "k-") - pylab.legend(loc=1) - - pylab.subplot(2, 2, 4) - pylab.plot(xr_sini, xg_sini, "bo", label="G(r) x-ray sini Data") - pylab.plot(xr_sini, xgcalc_sini, "r-", label="G(r) x-ray sini Fit") - pylab.plot(xr_sini, xdiff_sini, "g-", label="G(r) x-ray sini diff") - pylab.plot(xr_sini, xdiffzero_sini, "k-") - pylab.legend(loc=1) - - pylab.show() - return - +plot_styles = { + "xlabel": r"$r (\AA)$", + "ylabel": r"$G (\AA^{-2})$", +} if __name__ == "__main__": # Make the data and the recipe - ciffile_ni = "data/ni.cif" - ciffile_si = "data/si.cif" - xdata_ni = "data/ni-q27r60-xray.gr" - ndata_ni = "data/ni-q27r100-neutron.gr" - xdata_si = "data/si-q27r60-xray.gr" - xdata_sini = "data/si90ni10-q27r60-xray.gr" + ciffile_ni = Path(__file__).parent / "data/ni.cif" + ciffile_si = Path(__file__).parent / "data/si.cif" + xdata_ni = Path(__file__).parent / "data/ni-q27r60-xray.gr" + ndata_ni = Path(__file__).parent / "data/ni-q27r100-neutron.gr" + xdata_si = Path(__file__).parent / "data/si-q27r60-xray.gr" + xdata_sini = Path(__file__).parent / "data/si90ni10-q27r60-xray.gr" # Make the recipe recipe = makeRecipe( @@ -232,7 +171,9 @@ def plotResults(recipe): res = FitResults(recipe) res.print_results() - # Plot! - plotResults(recipe) + # Plot! The recipe has four contributions ("xnickel", "xsilicon", + # "nnickel", "xsini"), so plot_recipe produces one figure per + # contribution. + recipe.plot_recipe(**plot_styles) # End of file diff --git a/docs/examples/crystalpdfobjcryst.py b/docs/examples/crystalpdfobjcryst.py index 0d427d04..3fd889f6 100644 --- a/docs/examples/crystalpdfobjcryst.py +++ b/docs/examples/crystalpdfobjcryst.py @@ -19,7 +19,8 @@ provided by the ObjCrystCrystalParSet structure adapter. """ -from crystalpdf import plotResults +from pathlib import Path + from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal @@ -90,9 +91,6 @@ def makeRecipe(ciffile, datname): # things by iterating through all the sgpars. for par in phase.sgpars: recipe.add_variable(par) - # set the initial thermal factor to a non-zero value - assert hasattr(recipe, "B11_0") - recipe.B11_0 = 0.1 # We now select non-structural parameters to refine. # This controls the scaling of the PDF. @@ -106,11 +104,17 @@ def makeRecipe(ciffile, datname): return recipe +plot_styles = { + "xlabel": r"$r (\AA)$", + "ylabel": r"$G (\AA^{-2})$", +} + + if __name__ == "__main__": # Make the data and the recipe - ciffile = "data/si.cif" - data = "data/si-q27r60-xray.gr" + ciffile = Path(__file__).parent / "data/si.cif" + data = Path(__file__).parent / "data/si-q27r60-xray.gr" # Make the recipe recipe = makeRecipe(ciffile, data) @@ -123,6 +127,6 @@ def makeRecipe(ciffile, datname): res.print_results() # Plot! - plotResults(recipe) + recipe.plot_recipe(**plot_styles) # End of file diff --git a/docs/examples/crystalpdftwodata.py b/docs/examples/crystalpdftwodata.py index 8c9eafe6..cb6b3091 100644 --- a/docs/examples/crystalpdftwodata.py +++ b/docs/examples/crystalpdftwodata.py @@ -20,7 +20,8 @@ underlying ObjCrystCrystalParSet. """ -import numpy +from pathlib import Path + from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal @@ -134,49 +135,18 @@ def makeRecipe(ciffile, xdatname, ndatname): return recipe -def plotResults(recipe): - """Plot the results contained within a refined FitRecipe.""" - # All this should be pretty familiar by now. - xr = recipe.xnickel.profile.x - xg = recipe.xnickel.profile.y - xgcalc = recipe.xnickel.profile.ycalc - xdiffzero = -0.8 * max(xg) * numpy.ones_like(xg) - xdiff = xg - xgcalc + xdiffzero - - nr = recipe.nnickel.profile.x - ng = recipe.nnickel.profile.y - ngcalc = recipe.nnickel.profile.ycalc - ndiffzero = -0.8 * max(ng) * numpy.ones_like(ng) - ndiff = ng - ngcalc + ndiffzero - - import pylab - - pylab.subplot(2, 1, 1) - pylab.plot(xr, xg, "bo", label="G(r) x-ray Data") - pylab.plot(xr, xgcalc, "r-", label="G(r) x-ray Fit") - pylab.plot(xr, xdiff, "g-", label="G(r) x-ray diff") - pylab.plot(xr, xdiffzero, "k-") - pylab.legend(loc=1) - - pylab.subplot(2, 1, 2) - pylab.plot(nr, ng, "bo", label="G(r) neutron Data") - pylab.plot(nr, ngcalc, "r-", label="G(r) neutron Fit") - pylab.plot(nr, ndiff, "g-", label="G(r) neutron diff") - pylab.plot(nr, ndiffzero, "k-") - pylab.xlabel(r"$r (\AA)$") - pylab.ylabel(r"$G (\AA^{-2})$") - pylab.legend(loc=1) - - pylab.show() - return +plot_styles = { + "xlabel": r"$r (\AA)$", + "ylabel": r"$G (\AA^{-2})$", +} if __name__ == "__main__": # Make the data and the recipe - ciffile = "data/ni.cif" - xdata = "data/ni-q27r60nodg-xray.gr" - ndata = "data/ni-q27r100-neutron.gr" + ciffile = Path(__file__).parent / "data/ni.cif" + xdata = Path(__file__).parent / "data/ni-q27r60nodg-xray.gr" + ndata = Path(__file__).parent / "data/ni-q27r100-neutron.gr" # Make the recipe recipe = makeRecipe(ciffile, xdata, ndata) @@ -188,7 +158,9 @@ def plotResults(recipe): res = FitResults(recipe) res.print_results() - # Plot! - plotResults(recipe) + # Plot! The recipe has two contributions ("xnickel" for x-ray, + # "nnickel" for neutron), so plot_recipe produces one figure per + # contribution. + recipe.plot_recipe(**plot_styles) # End of file diff --git a/docs/examples/crystalpdftwophase.py b/docs/examples/crystalpdftwophase.py index 9407d028..af6d1df7 100644 --- a/docs/examples/crystalpdftwophase.py +++ b/docs/examples/crystalpdftwophase.py @@ -20,7 +20,8 @@ nickel and silicon to find the structures and phase fractions. """ -import numpy +from pathlib import Path + from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal @@ -151,35 +152,18 @@ def makeRecipe(niciffile, siciffile, datname): return recipe -def plotResults(recipe): - """Plot the results contained within a refined FitRecipe.""" - # All this should be pretty familiar by now. - r = recipe.nisi.profile.x - g = recipe.nisi.profile.y - gcalc = recipe.nisi.profile.ycalc - diffzero = -0.8 * max(g) * numpy.ones_like(g) - diff = g - gcalc + diffzero - - import pylab - - pylab.plot(r, g, "bo", label="G(r) Data") - pylab.plot(r, gcalc, "r-", label="G(r) Fit") - pylab.plot(r, diff, "g-", label="G(r) diff") - pylab.plot(r, diffzero, "k-") - pylab.xlabel(r"$r (\AA)$") - pylab.ylabel(r"$G (\AA^{-2})$") - pylab.legend(loc=1) - - pylab.show() - return +plot_styles = { + "xlabel": r"$r (\AA)$", + "ylabel": r"$G (\AA^{-2})$", +} if __name__ == "__main__": # Make the data and the recipe - niciffile = "data/ni.cif" - siciffile = "data/si.cif" - data = "data/si90ni10-q27r60-xray.gr" + niciffile = Path(__file__).parent / "data/ni.cif" + siciffile = Path(__file__).parent / "data/si.cif" + data = Path(__file__).parent / "data/si90ni10-q27r60-xray.gr" # Make the recipe recipe = makeRecipe(niciffile, siciffile, data) @@ -192,6 +176,6 @@ def plotResults(recipe): res.print_results() # Plot! - plotResults(recipe) + recipe.plot_recipe(**plot_styles) # End of file diff --git a/docs/examples/debyemodel.py b/docs/examples/debyemodel.py index 549b9b09..1529d848 100644 --- a/docs/examples/debyemodel.py +++ b/docs/examples/debyemodel.py @@ -157,25 +157,19 @@ def makeRecipe(): return recipe -def plotResults(recipe): +def plot_results(recipe): """Plot the results contained within a refined FitRecipe.""" - # Plot this. # Note that since the contribution was given the name "pb", it is # accessible from the recipe with this name. This is a useful way to # organize multiple contributions to a fit. - T = recipe.pb.profile.x - U = recipe.pb.profile.y - Ucalc = recipe.pb.profile.ycalc - - import pylab - - pylab.plot(T, U, "o", label="Pb $U_{iso}$ Data") - pylab.plot(T, Ucalc) - pylab.xlabel("T (K)") - pylab.ylabel(r"$U_{iso} (\AA^2)$") - pylab.legend(loc=(0.0, 0.8)) - - pylab.show() + recipe.plot_recipe( + show_diff=False, + data_label=r"Pb $U_{iso}$ Data", + fit_label="Calculated", + xlabel="T (K)", + ylabel=r"$U_{iso} (\AA^2)$", + legend_loc=(0.0, 0.8), + ) return @@ -194,7 +188,7 @@ def main(): res.print_results() # Plot the results - plotResults(recipe) + plot_results(recipe) return diff --git a/docs/examples/debyemodelII.py b/docs/examples/debyemodelII.py index 400daa6f..c75d9cf7 100644 --- a/docs/examples/debyemodelII.py +++ b/docs/examples/debyemodelII.py @@ -93,43 +93,36 @@ def makeRecipeII(): return recipe -def plotResults(recipe): +def plot_results(recipe): """Display the results contained within a refined FitRecipe.""" # The variable values are returned in the order in which the variables were # added to the FitRecipe. lowToffset, highToffset, thetaD = recipe.get_values() + print( + r"lowT: $T_d$=%3.1f K, offset=%1.5f $\AA^2$" + % (abs(thetaD), lowToffset) + ) + print( + r"highT: $T_d$=%3.1f K, offset=%1.5f $\AA^2$" + % (abs(thetaD), highToffset) + ) # We want to extend the fitting range to its full extent so we can get a - # nice full plot. + # nice full plot. Since the calculated profile is only valid for the + # calculation range that was used during the fit, we need to trigger a + # recalculation over the widened range before plotting. recipe.lowT.profile.set_calculation_range(xmin="obs", xmax="obs") recipe.highT.profile.set_calculation_range(xmin="obs", xmax="obs") - T = recipe.lowT.profile.x - U = recipe.lowT.profile.y - # We can use a FitContribution's 'evaluate_equation' method to evaluate - # expressions involving the Parameters and other aspects of the - # FitContribution. Here we evaluate the fitting equation, which is always - # accessed using the name "eq". We access it this way (rather than through - # the Profile's ycalc attribute) because we changed the calculation range - # above, and we therefore need to recalculate the profile. - lowU = recipe.lowT.evaluate_equation("eq") - highU = recipe.highT.evaluate_equation("eq") - - # Now we can plot this. - import pylab - - pylab.plot(T, U, "o", label="Pb $U_{iso}$ Data") - lbl1 = r"$T_d$=%3.1f K, lowToff=%1.5f $\AA^2$" % (abs(thetaD), lowToffset) - lbl2 = r"$T_d$=%3.1f K, highToff=%1.5f $\AA^2$" % ( - abs(thetaD), - highToffset, + recipe.residual() + + recipe.plot_recipe( + show_diff=False, + data_label=r"Pb $U_{iso}$ Data", + fit_label="Calculated", + xlabel="T (K)", + ylabel=r"$U_{iso} (\AA^2)$", + legend_loc=(0.0, 0.8), ) - pylab.plot(T, lowU, label=lbl1) - pylab.plot(T, highU, label=lbl2) - pylab.xlabel("T (K)") - pylab.ylabel(r"$U_{iso} (\AA^2)$") - pylab.legend(loc=(0.0, 0.8)) - - pylab.show() return @@ -148,7 +141,7 @@ def main(): res.print_results() # Plot the results - plotResults(recipe) + plot_results(recipe) return diff --git a/docs/examples/ellipsoidsas.py b/docs/examples/ellipsoidsas.py index b1fa1aa3..ad9a2c86 100644 --- a/docs/examples/ellipsoidsas.py +++ b/docs/examples/ellipsoidsas.py @@ -14,6 +14,9 @@ ######################################################################## """Example of a refinement of SAS I(Q) data to an ellipsoidal model.""" +from pathlib import Path + +import matplotlib.pyplot as plt from gaussianrecipe import scipyOptimize from diffpy.srfit.fitbase import ( @@ -86,31 +89,35 @@ def makeRecipe(datname): return recipe -def plotResults(recipe): +def plot_results(recipe): """Plot the results contained within a refined FitRecipe.""" - # All this should be pretty familiar by now. - r = recipe.ellipsoid.profile.x - y = recipe.ellipsoid.profile.y - ycalc = recipe.ellipsoid.profile.ycalc - diff = y - ycalc + min(y) - - import pylab - - pylab.loglog(r, y, "bo", label="I(Q) Data") - pylab.loglog(r, ycalc, "r-", label="I(Q) Fit") - pylab.loglog(r, diff, "g-", label="I(Q) diff") - pylab.xlabel(r"$Q (\AA^{-1})$") - pylab.ylabel("$I (arb. units)$") - pylab.legend(loc=1) - - pylab.show() + # I(Q) SAS data is best viewed on a log-log scale, so we prepare the + # axes ourselves and hand them to plot_recipe. The residual difference + # curve is not shown since it can go negative and is not meaningful on + # a log scale. + fig = plt.figure() + ax = fig.add_subplot(111) + ax.set_xscale("log") + ax.set_yscale("log") + recipe.plot_recipe( + ax=ax, + show=False, + show_diff=False, + data_color="b", + fit_color="r", + data_label="I(Q) Data", + fit_label="I(Q) Fit", + xlabel=r"$Q (\AA^{-1})$", + ylabel="$I (arb. units)$", + ) + plt.show() return if __name__ == "__main__": # Make the data and the recipe - data = "data/sas_ellipsoid_testdata.txt" + data = Path(__file__).parent / "data/sas_ellipsoid_testdata.txt" # Make the recipe recipe = makeRecipe(data) @@ -123,6 +130,6 @@ def plotResults(recipe): res.print_results() # Plot! - plotResults(recipe) + plot_results(recipe) # End of file diff --git a/docs/examples/gaussiangenerator.py b/docs/examples/gaussiangenerator.py index fc41c6fb..b7d3e007 100644 --- a/docs/examples/gaussiangenerator.py +++ b/docs/examples/gaussiangenerator.py @@ -39,6 +39,8 @@ GaussianGenerator will be accessible by its name, "g". """ +from pathlib import Path + from numpy import exp from diffpy.srfit.fitbase import ( @@ -131,7 +133,7 @@ def makeRecipe(): # Load data and add it to the profile. This uses the loadtxt function from # numpy. - profile.loadtxt("data/gaussian.dat") + profile.loadtxt(Path(__file__).parent / "data/gaussian.dat") # The ProfileGenerator # Create a GaussianGenerator named "g". This will be the name we use to diff --git a/docs/examples/gaussianrecipe.py b/docs/examples/gaussianrecipe.py index 93e3ff60..61f00384 100644 --- a/docs/examples/gaussianrecipe.py +++ b/docs/examples/gaussianrecipe.py @@ -26,8 +26,8 @@ to get an understanding of how a fit recipe can be used once created. After that, read the 'makeRecipe' code to see what goes into a fit recipe. After that, read the 'scipyOptimize' code to see how the refinement is executed. -Finally, read the 'plotResults' code to see how to extracts the refined profile -and plot it. +Finally, look at the 'plot_styles' dict and the 'recipe.plot_recipe' call +to see how the refined profile is plotted. Extensions @@ -45,6 +45,8 @@ from __future__ import print_function +from pathlib import Path + from diffpy.srfit.fitbase import ( FitContribution, FitRecipe, @@ -75,7 +77,7 @@ def main(): res.print_results() # Plot the results. - plotResults(recipe) + recipe.plot_recipe(**plot_styles) return @@ -100,7 +102,7 @@ def makeRecipe(): # Load data and add it to the profile. This uses the loadtxt function from # numpy. - profile.loadtxt("data/gaussian.dat") + profile.loadtxt(Path(__file__).parent / "data/gaussian.dat") # The FitContribution # The FitContribution associates the Profile with a fitting equation. The @@ -178,29 +180,10 @@ def scipyOptimize(recipe): return -def plotResults(recipe): - """Plot the results contained within a refined FitRecipe.""" - # We can access the data and fit profile through the Profile we created - # above. We get to it through our FitContribution, which we named "g1". - # - # The independent variable. This is always under the "x" attribute. - x = recipe.g1.profile.x - # The observed profile that we loaded earlier, the "y" attribute. - y = recipe.g1.profile.y - # The calculated profile, the "ycalc" attribute. - ycalc = recipe.g1.profile.ycalc - - # This stuff is specific to pylab from the matplotlib distribution. - import pylab - - pylab.plot(x, y, "b.", label="observed Gaussian") - pylab.plot(x, ycalc, "g-", label="calculated Gaussian") - pylab.legend(loc=(0.0, 0.8)) - pylab.xlabel("x") - pylab.ylabel("y") - - pylab.show() - return +plot_styles = { + "xlabel": "x", + "ylabel": "y", +} if __name__ == "__main__": diff --git a/docs/examples/interface.py b/docs/examples/interface.py index 03bb498a..96466808 100644 --- a/docs/examples/interface.py +++ b/docs/examples/interface.py @@ -18,6 +18,8 @@ defined in the diffpy.srfit.interface.interface.py module. """ +from pathlib import Path + from diffpy.srfit.fitbase import ( FitContribution, FitRecipe, @@ -32,7 +34,7 @@ def main(): p = Profile() - p.loadtxt("data/gaussian.dat") + p.loadtxt(Path(__file__).parent / "data/gaussian.dat") # FitContribution operations # "<<" - Inject a parameter value @@ -65,9 +67,11 @@ def main(): # Print the results. res.print_results() # Plot the results. - from gaussianrecipe import plotResults - - plotResults(r) + plot_styles = { + "xlabel": "x", + "ylabel": "y", + } + r.plot_recipe(**plot_styles) return diff --git a/docs/examples/npintensity.py b/docs/examples/npintensity.py index 586683bc..09a0cb26 100644 --- a/docs/examples/npintensity.py +++ b/docs/examples/npintensity.py @@ -43,6 +43,9 @@ from __future__ import print_function +from pathlib import Path + +import matplotlib.pyplot as plt import numpy from gaussianrecipe import scipyOptimize @@ -141,7 +144,7 @@ def setStructure(self, strufile): from diffpy.structure import Structure stru = Structure() - stru.read(strufile) + stru.read(str(strufile)) # Create a ParameterSet designed to interface with # diffpy.structure.Structure objects that organizes the Parameter @@ -306,7 +309,7 @@ def gaussian(q, q0, width): def main(): # Make the data and the recipe - strufile = "data/C60.stru" + strufile = Path(__file__).parent / "data/C60.stru" q = numpy.arange(1, 20, 0.05) makeData(strufile, q, "C60.iq", 1.0, 100.68, 0.005, 0.13, 2) @@ -328,32 +331,34 @@ def main(): res.print_results(footer=footer) # Plot! - plotResults(recipe) + plot_results(recipe) return -def plotResults(recipe): +def plot_results(recipe): """Plot the results contained within a refined FitRecipe.""" - # All this should be pretty familiar by now. + # The background is not part of the standard observed/fit/diff plot + # that plot_recipe produces, so we overlay it afterwards. q = recipe.bucky.profile.x - - Imeas = recipe.bucky.profile.y - Icalc = recipe.bucky.profile.ycalc bkgd = recipe.bucky.evaluate_equation("bkgd") - diff = Imeas - Icalc - - import pylab - - pylab.plot(q, Imeas, "ob", label="I(Q) Data") - pylab.plot(q, Icalc, "r-", label="I(Q) Fit") - pylab.plot(q, diff, "g-", label="I(Q) diff") - pylab.plot(q, bkgd, "c-", label="Bkgd. Fit") - pylab.xlabel(r"$Q (\AA^{-1})$") - pylab.ylabel("Intensity (arb. units)") - pylab.legend(loc=1) - pylab.show() + fig, ax = recipe.plot_recipe( + show=False, + return_fig=True, + data_color="b", + fit_color="r", + diff_color="g", + data_label="I(Q) Data", + fit_label="I(Q) Fit", + diff_label="I(Q) diff", + xlabel=r"$Q (\AA^{-1})$", + ylabel="Intensity (arb. units)", + ) + ax.plot(q, bkgd, "c-", label="Bkgd. Fit") + ax.legend(loc=1) + + plt.show() return @@ -485,7 +490,7 @@ def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl=1): from diffpy.structure import Structure S = Structure() - S.read(strufile) + S.read(str(strufile)) # Set the lattice parameters S.lattice.setLatPar(a, a, a) diff --git a/docs/examples/npintensityII.py b/docs/examples/npintensityII.py index ff0d6bcd..62b3e8c7 100644 --- a/docs/examples/npintensityII.py +++ b/docs/examples/npintensityII.py @@ -34,6 +34,9 @@ first step towards writing a user interface. """ +from pathlib import Path + +import matplotlib.pyplot as plt import numpy from gaussianrecipe import scipyOptimize from npintensity import IntensityGenerator, makeData @@ -186,45 +189,34 @@ def gaussian(q, q0, width): return recipe -def plotResults(recipe): - """Plot the results contained within a refined FitRecipe.""" - # plotting song and dance - q = recipe.bucky1.profile.x +def plot_results(recipe): + """Plot the results contained within a refined FitRecipe. - # Plot this for fun. - I1 = recipe.bucky1.profile.y - Icalc1 = recipe.bucky1.profile.ycalc + The recipe has two contributions ("bucky1" and "bucky2"), so + plot_recipe produces one figure per contribution. The backgrounds + are not part of the standard observed/fit/diff plot, so they are + overlaid on each figure afterwards. + """ + q = recipe.bucky1.profile.x bkgd1 = recipe.bucky1.evaluate_equation("bkgd") - diff1 = I1 - Icalc1 - I2 = recipe.bucky2.profile.y - Icalc2 = recipe.bucky2.profile.ycalc bkgd2 = recipe.bucky2.evaluate_equation("bkgd") - diff2 = I2 - Icalc2 - offset = 1.2 * max(I2) * numpy.ones_like(I2) - I1 += offset - Icalc1 += offset - bkgd1 += offset - diff1 += offset - - import pylab - - pylab.subplot(2, 1, 1) - pylab.plot(q, I1, "bo", label="I1(Q) Data") - pylab.plot(q, Icalc1, "r-", label="I1(Q) Fit") - pylab.plot(q, diff1, "g-", label="I1(Q) diff") - pylab.plot(q, bkgd1, "c-", label="Bkgd1 Fit") - pylab.legend(loc=1) - - pylab.subplot(2, 1, 2) - pylab.plot(q, I2, "bo", label="I2(Q) Data") - pylab.plot(q, Icalc2, "r-", label="I2(Q) Fit") - pylab.plot(q, diff2, "g-", label="I2(Q) diff") - pylab.plot(q, bkgd2, "c-", label="Bkgd2 Fit") - pylab.xlabel(r"$Q (\AA^{-1})$") - pylab.ylabel("Intensity (arb. units)") - pylab.legend(loc=1) - - pylab.show() + + figs, axes = recipe.plot_recipe( + show=False, + return_fig=True, + data_label="I(Q) Data", + fit_label="I(Q) Fit", + diff_label="I(Q) diff", + xlabel=r"$Q (\AA^{-1})$", + ylabel="Intensity (arb. units)", + ) + # "bucky1" was added to the recipe first, so its axes come first. + axes[0].plot(q, bkgd1, "c-", label="Bkgd1 Fit") + axes[0].legend(loc=1) + axes[1].plot(q, bkgd2, "c-", label="Bkgd2 Fit") + axes[1].legend(loc=1) + + plt.show() return @@ -232,7 +224,7 @@ def main(): # Make two different data sets, each from the same structure, but with # different scale, noise, broadening and background. - strufile = "data/C60.stru" + strufile = Path(__file__).parent / "data/C60.stru" q = numpy.arange(1, 20, 0.05) makeData(strufile, q, "C60_1.iq", 8.1, 101.68, 0.008, 0.12, 2, 0.01) makeData(strufile, q, "C60_2.iq", 3.2, 101.68, 0.02, 0.003, 0, 1) @@ -266,7 +258,7 @@ def main(): res.print_results() # Plot! - plotResults(recipe) + plot_results(recipe) return diff --git a/docs/examples/nppdfcrystal.py b/docs/examples/nppdfcrystal.py index 033a7162..a07be4e4 100644 --- a/docs/examples/nppdfcrystal.py +++ b/docs/examples/nppdfcrystal.py @@ -23,7 +23,9 @@ diffpy.srfit.pdf.characteristicfunctions module. """ -import numpy +from pathlib import Path + +import matplotlib.pyplot as plt from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal @@ -80,41 +82,37 @@ def makeRecipe(ciffile, grdata): return recipe -def plotResults(recipe): +def plot_results(recipe): """Plot the results contained within a refined FitRecipe.""" - # All this should be pretty familiar by now. r = recipe.pdf.profile.x g = recipe.pdf.profile.y - gcalc = recipe.pdf.profile.ycalc - diffzero = -0.8 * max(g) * numpy.ones_like(g) - diff = g - gcalc + diffzero + # These two curves are not part of the standard observed/fit/diff plot + # that plot_recipe produces, so we overlay them afterwards. gcryst = recipe.pdf.evaluate_equation("G") gcryst /= recipe.scale.value fr = recipe.pdf.evaluate_equation("f") fr *= max(g) / fr[0] - import pylab - - pylab.plot(r, g, "bo", label="G(r) Data") - pylab.plot(r, gcryst, "y--", label="G(r) Crystal") - pylab.plot(r, fr, "k--", label="f(r) calculated (scaled)") - pylab.plot(r, gcalc, "r-", label="G(r) Fit") - pylab.plot(r, diff, "g-", label="G(r) diff") - pylab.plot(r, diffzero, "k-") - pylab.xlabel(r"$r (\AA)$") - pylab.ylabel(r"$G (\AA^{-2})$") - pylab.legend(loc=1) - - pylab.show() + fig, ax = recipe.plot_recipe( + show=False, + return_fig=True, + xlabel=r"$r (\AA)$", + ylabel=r"$G (\AA^{-2})$", + ) + ax.plot(r, gcryst, "y--", label="G(r) Crystal") + ax.plot(r, fr, "k--", label="f(r) calculated (scaled)") + ax.legend(loc=1) + + plt.show() return if __name__ == "__main__": - ciffile = "data/pb.cif" - grdata = "data/pb_100_qmin1.gr" + ciffile = Path(__file__).parent / "data/pb.cif" + grdata = Path(__file__).parent / "data/pb_100_qmin1.gr" recipe = makeRecipe(ciffile, grdata) scipyOptimize(recipe) @@ -122,6 +120,6 @@ def plotResults(recipe): res = FitResults(recipe) res.print_results() - plotResults(recipe) + plot_results(recipe) # End of file diff --git a/docs/examples/nppdfobjcryst.py b/docs/examples/nppdfobjcryst.py index 6c702172..8367d1ca 100644 --- a/docs/examples/nppdfobjcryst.py +++ b/docs/examples/nppdfobjcryst.py @@ -18,7 +18,7 @@ the DebyePDFGenerator from SrReal to refine a pyobjcryst Molecule. """ -import numpy +from pathlib import Path from diffpy.srfit.fitbase import ( FitContribution, @@ -107,34 +107,17 @@ def makeRecipe(molecule, datname): return recipe -def plotResults(recipe): - """Plot the results contained within a refined FitRecipe.""" - # Plot this. - r = recipe.bucky.profile.x - g = recipe.bucky.profile.y - gcalc = recipe.bucky.profile.ycalc - diffzero = -0.8 * max(g) * numpy.ones_like(g) - diff = g - gcalc + diffzero - - import pylab - - pylab.plot(r, g, "ob", label="G(r) Data") - pylab.plot(r, gcalc, "-r", label="G(r) Fit") - pylab.plot(r, diff, "-g", label="G(r) diff") - pylab.plot(r, diffzero, "-k") - pylab.xlabel(r"$r (\AA)$") - pylab.ylabel(r"$G (\AA^{-2})$") - pylab.legend(loc=1) - - pylab.show() - return +plot_styles = { + "xlabel": r"$r (\AA)$", + "ylabel": r"$G (\AA^{-2})$", +} def main(): molecule = makeC60() # Make the data and the recipe - recipe = makeRecipe(molecule, "data/C60.gr") + recipe = makeRecipe(molecule, Path(__file__).parent / "data/C60.gr") # Tell the fithook that we want very verbose output. recipe.fithooks[0].verbose = 3 @@ -148,7 +131,7 @@ def main(): res.print_results() # Plot results - plotResults(recipe) + recipe.plot_recipe(**plot_styles) return diff --git a/docs/examples/nppdfsas.py b/docs/examples/nppdfsas.py index 863fea7a..91983bdf 100644 --- a/docs/examples/nppdfsas.py +++ b/docs/examples/nppdfsas.py @@ -22,7 +22,9 @@ of the nanoparticle that agrees best with both the PDF and SAS data. """ -import numpy +from pathlib import Path + +import matplotlib.pyplot as plt from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal @@ -144,14 +146,16 @@ def fitRecipe(recipe): return -def plotResults(recipe): - """Plot the results contained within a refined FitRecipe.""" - # All this should be pretty familiar by now. +def plot_results(recipe): + """Plot the results contained within a refined FitRecipe. + + The recipe has two contributions ("pdf" and "sas"), so plot_recipe + produces one figure per contribution. The G(r) crystal and shape + curves are not part of the standard observed/fit/diff plot, so they + are overlaid on the "pdf" figure afterwards. + """ r = recipe.pdf.profile.x g = recipe.pdf.profile.y - gcalc = recipe.pdf.profile.ycalc - diffzero = -0.8 * max(g) * numpy.ones_like(g) - diff = g - gcalc + diffzero gcryst = recipe.pdf.evaluate_equation("G") gcryst /= recipe.scale.value @@ -159,27 +163,27 @@ def plotResults(recipe): fr = recipe.pdf.evaluate_equation("f") fr *= max(g) / fr[0] - import pylab - - pylab.plot(r, g, "bo", label="G(r) Data") - pylab.plot(r, gcryst, "y--", label="G(r) Crystal") - pylab.plot(r, fr, "k--", label="f(r) calculated (scaled)") - pylab.plot(r, gcalc, "r-", label="G(r) Fit") - pylab.plot(r, diff, "g-", label="G(r) diff") - pylab.plot(r, diffzero, "k-") - pylab.xlabel(r"$r (\AA)$") - pylab.ylabel(r"$G (\AA^{-2})$") - pylab.legend(loc=1) - - pylab.show() + figs, axes = recipe.plot_recipe( + show=False, + return_fig=True, + xlabel=r"$r (\AA)$", + ylabel=r"$G (\AA^{-2})$", + ) + # "pdf" was added to the recipe first, so its axes come first. + ax = axes[0] + ax.plot(r, gcryst, "y--", label="G(r) Crystal") + ax.plot(r, fr, "k--", label="f(r) calculated (scaled)") + ax.legend(loc=1) + + plt.show() return if __name__ == "__main__": - ciffile = "data/pb.cif" - grdata = "data/pb_100_qmin1.gr" - iqdata = "data/pb_100_qmax1.iq" + ciffile = Path(__file__).parent / "data/pb.cif" + grdata = Path(__file__).parent / "data/pb_100_qmin1.gr" + iqdata = Path(__file__).parent / "data/pb_100_qmax1.iq" recipe = makeRecipe(ciffile, grdata, iqdata) recipe.fithooks[0].verbose = 3 @@ -188,6 +192,6 @@ def plotResults(recipe): res = FitResults(recipe) res.print_results() - plotResults(recipe) + plot_results(recipe) # End of file diff --git a/docs/examples/simplepdf.py b/docs/examples/simplepdf.py index 0611dc08..143ee38e 100644 --- a/docs/examples/simplepdf.py +++ b/docs/examples/simplepdf.py @@ -18,7 +18,8 @@ data. It uses the PDFContribution class to simplify fit setup. """ -from crystalpdf import plotResults +from pathlib import Path + from gaussianrecipe import scipyOptimize from diffpy.srfit.fitbase import FitRecipe, FitResults @@ -38,7 +39,7 @@ def makeRecipe(ciffile, datname): # and the phase stru = Structure() - stru.read(ciffile) + stru.read(str(ciffile)) contribution.addStructure("nickel", stru) # Make the FitRecipe and add the FitContribution. @@ -65,11 +66,17 @@ def makeRecipe(ciffile, datname): return recipe +plot_styles = { + "xlabel": r"$r (\AA)$", + "ylabel": r"$G (\AA^{-2})$", +} + + if __name__ == "__main__": # Make the data and the recipe - ciffile = "data/ni.cif" - data = "data/ni-q27r100-neutron.gr" + ciffile = Path(__file__).parent / "data/ni.cif" + data = Path(__file__).parent / "data/ni-q27r100-neutron.gr" # Make the recipe recipe = makeRecipe(ciffile, data) @@ -85,6 +92,6 @@ def makeRecipe(ciffile, datname): res.save_results("nickel_example.res") # Plot! - plotResults(recipe) + recipe.plot_recipe(**plot_styles) # End of file diff --git a/docs/examples/simplepdftwophase.py b/docs/examples/simplepdftwophase.py index 9d7202b9..520b734c 100644 --- a/docs/examples/simplepdftwophase.py +++ b/docs/examples/simplepdftwophase.py @@ -14,7 +14,8 @@ ######################################################################## """Example of a simplified PDF refinement of two-phase structure.""" -from crystalpdftwophase import plotResults +from pathlib import Path + from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal @@ -110,12 +111,18 @@ def makeRecipe(niciffile, siciffile, datname): return recipe +plot_styles = { + "xlabel": r"$r (\AA)$", + "ylabel": r"$G (\AA^{-2})$", +} + + if __name__ == "__main__": # Make the data and the recipe - niciffile = "data/ni.cif" - siciffile = "data/si.cif" - data = "data/si90ni10-q27r60-xray.gr" + niciffile = Path(__file__).parent / "data/ni.cif" + siciffile = Path(__file__).parent / "data/si.cif" + data = Path(__file__).parent / "data/si90ni10-q27r60-xray.gr" # Make the recipe recipe = makeRecipe(niciffile, siciffile, data) @@ -128,6 +135,6 @@ def makeRecipe(niciffile, siciffile, datname): res.print_results() # Plot! - plotResults(recipe) + recipe.plot_recipe(**plot_styles) # End of file diff --git a/docs/examples/simplerecipe.py b/docs/examples/simplerecipe.py index d0244d92..0ff981f4 100644 --- a/docs/examples/simplerecipe.py +++ b/docs/examples/simplerecipe.py @@ -19,6 +19,8 @@ creation. """ +from pathlib import Path + from diffpy.srfit.fitbase import SimpleRecipe ###### @@ -32,7 +34,7 @@ def main(): recipe = SimpleRecipe() # Load text from file. - recipe.loadtxt("data/gaussian.dat") + recipe.loadtxt(Path(__file__).parent / "data/gaussian.dat") # Set the equation. The variable "x" is taken from the data that was just # loaded. The other variables, "A", "x0" and "sigma" are turned into diff --git a/docs/examples/threedoublepeaks.py b/docs/examples/threedoublepeaks.py index 7802b222..935aa715 100644 --- a/docs/examples/threedoublepeaks.py +++ b/docs/examples/threedoublepeaks.py @@ -16,6 +16,8 @@ from __future__ import print_function +from pathlib import Path + import numpy from diffpy.srfit.fitbase import ( @@ -47,7 +49,9 @@ def makeRecipe(): # The Profile # Create a Profile to hold the experimental and calculated signal. profile = Profile() - x, y, dy = profile.loadtxt("data/threedoublepeaks.dat") + x, y, dy = profile.loadtxt( + Path(__file__).parent / "data/threedoublepeaks.dat" + ) # Create the contribution contribution = FitContribution("peaks") @@ -188,30 +192,10 @@ def scipyOptimize(recipe): return -def plotResults(recipe): - """Plot the results contained within a refined FitRecipe.""" - # We can access the data and fit profile through the Profile we created - # above. We get to it through our FitContribution, which we named "g1". - # - # The independent variable. This is always under the "x" attribute. - x = recipe.peaks.profile.x - # The observed profile that we loaded earlier, the "y" attribute. - y = recipe.peaks.profile.y - # The calculated profile, the "ycalc" attribute. - ycalc = recipe.peaks.profile.ycalc - - # This stuff is specific to pylab from the matplotlib distribution. - import pylab - - pylab.plot(x, y, "b.", label="observed profile") - pylab.plot(x, ycalc, "r-", label="calculated profile") - pylab.plot(x, y - ycalc - 0.1 * max(y), "g-", label="difference") - pylab.legend(loc=(0.0, 0.8)) - pylab.xlabel("x") - pylab.ylabel("y") - - pylab.show() - return +plot_styles = { + "xlabel": "x", + "ylabel": "y", +} def steerFit(recipe): @@ -253,7 +237,7 @@ def steerFit(recipe): res.print_results() # Plot the results - plotResults(recipe) + recipe.plot_recipe(**plot_styles) # End of file diff --git a/news/np-docstrings.rst b/news/np-docstrings.rst new file mode 100644 index 00000000..a123ce9e --- /dev/null +++ b/news/np-docstrings.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news needed: converted docstrings in `fitbase/` and `pdf/` to NumPy style + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/srfit/fitbase/calculator.py b/src/diffpy/srfit/fitbase/calculator.py index 41ebde60..c0afc776 100644 --- a/src/diffpy/srfit/fitbase/calculator.py +++ b/src/diffpy/srfit/fitbase/calculator.py @@ -101,15 +101,37 @@ def symbol(self): # Overload me! def __call__(self, *args): - """Calculate something. + """Calculate the signal produced by this Calculator. This method must be overloaded. When overloading, you should specify the arguments explicitly, otherwise the parameters must be specified when adding the Calculator to a RecipeOrganizer. + + Parameters + ---------- + *args + The arguments needed to calculate the signal. + + Returns + ------- + object + The calculated signal. """ return 0 def operation(self, *args): + """Calculate and cache the signal produced by this Calculator. + + Parameters + ---------- + *args + The arguments needed to calculate the signal. + + Returns + ------- + object + The calculated signal. + """ self._value = self.__call__(*args) return self._value @@ -120,7 +142,10 @@ def _validate(self): the operation, since this could be costly. The operation should be validated with a containing equation. - Raises AttributeError if validation fails. + Raises + ------ + AttributeError + If validation fails. """ ParameterSet._validate(self) diff --git a/src/diffpy/srfit/fitbase/configurable.py b/src/diffpy/srfit/fitbase/configurable.py index 8fb275bc..7c0ba19d 100644 --- a/src/diffpy/srfit/fitbase/configurable.py +++ b/src/diffpy/srfit/fitbase/configurable.py @@ -21,15 +21,13 @@ class Configurable(object): - """Configurable class. - - A Configurable has state of which a FitRecipe must be aware. + """Base class for objects with state a FitRecipe must be aware of. Attributes ---------- _configobjs - Set of Configureables in a hierarchy or instances. - Messages get passed up the hierarchy to a FitReciple + The set of Configurables in a hierarchy of instances. + Messages get passed up the hierarchy to a FitRecipe via these objects. """ @@ -46,8 +44,8 @@ def _update_configuration(self): def _store_configurable(self, obj): """Store a Configurable. - The passed obj is only stored if it is a a Configurable, - otherwise this method quietly exits. + The passed obj is only stored if it is a Configurable, otherwise + this method quietly exits. """ if isinstance(obj, Configurable): self._configobjs.add(obj) diff --git a/src/diffpy/srfit/fitbase/constraint.py b/src/diffpy/srfit/fitbase/constraint.py index 8119c820..f78d1de0 100644 --- a/src/diffpy/srfit/fitbase/constraint.py +++ b/src/diffpy/srfit/fitbase/constraint.py @@ -45,7 +45,7 @@ class Constraint(Validatable): - """Constraint class. + """Associate a Parameter with an equation that determines its value. Constraints are designed to be stored in only one place. (The holder of the constraint owns it). @@ -53,14 +53,14 @@ class Constraint(Validatable): Attributes ---------- par - A Parameter that is the subject of the constraint. + The Parameter that is the subject of the constraint. eq - An equation whose evaluation is used to set the value of the + The equation whose evaluation is used to set the value of the constraint. """ def __init__(self): - """Initialization.""" + """Initialize an empty constraint.""" self.par = None self.eq = None return @@ -139,9 +139,12 @@ def update(self): def _validate(self): """Validate my state. - This validates that par is not None. This validates eq. + This validates that ``par`` is not None. This validates ``eq``. - Raises SrFitError if validation fails. + Raises + ------ + SrFitError + If validation fails. """ if self.par is None: raise SrFitError("par is None") diff --git a/src/diffpy/srfit/fitbase/fitcontribution.py b/src/diffpy/srfit/fitbase/fitcontribution.py index e1c29f7f..d896e86e 100644 --- a/src/diffpy/srfit/fitbase/fitcontribution.py +++ b/src/diffpy/srfit/fitbase/fitcontribution.py @@ -79,7 +79,7 @@ class FitContribution(ParameterSet): - """FitContribution class. + """Organize an Equation, a Profile, and their supporting objects. FitContributions organize an Equation that calculates the signal, and a Profile that holds the signal. ProfileGenerators and Calculators can be @@ -97,14 +97,14 @@ class FitContribution(ParameterSet): A managed dictionary of Calculators, indexed by name. _constraints A set of constrained Parameters. Constraints can be - added using the 'constrain' methods. + added using the ``constrain`` methods. _generators A managed dictionary of ProfileGenerators. _parameters A managed OrderedDict of parameters. _restraints A set of Restraints. Restraints can be added using the - 'restrain' method. + ``restrain`` method. _parsets A managed dictionary of ParameterSets. _eqfactory @@ -131,7 +131,13 @@ class FitContribution(ParameterSet): """ def __init__(self, name): - """Initialization.""" + """Initialize the FitContribution. + + Parameters + ---------- + name : str + The name of this FitContribution. + """ ParameterSet.__init__(self, name) self._eq = None self._reseq = None @@ -149,20 +155,20 @@ def set_profile(self, profile, xname=None, yname=None, dyname=None): Parameters ---------- - profile - A Profile that specifies the calculation points and that + profile : Profile + The Profile that specifies the calculation points and that will store the calculated signal. - xname + xname : str, optional The name of the independent variable from the Profile. If this is None (default), then the name specified by the Profile for this parameter will be used. This variable is usable within string equations with the specified name. - yname + yname : str, optional The name of the observed Profile. If this is None (default), then the name specified by the Profile for this parameter will be used. This variable is usable within string equations with the specified name. - dyname + dyname : str, optional The name of the uncertainty in the observed Profile. If this is None (default), then the name specified by the Profile for this parameter will be used. This variable is @@ -219,26 +225,28 @@ def add_profile_generator(self, gen, name=None): """Add a ProfileGenerator to be used by this FitContribution. The ProfileGenerator is given a name so that it can be used as part of - the profile equation (see setEquation). This can be different from the - name of the ProfileGenerator used for attribute access. + the profile equation (see ``set_equation``). This can be different + from the name of the ProfileGenerator used for attribute access. FitContributions should not share ProfileGenerator instances. Different ProfileGenerators can share Parameters and ParameterSets, however. - Calling addProfileGenerator sets the profile equation to call the - calculator and if there is not a profile equation already. + Calling ``add_profile_generator`` sets the profile equation to call + the calculator if there is not a profile equation already. Parameters ---------- - gen - A ProfileGenerator instance - name + gen : ProfileGenerator + The ProfileGenerator instance to add. + name : str, optional A name for the calculator. If name is None (default), then the ProfileGenerator's name attribute will be used. - - Raises ValueError if the ProfileGenerator has no name. - Raises ValueError if the ProfileGenerator has the same name as some - other managed object. + Raises + ------ + ValueError + If the ProfileGenerator has no name, or if the + ProfileGenerator has the same name as some other managed + object. """ if name is None: name = gen.name @@ -276,24 +284,25 @@ def set_equation(self, eqstr, ns={}): This sets the equation that will be used when generating the residual for this FitContribution. The equation will be usable within - set_residual_equation as "eq", and it takes no arguments. + ``set_residual_equation`` as ``"eq"``, and it takes no arguments. Parameters ---------- - eqstr + eqstr : str A string representation of the equation. Any Parameter - registered by addParameter or setProfile, or function - registered by setCalculator, register_function or - register_string_function can be can be used in the equation + registered by ``addParameter`` or ``set_profile``, or function + registered by ``register_calculator``, ``register_function`` or + ``register_string_function`` can be used in the equation by name. Other names will be turned into Parameters of this FitContribution. - ns + ns : dict, optional A dictionary of Parameters, indexed by name, that are used in the eqstr, but not registered (default {}). - - Raises ValueError if ns uses a name that is already used for a - variable. + Raises + ------ + ValueError + If ns uses a name that is already used for a variable. """ # Build the equation instance. eq = get_equation_from_string( @@ -328,10 +337,14 @@ def setEquation(self, eqstr, ns={}): return def get_equation(self): - """Get math expression string for the active profile equation. - - Return normalized math expression or an empty string if profile - equation has not been set yet. + """Get the math expression string for the active profile + equation. + + Returns + ------- + str + The normalized math expression, or an empty string if the + profile equation has not been set yet. """ from diffpy.srfit.equation.visitors import getExpression @@ -353,26 +366,29 @@ def getEquation(self): def set_residual_equation(self, eqstr): """Set the residual equation for the FitContribution. + Two residuals are preset for convenience, ``"chiv"`` and ``"resv"``. + ``chiv`` is defined such that ``dot(chiv, chiv) = chi^2``. + ``resv`` is defined such that ``dot(resv, resv) = Rw^2``. + You can call on these in your residual equation. Note that the quantity + that will be optimized is the summed square of the residual equation. + Keep that in mind when defining a new residual or using the built-in + ones. + Parameters ---------- - eqstr + eqstr : str A string representation of the residual. If eqstr is None (default), then the previous residual equation will be used, or the chi2 residual will be used if that does not exist. - - Two residuals are preset for convenience, "chiv" and "resv". - chiv is defined such that dot(chiv, chiv) = chi^2. - resv is defined such that dot(resv, resv) = Rw^2. - You can call on these in your residual equation. Note that the quantity - that will be optimized is the summed square of the residual equation. - Keep that in mind when defining a new residual or using the built-in - ones. - - Raises SrFitError if the Profile is not yet defined. - Raises ValueError if eqstr depends on a Parameter that is not part of - the FitContribution. + Raises + ------ + SrFitError + If the Profile is not yet defined. + ValueError + If eqstr depends on a Parameter that is not part of the + FitContribution. """ if self.profile is None: raise SrFitError("Assign the Profile first") @@ -407,10 +423,14 @@ def setResidualEquation(self, eqstr): return def get_residual_equation(self): - """Get math expression string for the active residual equation. - - Return normalized math formula or an empty string if residual - equation has not been configured yet. + """Get the math expression string for the active residual + equation. + + Returns + ------- + str + The normalized math formula, or an empty string if the + residual equation has not been configured yet. """ from diffpy.srfit.equation.visitors import getExpression @@ -431,18 +451,23 @@ def getResidualEquation(self): return self.get_residual_equation() def residual(self): - """Calculate the residual for this fitcontribution. + """Calculate the residual for this FitContribution. When this method is called, it is assumed that all parameters have been assigned their most current values by the FitRecipe. This will be the case when being called as part of a FitRecipe refinement. - The residual is by default an array chiv: - chiv = (eq() - self.profile.y) / self.profile.dy - The value that is optimized is dot(chiv, chiv). + The residual is by default an array ``chiv``: + ``chiv = (eq() - self.profile.y) / self.profile.dy``. + The value that is optimized is ``dot(chiv, chiv)``. + + The residual equation can be changed with the + ``set_residual_equation`` method. - The residual equation can be changed with the set_residual_equation - method. + Returns + ------- + numpy.ndarray + The array of residual values. """ # Assign the calculated profile self.profile.ycalc = self._eq() @@ -451,8 +476,13 @@ def residual(self): return self._reseq() def evaluate(self): - """Evaluate the contribution equation and update - profile.ycalc.""" + """Evaluate the contribution equation and update profile.ycalc. + + Returns + ------- + numpy.ndarray + The calculated signal. + """ yc = self._eq() if self.profile is not None: self.profile.ycalc = yc @@ -465,7 +495,10 @@ def _validate(self): ProfileGenerator validations. This validates _eq. This validates _reseq and residual. - Raises SrFitError if validation fails. + Raises + ------ + SrFitError + If validation fails. """ self.profile._validate() ParameterSet._validate(self) diff --git a/src/diffpy/srfit/fitbase/fithook.py b/src/diffpy/srfit/fitbase/fithook.py index 43e2c84e..6edae312 100644 --- a/src/diffpy/srfit/fitbase/fithook.py +++ b/src/diffpy/srfit/fitbase/fithook.py @@ -22,7 +22,7 @@ and the current variable values. Custom FitHooks can be added to a FitRecipe with the -FitRecipe.setFitHook method. +FitRecipe.push_fit_hook method. """ from __future__ import print_function @@ -60,8 +60,8 @@ def precall(self, recipe): Parameters ---------- - recipe - The FitRecipe instance + recipe : FitRecipe + The FitRecipe instance. """ return @@ -71,10 +71,10 @@ def postcall(self, recipe, chiv): Parameters ---------- - recipe - The FitRecipe instance - chiv - The residual vector + recipe : FitRecipe + The FitRecipe instance. + chiv : ndarray + The residual vector. """ return @@ -83,7 +83,7 @@ def postcall(self, recipe, chiv): class PrintFitHook(FitHook): - """Base class for inspecting the progress of a FitRecipe refinement. + """Print the progress of a FitRecipe refinement. This FitHook prints out a running count of the number of times the residual has been called, or other information, based on the verbosity. @@ -94,14 +94,15 @@ class PrintFitHook(FitHook): The number of times the residual has been called (default 0). verbose An integer telling how verbose to be (default 1). - 0 - print nothing - 1 - print the count during the precall - 2 - print the residual during the postcall - >=3 - print the variables during the postcall + + 0 + Print nothing. + 1 + Print the count during the precall. + 2 + Print the residual during the postcall. + >=3 + Print the variables during the postcall. """ def __init__(self): @@ -127,8 +128,8 @@ def precall(self, recipe): Parameters ---------- - recipe - The FitRecipe instance + recipe : FitRecipe + The FitRecipe instance. """ self.count += 1 if self.verbose > 0: @@ -141,10 +142,10 @@ def postcall(self, recipe, chiv): Parameters ---------- - recipe - The FitRecipe instance - chiv - The residual vector + recipe : FitRecipe + The FitRecipe instance. + chiv : ndarray + The residual vector. """ if self.verbose < 2: return @@ -185,7 +186,7 @@ def _byname(nv): # TODO - Display the chi^2 on the plot during refinement. class PlotFitHook(FitHook): - """This FitHook has live plotting of whatever is being refined.""" + """Live-plot the progress of a FitRecipe refinement.""" def reset(self, recipe): """Set up the plot.""" @@ -237,10 +238,10 @@ def postcall(self, recipe, chiv): Parameters ---------- - recipe - The FitRecipe instance - chiv - The residual vector + recipe : FitRecipe + The FitRecipe instance. + chiv : ndarray + The residual vector. """ FitHook.postcall(self, recipe, chiv) import pylab diff --git a/src/diffpy/srfit/fitbase/fitrecipe.py b/src/diffpy/srfit/fitbase/fitrecipe.py index 101c88b8..efa1aa64 100644 --- a/src/diffpy/srfit/fitbase/fitrecipe.py +++ b/src/diffpy/srfit/fitbase/fitrecipe.py @@ -26,7 +26,7 @@ Variables added to a FitRecipe can be tagged with string identifiers. Variables can be later retrieved or manipulated by tag. The tag name -"__fixed" is reserved. +``__fixed`` is reserved. See the examples in the documentation for how to create an optimization problem using FitRecipe. @@ -135,7 +135,8 @@ class FitRecipe(_fitrecipe_interface, RecipeOrganizer): - """FitRecipe class. + """Organize FitContributions, variables, restraints, and constraints + into a refinable recipe. Attributes ---------- @@ -148,7 +149,7 @@ class FitRecipe(_fitrecipe_interface, RecipeOrganizer): _constraints : dict The dictionary of Constraints, indexed by the constrained Parameter. Constraints can be added using the - 'constrain' method. + `add_constraint` method. _oconstraints : list The ordered list of the constraints from this and all sub-components. @@ -180,7 +181,7 @@ class FitRecipe(_fitrecipe_interface, RecipeOrganizer): weights are multiplied by the residual of the FitContribution when determining the overall residual. _fixedtag : str - "__fixed", used for tagging variables as fixed. Don't + ``__fixed``, used for tagging variables as fixed. Don't use this tag unless you want issues. Properties @@ -221,7 +222,13 @@ class FitRecipe(_fitrecipe_interface, RecipeOrganizer): bounds2 = property(lambda self: self.get_bounds_array()) def __init__(self, name="fit"): - """Initialization.""" + """Initialize the FitRecipe. + + Parameters + ---------- + name : str, optional + The name for this FitRecipe. Default is "fit". + """ RecipeOrganizer.__init__(self, name) self.fithooks = [] self.pushFitHook(PrintFitHook()) @@ -337,7 +344,13 @@ def popFitHook(self, fithook=None, index=-1): return def get_fit_hooks(self): - """Get the sequence of FitHook instances.""" + """Get the sequence of FitHook instances. + + Returns + ------- + list + The list of FitHook instances registered with this FitRecipe. + """ return self.fithooks[:] @deprecated(getfithooks_dep_msg) @@ -454,13 +467,13 @@ def remove_parameter_set(self, parset): hierarchy of managed ParameterSets. If the provided ParameterSet is not currently managed by this object, a ValueError will be raised. - Parameters: - ----------- + Parameters + ---------- parset : ParameterSet The ParameterSet instance to be removed from the hierarchy. - Raises: - ------- + Raises + ------ ValueError If the provided ParameterSet is not managed by this object. """ @@ -482,8 +495,8 @@ def residual(self, p=[]): The residual is by default the weighted concatenation of each FitContribution's residual, plus the value of each restraint. The array - returned, denoted chiv, is such that - dot(chiv, chiv) = chi^2 + restraints. + returned, denoted ``chiv``, is such that + ``dot(chiv, chiv) = chi^2 + restraints``. Parameters ---------- @@ -494,11 +507,11 @@ def residual(self, p=[]): been updated in some other way, and the explicit update within this function is skipped. - Return - ------ + Returns + ------- chiv : numpy.ndarray The array of residuals to be optimized. The array is such that - dot(chiv, chiv) = chi^2 + restraints. + ``dot(chiv, chiv) = chi^2 + restraints``. """ # Prepare, if necessary @@ -546,6 +559,12 @@ def scalar_residual(self, p=[]): been updated in some other way, and the explicit update within this function is skipped. + Returns + ------- + float + The scalar residual, ``dot(chiv, chiv)``, where ``chiv`` is + the vector residual returned by `residual`. + Notes ----- The residual is by default the weighted concatenation of each @@ -567,7 +586,19 @@ def scalarResidual(self, p=[]): return self.scalar_residual(p) def __call__(self, p=[]): - """Same as scalar_residual method.""" + """Compute the scalar residual, same as `scalar_residual`. + + Parameters + ---------- + p : list or numpy.ndarray, optional + The list of current variable values, provided in the same order + as the ``_parameters`` list. Default is an empty list. + + Returns + ------- + float + The scalar residual, ``dot(chiv, chiv)``. + """ return self.scalar_residual(p) def _prepare(self): @@ -754,7 +785,7 @@ def add_variable( Returns ------- ParameterProxy - ParameterProxy (variable) for the passed Parameter. + The ParameterProxy (variable) for the passed Parameter. Raises ------ @@ -827,6 +858,8 @@ def delVar(self, var): return def __delattr__(self, name): + """Delete a variable if name refers to one, otherwise defer to + the base class.""" if name in self._parameters: self.delete_variable(self._parameters[name]) return @@ -910,8 +943,15 @@ def __get_var_and_check(self, var): var A variable of the FitRecipe, or the name of a variable. - Returns the variable or None if the variable cannot be found in the - _parameters list. + Returns + ------- + object + The variable. + + Raises + ------ + ValueError + If the variable is not part of the FitRecipe. """ if isinstance(var, str): var = self._parameters.get(var) @@ -973,24 +1013,24 @@ def fix(self, *args, **kw): Parameters ---------- - *args : str or Parameter - The positional arguments specifying the parameters to fix. - These can be parameter objects, their names as strings, or - tags. The special string "all" can be used to select all - parameters. - **kw : dict - The keyword arguments where the keys are parameter names and - the values are the values to assign to the corresponding - fixed parameters. + *args : str or Parameter + The positional arguments specifying the parameters to fix. + These can be parameter objects, their names as strings, or + tags. The special string "all" can be used to select all + parameters. + **kw : dict + The keyword arguments where the keys are parameter names and + the values are the values to assign to the corresponding + fixed parameters. Raises ------ - ValueError: - If an unknown parameter, name, or tag is passed, or if a - tag is passed as a keyword argument. + ValueError + If an unknown parameter, name, or tag is passed, or if a + tag is passed as a keyword argument. - Example - ------- + Examples + -------- :: @@ -1044,6 +1084,10 @@ def free(self, *args, **kw): their values to assign after freeing. This is useful for setting the value of a parameter while marking it as free. + Returns + ------- + None + Raises ------ ValueError @@ -1057,10 +1101,6 @@ def free(self, *args, **kw): are freed. - If keyword arguments are provided, the corresponding parameter values will be updated after freeing. - - Returns - ------- - None """ # Check the inputs and get the variables from them varargs = self.__get_vars_from_args(*args, **kw) @@ -1238,7 +1278,6 @@ def get_values(self): Returns ------- - values_array : numpy.ndarray The array containing the current values of all free variables in the fit recipe. @@ -1264,7 +1303,7 @@ def get_names(self): Returns ------- - parameter_names :list of str + parameter_names : list of str The list containing the names of free variables. """ parameter_names = [ @@ -1502,7 +1541,9 @@ def set_plot_defaults(self, **kwargs): ylabel : str, optional The label for the y-axis. title : str or None, optional - The plot title. Default is no title. + The plot title. If None (default), each figure created by + `plot_recipe` is titled with the name of the contribution it + shows. A title is not added to a user-supplied axes. legend : bool, optional The legend is shown if True. Default is True. legend_loc : str, optional @@ -1518,11 +1559,16 @@ def set_plot_defaults(self, **kwargs): Default is 1.0. show : bool, optional The plot is displayed using `plt.show()` if True. Default is True. - ax : matplotlib.axes.Axes or None, optional - The axes object to plot on. If None, creates a new figure. - Default is None. - return_fig : bool, optional - The figure and axes objects are returned if True. Default is False. + + Notes + ----- + The `data_label`, `fit_label`, `diff_label` and `title` options accept + a ``{contribution}`` placeholder that is replaced by the name of the + FitContribution being plotted, e.g. + ``fit_label="{contribution} calculated"``. When several contributions + are drawn on a shared axes, labels without the placeholder are + prefixed with the contribution name so the legend entries stay + distinguishable. Examples -------- @@ -1541,6 +1587,14 @@ def set_plot_defaults(self, **kwargs): ) self.plot_options.update(kwargs) + def _format_plot_label(self, label, contribution_name, add_prefix): + """Insert the contribution name into a legend label.""" + if "{contribution}" in label: + return label.format(contribution=contribution_name) + if add_prefix: + return f"{contribution_name}: {label}" + return label + def _set_axes_labels_from_metadata(self, meta, plot_params): """Set axes labels based on filename suffix in profile metadata if not already set.""" @@ -1577,9 +1631,12 @@ def plot_recipe(self, ax=None, return_fig=False, **kwargs): Returns ------- - fig, axes : tuple of (mpl.figure.Figure, list of mpl.axes.Axes) - The figure object and a list of axes objects (one per contribution) - are returned if return_fig=True. + fig, axes : tuple + The figure and axes objects, returned only if + ``return_fig=True``. If the recipe has a single contribution, + a single ``mpl.figure.Figure`` and ``mpl.axes.Axes`` are + returned. If it has multiple contributions, a list of figures + and a list of axes (one per contribution) are returned instead. Examples -------- @@ -1640,20 +1697,23 @@ def plot_recipe(self, ax=None, return_fig=False, **kwargs): ) 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 plot_params["show_fit"] or plot_params["show_diff"]: + if show_fit or show_diff: print( f"Contribution '{name}' has no calculated values " "(ycalc is None). " "Only observed data will be plotted." ) - plot_params["show_fit"] = False - plot_params["show_diff"] = False + show_fit = False + show_diff = False else: diff = yobs - ycalc y_min = min(yobs.min(), ycalc.min()) @@ -1672,27 +1732,33 @@ def plot_recipe(self, ax=None, return_fig=False, **kwargs): x, yobs, plot_params["data_style"], - label=plot_params["data_label"], + 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 plot_params["show_fit"]: + if show_fit: current_ax.plot( x, ycalc, plot_params["fit_style"], - label=plot_params["fit_label"], + 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 plot_params["show_diff"]: + if show_diff: current_ax.plot( x, diff + offset, plot_params["diff_style"], - label=plot_params["diff_label"], + 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"], @@ -1709,7 +1775,11 @@ def plot_recipe(self, ax=None, return_fig=False, **kwargs): 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(plot_params["title"]) + 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"]: @@ -1744,7 +1814,6 @@ def convert_bounds_to_restraints(self, sig=1, scaled=False): Smaller values produce stronger restraints. If a scalar is given, the same value is applied to all parameters. If an iterable is provided, it must match the number of parameters. Default is 1. - scaled : bool, optional If True, scale each restraint by the magnitude of the corresponding parameter, consistent with the behavior of :meth:`restrain`. diff --git a/src/diffpy/srfit/fitbase/fitresults.py b/src/diffpy/srfit/fitbase/fitresults.py index f3a48f23..4f7fa5d8 100644 --- a/src/diffpy/srfit/fitbase/fitresults.py +++ b/src/diffpy/srfit/fitbase/fitresults.py @@ -102,8 +102,8 @@ class FitResults(object): The estimated standard uncertainties of the variables. None if invalid. showfixed : bool - Show the fixed variables in the formatted output - (default True). + The flag indicating whether to show the fixed variables in the + formatted output (default True). fixednames : list[str] The names of variables held fixed during refinement. @@ -112,8 +112,8 @@ class FitResults(object): The values of the fixed variables. showcon : bool - show the constrained parameters in the formatted output - (default False). + The flag indicating whether to show the constrained parameters + in the formatted output (default False). connames : list[str] The names of constrained parameters. @@ -170,9 +170,11 @@ def __init__(self, recipe, update=True, showfixed=True, showcon=False): The flag indicating whether to do an immediate update (default True). showfixed : bool - Show fixed variables in the output (default True). + The flag indicating whether to show fixed variables in the + output (default True). showcon : bool - Show constraint values in the output (default False). + The flag indicating whether to show constraint values in + the output (default False). """ self.recipe = recipe self.conresults = OrderedDict() @@ -394,8 +396,9 @@ def _calculate_constraint_uncertainties(self): def get_results_string(self, header="", footer="", update=False): """Format the results and return them in a string. - This function is called by print_results and save_results. Overloading - the formatting here will change all three functions. + This function is called by ``print_results`` and + ``save_results``. Overloading the formatting here will change + all three functions. Parameters ---------- @@ -404,12 +407,13 @@ def get_results_string(self, header="", footer="", update=False): footer : str The footer to add to the output (default "") update : bool - The flag indicating whether to call update() (default False). + The flag indicating whether to call ``update()`` (default + False). Returns ------- - out : str - a string containing the formatted results. + str + The string containing the formatted results. """ if update: self.update() @@ -598,12 +602,13 @@ def print_results(self, header="", footer="", update=False): Parameters ---------- - header + header : str The header to add to the output (default "") - footer + footer : str The footer to add to the output (default "") - update - The flag indicating whether to call update() (default False). + update : bool + The flag indicating whether to call ``update()`` (default + False). """ print(self.get_results_string(header, footer, update).rstrip()) return @@ -620,21 +625,23 @@ def printResults(self, header="", footer="", update=False): return def __str__(self): + """Return the formatted results string.""" return self.get_results_string() def save_results(self, filename, header="", footer="", update=False): """Format and save the results. Parameters - ---------------------------------- - filename + ---------- + filename : str The name of the save file. - header + header : str The header to add to the output (default "") - footer + footer : str The footer to add to the output (default "") - update - The flag indicating whether to call update() (default False). + update : bool + The flag indicating whether to call ``update()`` (default + False). """ # Save the time and user from getpass import getuser @@ -736,12 +743,13 @@ def __init__(self, con, weight, fitres): Parameters ---------- - con - The FitContribution - weight - The weight of the FitContribution in the recipe - fitres - The FitResults instance to contain this ContributionResults + con : FitContribution + The FitContribution to summarize. + weight : float + The weight of the FitContribution in the recipe. + fitres : FitResults + The FitResults instance containing this + ContributionResults. """ self.x = None self.y = None @@ -819,11 +827,11 @@ def _calculate_metrics(self): @deprecated(resultsDictionary_dep_msg) def resultsDictionary(results): - """**This function has been deprecated and will be** **removed in version - 4.0.0.** + """This function has been deprecated and will be removed in version + 4.0.0. - **Please use** - **diffpy.srfit.fitbase.FitResults.get_results_dictionary instead.** + Please use + diffpy.srfit.fitbase.FitResults.get_results_dictionary instead. Get dictionary of results from file. @@ -832,9 +840,14 @@ def resultsDictionary(results): Parameters ---------- - results - An open file-like object, name of a file that contains - results from FitResults or a string containing fit results. + results : str or file-like + The open file-like object, name of a file that contains + results from FitResults, or a string containing fit results. + + Returns + ------- + dict + The mapping of result names to their string values. """ resstr = inputToString(results) @@ -853,12 +866,12 @@ def resultsDictionary(results): @deprecated(initializeRecipe_dep_msg) def initializeRecipe(recipe, results): - """**This function has been deprecated and will be** **removed in - version 4.0.0.** + """This function has been deprecated and will be removed in version + 4.0.0. - **Please use** - **diffpy.srfit.fitbase.FitRecipe.initialize_recipe_with_results** - **instead.** + Please use + diffpy.srfit.fitbase.FitRecipe.initialize_recipe_with_results + instead. Initialize the variables of a recipe from a results file. @@ -868,11 +881,16 @@ def initializeRecipe(recipe, results): Parameters ---------- - recipe - A configured recipe with variables - results - An open file-like object, name of a file that contains - results from FitResults or a string containing fit results. + recipe : FitRecipe + The configured recipe with variables. + results : str or file-like + The open file-like object, name of a file that contains + results from FitResults, or a string containing fit results. + + Raises + ------ + AttributeError + If no results can be found in ``results``. """ mpairs = resultsDictionary(results) if not mpairs: diff --git a/src/diffpy/srfit/fitbase/parameter.py b/src/diffpy/srfit/fitbase/parameter.py index 4fa2c2b6..d6a52d1a 100644 --- a/src/diffpy/srfit/fitbase/parameter.py +++ b/src/diffpy/srfit/fitbase/parameter.py @@ -56,7 +56,7 @@ class Parameter(_parameter_interface, Argument, Validatable): - """Parameter class. + """Encapsulate an adjustable parameter within SrFit. Attributes ---------- @@ -65,9 +65,9 @@ class Parameter(_parameter_interface, Argument, Validatable): const A flag indicating whether this is considered a constant. _value - The value of the Parameter. Modified with 'set_value'. + The value of the Parameter. Modified with ``set_value``. value - Property for 'getValue' and 'set_value'. + Property for ``getValue`` and ``set_value``. constrained A flag indicating if the Parameter is constrained (default False). @@ -78,21 +78,23 @@ class Parameter(_parameter_interface, Argument, Validatable): """ def __init__(self, name, value=None, const=False): - """Initialization. + """Initialize the Parameter. Parameters ---------- - name + name : str The name of this Parameter (must be a valid attribute - identifier) - value + identifier). + value : float, optional The initial value of this Parameter (default 0). - const - A flag inticating whether the Parameter is a constant (like + const : bool, optional + A flag indicating whether the Parameter is a constant (like pi). - - Raises ValueError if the name is not a valid attribute identifier + Raises + ------ + ValueError + If the name is not a valid attribute identifier. """ self.constrained = False self.bounds = [-numpy.inf, +numpy.inf] @@ -101,23 +103,17 @@ def __init__(self, name, value=None, const=False): return def set_value(self, val): - """Set the value of the Parameter and the bounds. + """Set the value of the Parameter. Parameters ---------- - val + val : float The value to assign. - lower_bound : float - The lower bounds for the bounds list. If this is None - (default), then the lower bound will not be alterered. - upper_bound : float - The upper bounds for the bounds list. If this is None - (default), then the upper bound will not be alterered. Returns ------- - self - Returns self so that mutators can be chained. + Parameter + Return self so that mutators can be chained. """ Argument.set_value(self, val) return self @@ -140,14 +136,14 @@ def set_constant(self, is_constant=True, value=None): The flag indicating if the parameter is constant (default True). value : float, optional - The value value for the parameter to be set to (default None). - If this is not None, then the parameter will get a new value, - constant or otherwise. + The value to set the parameter to (default None). If this is + not None, then the parameter will get a new value, constant + or otherwise. Returns ------- - self - Returns self so that mutators can be chained. + Parameter + Return self so that mutators can be chained. """ self.const = bool(is_constant) if value is not None: @@ -176,8 +172,8 @@ def bound_range(self, lower_bound=None, upper_bound=None): Returns ------- - self - Returns self so that mutators can be chained. + Parameter + Return self so that mutators can be chained. """ if lower_bound is not None: self.bounds[0] = lower_bound @@ -210,8 +206,8 @@ def bound_window(self, lower_radius=0, upper_radius=None): Returns ------- - self - Returns self so that mutators can be chained. + Parameter + Return self so that mutators can be chained. """ val = self.getValue() lower_bound = val - lower_radius @@ -236,7 +232,10 @@ def _validate(self): This validates that value is not None. - Raises SrFitError if validation fails. + Raises + ------ + SrFitError + If validation fails. """ if self.value is None: raise SrFitError("value of '%s' is None" % self.name) @@ -261,17 +260,19 @@ class ParameterProxy(Parameter): """ def __init__(self, name, par): - """Initialization. + """Initialize the ParameterProxy. Parameters ---------- - name + name : str The name of this ParameterProxy. - par + par : Parameter The Parameter this is a proxy for. - - Raises ValueError if the name is not a valid attribute identifier + Raises + ------ + ValueError + If the name is not a valid attribute identifier. """ validateName(name) @@ -337,7 +338,10 @@ def _validate(self): This validates that value and par are not None. - Raises SrFitError if validation fails. + Raises + ------ + SrFitError + If validation fails. """ if self.par is None: raise SrFitError("par is None") @@ -360,33 +364,35 @@ def __init__(self, name, obj, getter=None, setter=None, attr=None): Parameters ---------- - name + name : str The name of this Parameter. - obj + obj : object The object to be wrapped. - getter + getter : callable, optional The unbound function that can be used to access the - attribute containing the parameter value. getter(obj) - should return the Parameter value. If getter is None + attribute containing the parameter value. ``getter(obj)`` + should return the Parameter value. If getter is None (default), it is assumed that an attribute is accessed via attr. If attr is also specified, then the Parameter - value will be accessed via getter(obj, attr). - setter + value will be accessed via ``getter(obj, attr)``. + setter : callable, optional The unbound function that can be used to modify the attribute containing the parameter value. - setter(obj, value) should set the attribute to the + ``setter(obj, value)`` should set the attribute to the passed value. If setter is None (default), it is assumed that an attribute is accessed via attr. If attr is also specified, then the Parameter value will be set via - setter(obj, attr, value). - attr + ``setter(obj, attr, value)``. + attr : str, optional The name of the attribute that contains the value of the parameter. If attr is None (default), then both getter and setter must be specified. - - Raises ValueError if exactly one of getter or setter is not None, or if - getter, setter and attr are all None. + Raises + ------ + ValueError + If exactly one of getter or setter is not None, or if + getter, setter and attr are all None. """ if getter is None and setter is None and attr is None: raise ValueError("Specify attribute access") @@ -414,11 +420,28 @@ def __init__(self, name, obj, getter=None, setter=None, attr=None): return def getValue(self): - """Get the value of the Parameter.""" + """Get the value of the Parameter. + + Returns + ------- + object + The current value of the wrapped attribute. + """ return self.getter(self.obj) def set_value(self, value): - """Set the value of the Parameter.""" + """Set the value of the Parameter. + + Parameters + ---------- + value : object + The value to assign. + + Returns + ------- + ParameterAdapter + Return self so that mutators can be chained. + """ if value != self.getValue(): self.setter(self.obj, value) self.notify() diff --git a/src/diffpy/srfit/fitbase/parameterset.py b/src/diffpy/srfit/fitbase/parameterset.py index edaac6f5..21bb2dac 100644 --- a/src/diffpy/srfit/fitbase/parameterset.py +++ b/src/diffpy/srfit/fitbase/parameterset.py @@ -44,7 +44,7 @@ class ParameterSet(RecipeOrganizer): - """Class for organizing Parameters and other ParameterSets. + """Organize Parameters and other ParameterSets in a hierarchy. ParameterSets are hierarchical organizations of Parameters, Constraints, Restraints and other ParameterSets. @@ -52,8 +52,8 @@ class ParameterSet(RecipeOrganizer): Contained Parameters and other ParameterSets can be accessed by name as attributes in order to facilitate multi-level constraints and restraints. These constraints and restraints can be placed at any level and a flattened - list of them can be retrieved with the getConstraints and getRestraints - methods. + list of them can be retrieved with the '_get_constraints' and + '_get_restraints' methods. Attributes ---------- @@ -89,7 +89,7 @@ def __init__(self, name): Parameters ---------- - name + name : str The name of this ParameterSet. """ RecipeOrganizer.__init__(self, name) @@ -108,13 +108,14 @@ def add_parameter_set(self, parset): Parameters ---------- - parset + parset : ParameterSet The ParameterSet to be stored. - - Raises ValueError if the ParameterSet has no name. - Raises ValueError if the ParameterSet has the same name as some other - managed object. + Raises + ------ + ValueError + If the ParameterSet has no name, or if it has the same name + as some other managed object. """ self._add_object(parset, self._parsets, True) return @@ -134,7 +135,15 @@ def addParameterSet(self, parset): def remove_parameter_set(self, parset): """Remove a ParameterSet from the hierarchy. - Raises ValueError if parset is not managed by this object. + Parameters + ---------- + parset : ParameterSet + The ParameterSet to remove. + + Raises + ------ + ValueError + If parset is not managed by this object. """ self._remove_object(parset, self._parsets) return diff --git a/src/diffpy/srfit/fitbase/profile.py b/src/diffpy/srfit/fitbase/profile.py index 3718e907..45a295b7 100644 --- a/src/diffpy/srfit/fitbase/profile.py +++ b/src/diffpy/srfit/fitbase/profile.py @@ -89,13 +89,13 @@ class Profile(Observable, Validatable): Read-only property of _dyobs. x A numpy array of the calculated independent variable (default - None, property for xpar accessors). + None, property for ``xpar`` accessors). y The profile over the calculation range (default None, property - for ypar accessors). + for ``ypar`` accessors). dy The uncertainty in the profile over the calculation range - (default None, property for dypar accessors). + (default None, property for ``dypar`` accessors). ycalc A numpy array of the calculated signal (default None). xpar @@ -157,7 +157,13 @@ def __init__(self): def load_parsed_data(self, parser): """Load parsed data from a ProfileParser. - This sets the xobs, yobs, dyobs arrays as well as the metadata. + This sets the ``xobs``, ``yobs``, ``dyobs`` arrays as well as + the metadata. + + Parameters + ---------- + parser : ProfileParser + The parser holding the observed profile data and metadata. """ x, y, dx, dy = parser.get_data() self.meta = dict(parser.get_metadata()) @@ -180,23 +186,22 @@ def set_observed_profile(self, xobs, yobs, dyobs=None): Parameters ---------- - xobs - Numpy array of the independent variable - yobs - Numpy array of the observed signal. - dyobs - Numpy array of the uncertainty in the observed signal. If - `dyobs` is None (default), `dyobs` stays None to indicate - no uncertainty was observed, and the calculated `dy` will - be set to 1 at each calculation point instead. - + xobs : numpy.ndarray + The array of the independent variable. + yobs : numpy.ndarray + The array of the observed signal. + dyobs : numpy.ndarray, optional + The array of the uncertainty in the observed signal. If + ``dyobs`` is None (default), ``dyobs`` stays None to + indicate no uncertainty was observed, and the calculated + ``dy`` will be set to 1 at each calculation point instead. Raises - ----------- + ------ ValueError - if len(yobs) != len(xobs) + If ``len(yobs) != len(xobs)``. ValueError - if dyobs != None and len(dyobs) != len(xobs) + If ``dyobs`` is not None and ``len(dyobs) != len(xobs)``. """ if len(yobs) != len(xobs): raise ValueError("xobs and yobs are different lengths") @@ -240,7 +245,6 @@ def set_calculation_range(self, xmin=None, xmax=None, dx=None): Parameters ---------- - xmin : float or "obs", optional The minimum value of the independent variable. Keep the current minimum when not specified. If specified as "obs" @@ -354,14 +358,14 @@ def setCalculationRange(self, xmin=None, xmax=None, dx=None): def set_calculation_points(self, x): """Set the calculation points. + This creates ``y`` and ``dy`` on the specified grid if + ``xobs``, ``yobs`` and ``dyobs`` exist. + Parameters ---------- - x - A non-empty numpy array containing the calculation points. If - xobs exists, the bounds of x will be limited to its bounds. - - This will create y and dy on the specified grid if xobs, yobs and - dyobs exist. + x : numpy.ndarray + The non-empty array of calculation points. If ``xobs`` + exists, the bounds of ``x`` will be limited to its bounds. """ x = numpy.asarray(x) if self.xobs is not None: @@ -385,40 +389,48 @@ def set_calculation_points(self, x): @deprecated(setCalculationPoints_dep_msg) def setCalculationPoints(self, x): - """Set the calculation points. - - Parameters - ---------- - x - A non-empty numpy array containing the calculation points. If - xobs exists, the bounds of x will be limited to its bounds. + """This function has been deprecated and will be removed in version + 4.0.0. - This will create y and dy on the specified grid if xobs, yobs and - dyobs exist. + Please use + diffpy.srfit.fitbase.profile.Profile.set_calculation_points + instead. """ self.set_calculation_points(x) return def loadtxt(self, *args, **kw): - """Use numpy.loadtxt to load data. + """Load data using ``numpy.loadtxt``. - Arguments are passed to numpy.loadtxt. unpack = True is - enforced. The first two arrays returned by numpy.loadtxt are - assumed to be x and y. If there is a third array, it is assumed - to by dy. Any other arrays are ignored. These are passed to - set_observed_profile. + Arguments are passed to ``numpy.loadtxt``. ``unpack=True`` is + enforced. The first two arrays returned by ``numpy.loadtxt`` + are assumed to be x and y. If there is a third array, it is + assumed to be dy. Any other arrays are ignored. The loaded + arrays are passed to ``set_observed_profile``. - Raises ValueError if the call to numpy.loadtxt returns fewer - than 2 arrays. + Parameters + ---------- + *args + The positional arguments passed to ``numpy.loadtxt``. + **kw + The keyword arguments passed to ``numpy.loadtxt``. Returns ------- - x - x array loaded from the file. - y - y array loaded from the file. - dy - dy array loaded from the file. + x : numpy.ndarray + The array of the independent variable loaded from the + file. + y : numpy.ndarray + The array of the observed signal loaded from the file. + dy : numpy.ndarray or None + The array of the uncertainty loaded from the file, or None + if no third column is present. + + Raises + ------ + ValueError + If the call to ``numpy.loadtxt`` returns fewer than 2 + arrays. """ if len(args) == 8 and not args[-1]: args = list(args) @@ -441,21 +453,21 @@ def loadtxt(self, *args, **kw): return x, y, dy def savetxt(self, fname, **kwargs): - """Call `numpy.savetxt` with x, ycalc, y, dy. + """Call ``numpy.savetxt`` with x, ycalc, y, dy. Parameters ---------- fname : filename or file handle - This is passed to `numpy.savetxt`. + The filename or file handle passed to ``numpy.savetxt``. **kwargs - The keyword arguments that are passed to `numpy.savetxt`. + The keyword arguments that are passed to ``numpy.savetxt``. We preset file header "x ycalc y dy". Use ``header=''`` to save data without any header. Raises ------ SrFitError - When `self.ycalc` has not been set. + When ``self.ycalc`` has not been set. See also -------- @@ -484,11 +496,15 @@ def _flush(self, other): def _validate(self): """Validate my state. - This validates that x, y, dy, xobs and yobs are not None. dyobs - may be None, since observed uncertainties are optional. This - validates that x, y, and dy are the same length. + This validates that ``x``, ``y``, ``dy``, ``xobs`` and + ``yobs`` are not None. ``dyobs`` may be None, since observed + uncertainties are optional. This also validates that ``x``, + ``y``, and ``dy`` are the same length. - Raises SrFitError if validation fails. + Raises + ------ + SrFitError + If validation fails. """ datanotset = any( v is None @@ -511,24 +527,23 @@ def _validate(self): def _rebin_array(A, xold, xnew): - """Rebin the an array by interpolating over the new x range. + """Rebin an array by interpolating over a new sampling grid. + + This uses linear interpolation via ``numpy.interp``. Parameters ---------- - A - Array to interpolate - xold - Old sampling array - xnew - New sampling array - - - This uses cubic spline interpolation. + A : numpy.ndarray + The array to interpolate. + xold : numpy.ndarray + The old sampling array. + xnew : numpy.ndarray + The new sampling array. Returns ------- - array - A new array over the new sampling array. + numpy.ndarray + The array ``A`` resampled onto ``xnew``. """ if numpy.array_equal(xold, xnew): return A diff --git a/src/diffpy/srfit/fitbase/profilegenerator.py b/src/diffpy/srfit/fitbase/profilegenerator.py index e0206084..402b3916 100644 --- a/src/diffpy/srfit/fitbase/profilegenerator.py +++ b/src/diffpy/srfit/fitbase/profilegenerator.py @@ -136,10 +136,18 @@ def symbol(self): def __call__(self, x): """Evaluate the profile. - This method must be overloaded. + This method must be overloaded. It only takes the independent + variable to calculate over. - This method only takes the independent variables to calculate - over. + Parameters + ---------- + x : ndarray + The independent variable over which to calculate. + + Returns + ------- + ndarray + The calculated profile. """ return x @@ -148,7 +156,10 @@ def __call__(self, x): def operation(self): """Evaluate the profile. - Return the result of __call__(profile.x). + Returns + ------- + ndarray + The result of ``__call__(profile.x)``. """ y = self.__call__(self.profile.x) return y @@ -158,8 +169,8 @@ def set_profile(self, profile): Parameters ---------- - profile - A Profile that specifies the calculation points and which + profile : Profile + The Profile that specifies the calculation points and which will store the calculated signal. """ if self.profile is not None: @@ -191,7 +202,10 @@ def _validate(self): could be costly. The operation should be validated with a containing equation. - Raises SrFitError if validation fails. + Raises + ------ + SrFitError + If validation fails. """ if self.profile is None: raise SrFitError("profile is None") diff --git a/src/diffpy/srfit/fitbase/profileparser.py b/src/diffpy/srfit/fitbase/profileparser.py index 32bf2fba..7dd8ca4e 100644 --- a/src/diffpy/srfit/fitbase/profileparser.py +++ b/src/diffpy/srfit/fitbase/profileparser.py @@ -77,13 +77,13 @@ class ProfileParser(object): - """Class for parsing data from a or string. + """Base class for parsing profile data from a file. Attributes ---------- _format : str, optional The name of the data format that this parses (string, default - `""`). The format string is a unique identifier for the data + ``""``). The format string is a unique identifier for the data format handled by the parser. _banks : list of tuples The data from each bank. Each bank contains a (x, y, dx, @@ -177,15 +177,15 @@ def parse_file( automatic handling of uncertainties. This is a template method. Subclasses customize a format by - overriding the `_parse_metadata` and `_parse_data` hooks rather + overriding the ``_parse_metadata`` and ``_parse_data`` hooks rather than this method. - The default `_parse_data` reads a single bank: + The default ``_parse_data`` reads a single bank: - For files with 2 columns: assumes (x, y) and sets dx, dy to None. - For files with 3 columns: assumes (x, y, dy) and sets dx to None. - For files with 4 columns: assumes (x, y, dx, dy). - - For other cases: `column_format` must be explicitly specified. + - For other cases: ``column_format`` must be explicitly specified. Uncertainty columns (dx, dy) are only considered valid if all values are positive and not NaN/Inf. Otherwise they are set to None. @@ -201,31 +201,31 @@ def parse_file( If None, the format is auto-detected based on the number of columns. - Valid labels: `"x"`, `"y"`, `"dx"`, `"dy"` + Valid labels: ``"x"``, ``"y"``, ``"dx"``, ``"dy"`` Examples: - - `("x", "y")` - - `("x", "y", "dy")` - - `("x", "y", "dx", "dy")` - - `("x", "dx", "y", "dy")` + - ``("x", "y")`` + - ``("x", "y", "dy")`` + - ``("x", "y", "dx", "dy")`` + - ``("x", "dx", "y", "dy")`` metadata : dict, optional Additional metadata to merge into the metadata parsed from the file. Keys must be strings. A key that collides with one already present in the parsed metadata overrides the - parsed value. A key that collides with `"filename"`, - `"bank"`, or `"nbanks"`, which `parse_file` sets itself, + parsed value. A key that collides with ``"filename"``, + ``"bank"``, or ``"nbanks"``, which ``parse_file`` sets itself, also overrides the automatically set value, but raises a - `UserWarning` since it may affect other code that relies + ``UserWarning`` since it may affect other code that relies on the automatically set value. kwargs The keyword arguments passed on to - `diffpy.utils.parsers.load_data`, such as `usecols`, - `delimiter`, `comments` and `minrows`. Use `usecols` to + ``diffpy.utils.parsers.load_data``, such as ``usecols``, + ``delimiter``, ``comments`` and ``minrows``. Use ``usecols`` to select four columns out of a wider file, then label them - with `column_format`. + with ``column_format``. Raises ------ @@ -263,7 +263,8 @@ def _validate_metadata(metadata): return dict(metadata) def _apply_extra_metadata(self, metadata): - """Merge validated user-supplied metadata into `self._meta`.""" + """Merge validated user-supplied metadata into + ``self._meta``.""" if not metadata: return for key in metadata: @@ -385,13 +386,13 @@ def select_bank(self, index): Parameters ---------- - index - index of bank (integer, starting at 0). + index : int + The index of the bank (integer, starting at 0). Raises - ---------- + ------ IndexError - if requesting a bank that does not exist + If requesting a bank that does not exist. """ if index is None: index = self._meta.get("bank", 0) @@ -432,14 +433,15 @@ def get_data(self, index=None): Parameters ---------- - index - index of bank (integer, starting at 0, default None). If - index is None then the currently selected bank is used. + index : int, optional + The index of the bank (integer, starting at 0, default None). + If index is None then the currently selected bank is used. Returns - ---------- - This returns (x, y, dx, dy) tuple for the bank. dx is None if it - cannot be determined from the data format. + ------- + tuple + The ``(x, y, dx, dy)`` tuple for the bank. ``dx`` and ``dy`` + are None if they cannot be determined from the data format. """ self.select_bank(index) diff --git a/src/diffpy/srfit/fitbase/recipeorganizer.py b/src/diffpy/srfit/fitbase/recipeorganizer.py index e329dfb2..0e7e54cd 100644 --- a/src/diffpy/srfit/fitbase/recipeorganizer.py +++ b/src/diffpy/srfit/fitbase/recipeorganizer.py @@ -174,10 +174,10 @@ class RecipeContainer(Observable, Configurable, Validatable): RecipeContainers are hierarchical organizations of Parameters and other RecipeContainers. This class provides attribute-access to these contained objects. Parameters and other RecipeContainers can be found within the - hierarchy with the _locate_managed_object method. + hierarchy with the `_locate_managed_object` method. A RecipeContainer can manage dictionaries for that store various objects. - These dictionaries can be added to the RecipeContainer using the _manage + These dictionaries can be added to the RecipeContainer using the `_manage` method. RecipeContainer methods that add, remove or retrieve objects will work with any managed dictionary. This makes it easy to add new types of objects to be contained by a RecipeContainer. By default, the @@ -260,11 +260,16 @@ def iterate_over_parameters( top-level parameters will be iterated over. fullnames : bool, optional The flag indicating whether to match against hierarchical - dotted namesrelative to this object. + dotted names relative to this object. If False (default), match only leaf parameter names. - Example - ------- + Yields + ------ + Parameter + The next Parameter whose name matches `pattern`. + + Examples + -------- .. for param in recipe.iterate_over_parameters(pattern="scale_"): @@ -346,20 +351,59 @@ def iterPars(self, pattern="", recurse=True): return self.iterate_over_parameters(pattern=pattern, recurse=recurse) def __iter__(self): - """Iterate over top-level parameters.""" + """Iterate over top-level parameters. + + Returns + ------- + iterator + The iterator over the top-level Parameters. + """ return iter(self._parameters.values()) def __len__(self): - """Get number of top-level parameters.""" + """Get number of top-level parameters. + + Returns + ------- + int + The number of top-level Parameters. + """ return len(self._parameters) def __getitem__(self, idx): - """Get top-level parameters by index.""" + """Get top-level parameters by index. + + Parameters + ---------- + idx : int or slice + The index, or slice, of the top-level Parameters to get. + + Returns + ------- + Parameter or list of Parameter + The Parameter, or list of Parameters, at `idx`. + """ # need to wrap this in a list for python 3 compatibility. return list(self._parameters.values())[idx] def __getattr__(self, name): - """Gives access to the contained objects as attributes.""" + """Give access to the contained objects as attributes. + + Parameters + ---------- + name : str + The name of the managed object to retrieve. + + Returns + ------- + object + The managed object registered under `name`. + + Raises + ------ + AttributeError + If no managed object is registered under `name`. + """ arg = self.get(name) if arg is None: raise AttributeError(name) @@ -374,7 +418,14 @@ def __getattr__(self, name): ) def __dir__(self): - """Return sorted list of attributes for this object.""" + """Return sorted list of attributes for this object. + + Returns + ------- + list of str + The sorted list of attribute names, including managed + objects. + """ rv = set(dir(type(self))) rv.update(self.__dict__) # self.get fetches looks up for items in all managed dictionaries. @@ -388,7 +439,28 @@ def __dir__(self): __managed = [] def __setattr__(self, name, value): - """Parameter access and object checking.""" + """Set an attribute, routing Parameter names to Parameter + values. + + If `name` matches a managed Parameter, the Parameter's value is + set rather than replacing the Parameter itself. Otherwise this + behaves like normal attribute assignment, except that a managed + non-Parameter object of that name may not be overwritten. + + Parameters + ---------- + name : str + The name of the attribute to set. + value + The value to assign. If `name` refers to a managed + Parameter, this may be a plain value or a Parameter, whose + value will be copied. + + Raises + ------ + AttributeError + If `name` refers to a managed, non-Parameter object. + """ if name in self._parameters: parameter = self._parameters[name] if isinstance(value, Parameter): @@ -405,11 +477,21 @@ def __setattr__(self, name, value): return def __delattr__(self, name): - """Delete parameters with del. + """Delete parameters with ``del``. This does not allow deletion of non-parameters, as this may require configuration changes that are not yet handled in a general way. + + Parameters + ---------- + name : str + The name of the Parameter to delete. + + Raises + ------ + AttributeError + If `name` refers to a managed, non-Parameter object. """ if name in self._parameters: self._remove_parameter(self._parameters[name]) @@ -423,7 +505,22 @@ def __delattr__(self, name): return def get(self, name, default=None): - """Get a managed object.""" + """Get a managed object. + + Parameters + ---------- + name : str + The name of the managed object to retrieve. + default : optional + The value to return if no managed object is found under + `name` (default None). + + Returns + ------- + object + The managed object registered under `name`, or `default` + if no such object exists. + """ for d in self.__managed: arg = d.get(name) if arg is not None: @@ -432,7 +529,13 @@ def get(self, name, default=None): return default def get_names(self): - """Get the names of managed parameters.""" + """Get the names of managed parameters. + + Returns + ------- + list of str + The names of the managed Parameters. + """ return [p.name for p in self._parameters.values()] @deprecated(getNames_deprecation_msg) @@ -447,7 +550,13 @@ def getNames(self): return self.get_names() def get_values(self): - """Get the values of managed parameters.""" + """Get the values of managed parameters. + + Returns + ------- + list + The values of the managed Parameters. + """ return [p.value for p in self._parameters.values()] @deprecated(getValues_deprecation_msg) @@ -471,13 +580,16 @@ def _add_object(self, obj, d, check=True): d The managed dictionary to store the object in. check - If True (default), a ValueError is raised an object of the - given name already exists. - + If True (default), a ValueError is raised if an object of + the given name already exists. - Raises ValueError if the object has no name. - Raises ValueError if the object has the same name as some other managed - object. + Raises + ------ + ValueError + If the object has no name. + ValueError + If the object has the same name as some other managed + object. """ # Check name if not obj.name: @@ -518,7 +630,10 @@ def _add_object(self, obj, d, check=True): def _remove_object(self, obj, d): """Remove an object from a managed dictionary. - Raises ValueError if obj is not part of the dictionary. + Raises + ------ + ValueError + If `obj` is not part of the dictionary. """ if obj not in d.values(): m = "'%s' is not part of the %s" % (obj, self.__class__.__name__) @@ -537,11 +652,13 @@ def _locate_managed_object(self, obj): obj The object to find. - - Returns a list of objects. The first member of the list is this object, - and each subsequent member is a sub-object of the previous one. The - last entry in the list is obj. If obj cannot be found, the list is - empty. + Returns + ------- + list + The list of objects. The first member of the list is this + object, and each subsequent member is a sub-object of the + previous one. The last entry in the list is `obj`. If `obj` + cannot be found, the list is empty. """ loc = [self] @@ -580,7 +697,10 @@ def _validate(self): This validates that contained Parameters and managed objects are valid. - Raises AttributeError if validation fails. + Raises + ------ + AttributeError + If validation fails. """ iterable = chain(self.__iter__(), self._iter_managed()) self._validate_others(iterable) @@ -597,7 +717,7 @@ class RecipeOrganizer(_recipeorganizer_interface, RecipeContainer): Restraints, as well as Equations that can be used in Constraint and Restraint equations. These constraints and Restraints can be placed at any level and a flattened list of them can be retrieved with the - _get_constraints and _get_restraints methods. + `_get_constraints` and `_get_restraints` methods. Attributes ---------- @@ -627,8 +747,10 @@ class RecipeOrganizer(_recipeorganizer_interface, RecipeContainer): values Variable values (read only). See get_values. - - Raises ValueError if the name is not a valid attribute identifier + Raises + ------ + ValueError + If the name is not a valid attribute identifier. """ def __init__(self, name): @@ -647,9 +769,12 @@ def _new_parameter(self, name, value, check=True): """Add a new Parameter to the container. This creates a new Parameter and adds it to the container using - the _add_parameter method. + the `_add_parameter` method. - Returns the Parameter. + Returns + ------- + Parameter + The newly created Parameter. """ p = Parameter(name, value) self._add_parameter(p, check) @@ -665,13 +790,16 @@ def _add_parameter(self, parameter, check=True): parameter The Parameter to be stored. check - If True (default), a ValueError is raised a Parameter of + If True (default), a ValueError is raised if a Parameter of the specified name has already been inserted. - - Raises ValueError if the Parameter has no name. - Raises ValueError if the Parameter has the same name as a contained - RecipeContainer. + Raises + ------ + ValueError + If the Parameter has no name. + ValueError + If the Parameter has the same name as a contained + RecipeContainer. """ # Store the Parameter RecipeContainer._add_object(self, parameter, self._parameters, check) @@ -683,14 +811,16 @@ def _add_parameter(self, parameter, check=True): def _remove_parameter(self, parameter): """Remove a parameter. - This de-registers the Parameter with the _eqfactory. The + This de-registers the Parameter with the `_eqfactory`. The Parameter will remain part of built equations. Note that constraints and restraints involving the Parameter are not modified. - Raises ValueError if parameter is not part of the - RecipeOrganizer. + Raises + ------ + ValueError + If `parameter` is not part of the RecipeOrganizer. """ self._remove_object(parameter, self._parameters) self._eqfactory.deRegisterBuilder(parameter.name) @@ -714,6 +844,11 @@ def register_calculator(self, calculator, argnames=None): The names of the arguments to `calculator` (list or None). If this is None, then the argument names will be extracted from the function. + + Returns + ------- + Equation + The callable Equation object wrapping `calculator`. """ self._eqfactory.registerOperator(calculator.name, calculator) self._add_object(calculator, self._calculators) @@ -767,11 +902,10 @@ def register_function(self, function, name=None, argnames=None): If this is None (default), then the argument names will be extracted from the function. - Note - ---- - The `name` and `argnames` args can be extracted from regular Python - functions (of type ), bound class methods, and callable - classes. + Returns + ------- + equation_object : Equation + The callable Equation object. Raises ------ @@ -782,10 +916,11 @@ def register_function(self, function, name=None, argnames=None): ValueError If function is an Equation object and name is None. - Returns - ------- - equation_object : Equation - The callable Equation object. + Notes + ----- + The `name` and `argnames` args can be extracted from regular Python + functions (of type ), bound class methods, and callable + classes. """ # If the function is an equation, we treat it specially. This is # required so that the objects observed by the root get observed if the @@ -891,6 +1026,11 @@ def register_string_function(self, function_str, name, func_params={}): A dictionary of Parameters, indexed by name, that are used in `function_str`, but not part of the FitRecipe (default {}). + Returns + ------- + equation_object : Equation + The callable Equation object. + Raises ------ ValueError @@ -898,11 +1038,6 @@ def register_string_function(self, function_str, name, func_params={}): managed object. ValueError If the function name is the name of another managed object. - - Returns - ------- - equation_object : Equation - The callable Equation object. """ # Build the equation instance. eq = get_equation_from_string( @@ -1099,8 +1234,10 @@ def remove_constraint(self, *pars): *pars : str or Parameter The names of Parameters or Parameters to unconstrain. - - Raises ValueError if the Parameter is not constrained. + Raises + ------ + ValueError + If the Parameter is not constrained. """ update = False for parameter in pars: @@ -1148,10 +1285,10 @@ def get_constrained_parmeters(self, recurse=False): this object are returned. If True, constrained Parameters in managed sub-objects are also included. - Return - ------ + Returns + ------- constrained_params : list of Parameter - A list of constrained managed Parameters in this object. + The list of constrained managed Parameters in this object. """ const = self._get_constraints(recurse) constrained_params = const.keys() @@ -1159,13 +1296,12 @@ def get_constrained_parmeters(self, recurse=False): @deprecated(getConstrainedPars_deprecation_msg) def getConstrainedPars(self, recurse=False): - """Get a list of constrained managed Parameters in this object. + """This function has been deprecated and will be removed in + version 4.0.0. - Parameters - ---------- - recurse - Recurse into managed objects and retrieve their constrained - Parameters as well (default False). + Please use + diffpy.srfit.fitbase.recipeorganizer.RecipeOrganizer.get_constrained_parmeters + instead. """ return self.get_constrained_parmeters(recurse=recurse) @@ -1377,9 +1513,10 @@ def clear_all_soft_bounds(self, recurse=False): Parameters ---------- - recurse - Recurse into managed objects and clear all restraints - found there as well. + recurse : bool, optional + If False (default), only restraints in this object are + cleared. If True, restraints in managed sub-objects are + also cleared. """ self.remove_soft_bounds(*self._restraints) if recurse: @@ -1429,7 +1566,10 @@ def _validate(self): This performs RecipeContainer validations. This validates contained Restraints and Constraints. - Raises AttributeError if validation fails. + Raises + ------ + AttributeError + If validation fails. """ RecipeContainer._validate(self) iterable = chain(self._restraints, self._constraints.values()) @@ -1449,7 +1589,7 @@ def _format_managed(self, prefix=""): Returns ------- list - List of formatted lines, one per each Parameter. + The list of formatted lines, one per each Parameter. """ lines = [] formatstr = "{: