From 5684c189d22baabc95811f94e98ac51697b3f292 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:52:33 +0800 Subject: [PATCH 01/65] Lint check & fix to python3 format (#18) * lint check and change files from python2 to python3 * pre-commit check for these files --- devutils/makesdist | 8 +- devutils/prep.py | 2 - diffpy/srmise/applications/extract.py | 797 ++++++++++++++---------- diffpy/srmise/applications/plot.py | 435 ++++++++----- diffpy/srmise/baselines/arbitrary.py | 56 +- diffpy/srmise/baselines/base.py | 2 +- diffpy/srmise/baselines/fromsequence.py | 81 ++- 7 files changed, 834 insertions(+), 547 deletions(-) diff --git a/devutils/makesdist b/devutils/makesdist index 1d81cf0..a7e2676 100644 --- a/devutils/makesdist +++ b/devutils/makesdist @@ -17,11 +17,11 @@ sys.path.insert(0, BASEDIR) from setup import versiondata timestamp = versiondata.getint('DEFAULT', 'timestamp') -print 'Run "setup.py sdist --formats=tar"', +print('Run "setup.py sdist --formats=tar"',) cmd_sdist = [sys.executable] + 'setup.py sdist --formats=tar'.split() ec = subprocess.call(cmd_sdist, cwd=BASEDIR, stdout=open(os.devnull, 'w')) if ec: sys.exit(ec) -print "[done]" +print("[done]") tarname = max(glob.glob(BASEDIR + '/dist/*.tar'), key=os.path.getmtime) @@ -36,8 +36,8 @@ def fixtarinfo(tinfo): return tinfo -print 'Filter %s --> %s.gz' % (2 * (os.path.basename(tarname),)), +print('Filter %s --> %s.gz' % (2 * (os.path.basename(tarname),)),) for ti in tfin: tfout.addfile(fixtarinfo(ti), tfin.extractfile(ti)) os.remove(tarname) -print "[done]" +print("[done]") diff --git a/devutils/prep.py b/devutils/prep.py index c63ce0f..b765f7d 100644 --- a/devutils/prep.py +++ b/devutils/prep.py @@ -111,5 +111,3 @@ def rm(directory, filerestr): print "==== Scrubbing Endlines ====" # All *.srmise and *.pwa files in examples directory. scrubeol("../doc/examples/output", r".*(\.srmise|\.pwa)") - - diff --git a/diffpy/srmise/applications/extract.py b/diffpy/srmise/applications/extract.py index 5bbc646..59ca6b2 100755 --- a/diffpy/srmise/applications/extract.py +++ b/diffpy/srmise/applications/extract.py @@ -20,254 +20,409 @@ def main(): """Default SrMise entry-point.""" - usage = ("usage: %prog pdf_file [options]\n" - "pdf_file is a file containing a PDF (accepts several " - "common formats), or a .srmise file.") + usage = ( + "usage: %prog pdf_file [options]\n" + "pdf_file is a file containing a PDF (accepts several " + "common formats), or a .srmise file." + ) from diffpy.srmise import __version__ - version = "diffpy.srmise "+__version__ - - descr = ("The SrMise package is a tool to aid extracting and fitting peaks " - "that comprise a pair distribution function. This script exposes " - "basic peak extraction functionality. For many PDFs it is " - "sufficient to specify the range, baseline, and sometimes an ad " - "hoc uncertainty. See the discussion of these options below for " - "further guidance.") - - epilog = ("Options set above override those from an existing .srmise " - "file, as well as the usual defaults summarized here.\n\n" - "Defaults (when qmax > 0)\n" - "------------------------\n" - "baseline - None (identically 0).\n" - "dg - The uncertainty reported in the PDF (if any), otherwise " - "5% of maximum value of PDF.\n" - "nyquist - True\n" - "range - All the data\n" - "cres - The Nyquist rate.\n" - "supersample - 4.0\n" - "scale - (Deprecated) False\n\n" - "Defaults (when qmax = 0)\n" - "------------------------\n" - "baseline - as above\n" - "dg - as above\n" - "nyquist - False (and no effect if True)\n" - "range - as above\n" - "cres - Four times the average distance between data points\n" - "supersample - Parameter has no effect.\n" - "scale - (Deprecated) False, and no effect if True\n\n" - "Known issues\n" - "------------\n" - "1) Peak extraction works best when the data are moderately " - "oversampled first. When qmax > 0 this is handled " - "automatically, but when qmax = 0 no resampling of any kind is " - "performed.\n" - "2) Peak extraction performed on a PDF file and a .srmise file " - "derived from that data with identical extraction parameters " - "can give different results even on the same platform. This is " - "because the original data may undergo some processing before it " - "can be saved by SrMise. For consistent results, always specify " - "the original PDF, or always load the PDF from a .srmise file " - "you save before performing any peak extraction on that data.\n" - "3) Liveplotting depends on the matplotlib backend, and doesn't " - "implement an idle handler, so interaction with its window will " - "likely cause a freeze.") + + version = "diffpy.srmise " + __version__ + + descr = ( + "The SrMise package is a tool to aid extracting and fitting peaks " + "that comprise a pair distribution function. This script exposes " + "basic peak extraction functionality. For many PDFs it is " + "sufficient to specify the range, baseline, and sometimes an ad " + "hoc uncertainty. See the discussion of these options below for " + "further guidance." + ) + + epilog = ( + "Options set above override those from an existing .srmise " + "file, as well as the usual defaults summarized here.\n\n" + "Defaults (when qmax > 0)\n" + "------------------------\n" + "baseline - None (identically 0).\n" + "dg - The uncertainty reported in the PDF (if any), otherwise " + "5% of maximum value of PDF.\n" + "nyquist - True\n" + "range - All the data\n" + "cres - The Nyquist rate.\n" + "supersample - 4.0\n" + "scale - (Deprecated) False\n\n" + "Defaults (when qmax = 0)\n" + "------------------------\n" + "baseline - as above\n" + "dg - as above\n" + "nyquist - False (and no effect if True)\n" + "range - as above\n" + "cres - Four times the average distance between data points\n" + "supersample - Parameter has no effect.\n" + "scale - (Deprecated) False, and no effect if True\n\n" + "Known issues\n" + "------------\n" + "1) Peak extraction works best when the data are moderately " + "oversampled first. When qmax > 0 this is handled " + "automatically, but when qmax = 0 no resampling of any kind is " + "performed.\n" + "2) Peak extraction performed on a PDF file and a .srmise file " + "derived from that data with identical extraction parameters " + "can give different results even on the same platform. This is " + "because the original data may undergo some processing before it " + "can be saved by SrMise. For consistent results, always specify " + "the original PDF, or always load the PDF from a .srmise file " + "you save before performing any peak extraction on that data.\n" + "3) Liveplotting depends on the matplotlib backend, and doesn't " + "implement an idle handler, so interaction with its window will " + "likely cause a freeze." + ) # TODO: Move to argparse (though not in 2.6 by default) to handle # variable-length options without callbacks. Longterm, the major # value is using the same option to specify a baseline that should # use estimation vs. one that should use explicitly provided pars. - parser = OptionParser(usage=usage, description=descr, epilog=epilog, - version=version, - formatter=IndentedHelpFormatterWithNL()) - - parser.set_defaults(plot=False, liveplot=False, wait=False, - performextraction=True, verbosity="warning") - dg_defaults = {'absolute':None, 'data':None, 'max-fraction':.05, - 'ptp-fraction':.05, 'dG-fraction':1.} - - parser.add_option("--extract", action="store_true", - dest="performextraction", - help="[Default] Perform extraction.") - parser.add_option("--no-extract", action="store_false", - dest="performextraction", - help="Do not perform extraction.") - parser.add_option("--range", nargs=2, dest="rng", type="float", - metavar="rmin rmax", - help="Extract over the range (rmin, rmax).") - parser.add_option("--qmax", dest="qmax", type="string", metavar="QMAX", - help="Model peaks with this maximum q value.") - parser.add_option("--nyquist", action="store_true", dest="nyquist", - help="Use Nyquist resampling if qmax > 0.") - parser.add_option("--no-nyquist", action="store_false", dest="nyquist", - help="Do not use Nyquist resampling.") - parser.add_option("--pf", dest="peakfunction", metavar="PF", - help="Fit peak function PF defined in " - "diffpy.srmise.peaks, e.g. " - "'GaussianOverR(maxwidth=0.7)'") - parser.add_option("--cres", dest="cres", type="float", metavar="cres", - help="Clustering resolution.") - parser.add_option("--supersample", dest="supersample", type="float", - metavar="SS", - help="Minimum initial oversampling rate as multiple of " - "Nyquist rate.") - parser.add_option("--me", "-m", dest="modelevaluator", metavar="ME", - help="ModelEvaluator defined in " - "diffpy.srmise.modelevaluators, e.g. 'AIC'") - - group = OptionGroup(parser, "Baseline Options", - "SrMise cannot determine the appropriate type of " - "baseline (e.g. crystalline vs. some nanoparticle) " - "solely from the data, so the user should specify the " - "appropriate type and/or parameters. (Default is " - "identically 0, which is unphysical.) SrMise keeps the " - "PDF baseline fixed at its initial value until the " - "final stages of peak extraction, so results are " - "frequently conditioned on that choice. (See the " - "SrMise documentation for details.) A good estimate " - "is therefore important for best results. SrMise can " - "estimate initial parameters from the data for linear " - "baselines in some situations (all peaks are positive, " - "and the degree of overlap in the region of extraction " - "is not too great), but in most cases it is best to " - "provide reasonable initial parameters. Run 'srmise " - "pdf_file.gr [baseline_option] --no-extract --plot' " - "for different values of the parameters for rapid " - "visual estimation.") - group.add_option("--baseline", dest="baseline", metavar="BL", - help="Estimate baseline from baseline function BL " - "defined in diffpy.srmise.baselines, e.g. " - "'Polynomial(degree=1)'. All parameters are free. " - "(Many POSIX shells attempt to interpret the " - "parentheses, and on these shells the option should " - "be surrounded by quotation marks.)" ) - group.add_option("--bcrystal", dest="bcrystal", type="string", - metavar="rho0[c]", - help="Use linear baseline defined by crystal number " - "density rho0. Append 'c' to make parameter " - "constant. Equivalent to " - "'--bpoly1 -4*pi*rho0[c] 0c'.") - group.add_option("--bsrmise", dest="bsrmise", type="string", metavar="file", - help="Use baseline from specified .srmise file.") - group.add_option("--bpoly0", dest="bpoly0", type="string", metavar="a0[c]", - help="Use constant baseline given by y=a0. " - "Append 'c' to make parameter constant.") - group.add_option("--bpoly1", dest="bpoly1", type="string", nargs=2, - metavar="a1[c] a0[c]", - help="Use baseline given by y=a1*x + a0. Append 'c' to " - "make parameter constant.") - group.add_option("--bpoly2", dest="bpoly2", type="string", nargs=3, - metavar="a2[c] a1[c] a0[c]", - help="Use baseline given by y=a2*x^2+a1*x + a0. Append " - "'c' to make parameter constant.") - group.add_option("--bseq", dest="bseq", type="string", metavar="FILE", - help="Use baseline interpolated from x,y values in FILE. " - "This baseline has no free parameters.") - group.add_option("--bspherical", dest="bspherical", type="string", nargs=2, - metavar="s[c] r[c]", - help="Use spherical nanoparticle baseline with scale s " - "and radius r. Append 'c' to make parameter " - "constant.") + parser = OptionParser( + usage=usage, + description=descr, + epilog=epilog, + version=version, + formatter=IndentedHelpFormatterWithNL(), + ) + + parser.set_defaults( + plot=False, + liveplot=False, + wait=False, + performextraction=True, + verbosity="warning", + ) + dg_defaults = { + "absolute": None, + "data": None, + "max-fraction": 0.05, + "ptp-fraction": 0.05, + "dG-fraction": 1.0, + } + + parser.add_option( + "--extract", + action="store_true", + dest="performextraction", + help="[Default] Perform extraction.", + ) + parser.add_option( + "--no-extract", + action="store_false", + dest="performextraction", + help="Do not perform extraction.", + ) + parser.add_option( + "--range", + nargs=2, + dest="rng", + type="float", + metavar="rmin rmax", + help="Extract over the range (rmin, rmax).", + ) + parser.add_option( + "--qmax", + dest="qmax", + type="string", + metavar="QMAX", + help="Model peaks with this maximum q value.", + ) + parser.add_option( + "--nyquist", + action="store_true", + dest="nyquist", + help="Use Nyquist resampling if qmax > 0.", + ) + parser.add_option( + "--no-nyquist", + action="store_false", + dest="nyquist", + help="Do not use Nyquist resampling.", + ) + parser.add_option( + "--pf", + dest="peakfunction", + metavar="PF", + help="Fit peak function PF defined in " + "diffpy.srmise.peaks, e.g. " + "'GaussianOverR(maxwidth=0.7)'", + ) + parser.add_option( + "--cres", + dest="cres", + type="float", + metavar="cres", + help="Clustering resolution.", + ) + parser.add_option( + "--supersample", + dest="supersample", + type="float", + metavar="SS", + help="Minimum initial oversampling rate as multiple of " "Nyquist rate.", + ) + parser.add_option( + "--me", + "-m", + dest="modelevaluator", + metavar="ME", + help="ModelEvaluator defined in " "diffpy.srmise.modelevaluators, e.g. 'AIC'", + ) + + group = OptionGroup( + parser, + "Baseline Options", + "SrMise cannot determine the appropriate type of " + "baseline (e.g. crystalline vs. some nanoparticle) " + "solely from the data, so the user should specify the " + "appropriate type and/or parameters. (Default is " + "identically 0, which is unphysical.) SrMise keeps the " + "PDF baseline fixed at its initial value until the " + "final stages of peak extraction, so results are " + "frequently conditioned on that choice. (See the " + "SrMise documentation for details.) A good estimate " + "is therefore important for best results. SrMise can " + "estimate initial parameters from the data for linear " + "baselines in some situations (all peaks are positive, " + "and the degree of overlap in the region of extraction " + "is not too great), but in most cases it is best to " + "provide reasonable initial parameters. Run 'srmise " + "pdf_file.gr [baseline_option] --no-extract --plot' " + "for different values of the parameters for rapid " + "visual estimation.", + ) + group.add_option( + "--baseline", + dest="baseline", + metavar="BL", + help="Estimate baseline from baseline function BL " + "defined in diffpy.srmise.baselines, e.g. " + "'Polynomial(degree=1)'. All parameters are free. " + "(Many POSIX shells attempt to interpret the " + "parentheses, and on these shells the option should " + "be surrounded by quotation marks.)", + ) + group.add_option( + "--bcrystal", + dest="bcrystal", + type="string", + metavar="rho0[c]", + help="Use linear baseline defined by crystal number " + "density rho0. Append 'c' to make parameter " + "constant. Equivalent to " + "'--bpoly1 -4*pi*rho0[c] 0c'.", + ) + group.add_option( + "--bsrmise", + dest="bsrmise", + type="string", + metavar="file", + help="Use baseline from specified .srmise file.", + ) + group.add_option( + "--bpoly0", + dest="bpoly0", + type="string", + metavar="a0[c]", + help="Use constant baseline given by y=a0. " + "Append 'c' to make parameter constant.", + ) + group.add_option( + "--bpoly1", + dest="bpoly1", + type="string", + nargs=2, + metavar="a1[c] a0[c]", + help="Use baseline given by y=a1*x + a0. Append 'c' to " + "make parameter constant.", + ) + group.add_option( + "--bpoly2", + dest="bpoly2", + type="string", + nargs=3, + metavar="a2[c] a1[c] a0[c]", + help="Use baseline given by y=a2*x^2+a1*x + a0. Append " + "'c' to make parameter constant.", + ) + group.add_option( + "--bseq", + dest="bseq", + type="string", + metavar="FILE", + help="Use baseline interpolated from x,y values in FILE. " + "This baseline has no free parameters.", + ) + group.add_option( + "--bspherical", + dest="bspherical", + type="string", + nargs=2, + metavar="s[c] r[c]", + help="Use spherical nanoparticle baseline with scale s " + "and radius r. Append 'c' to make parameter " + "constant.", + ) parser.add_option_group(group) - - group = OptionGroup(parser, "Uncertainty Options", - "Ideally a PDF reports the accurate experimentally " - "determined uncertainty. In practice, many PDFs " - "report none, while for others the reported values " - "are not necessarily reliable. (If in doubt, ask your " - "friendly neighborhood diffraction expert!) Even when " - "uncertainties are accurate, it can be " - "pragmatically useful to see how the results of " - "peak extraction change when assuming a different " - "value. Nevertheless, the primary determinant of " - "model complexity in SrMise is the uncertainty, so an " - "ad hoc uncertainty yields ad hoc model complexity. " - "See the SrMise documentation for further discussion, " - "including methods to mitigate this issue with " - "multimodel selection.") - group.add_option("--dg-mode", dest="dg_mode", type="choice", - choices=['absolute', 'data', 'max-fraction', - 'ptp-fraction', 'dG-fraction'], - help="Define how values passed to '--dg' are treated. " - "Possible values are: \n" - "'absolute' - The actual uncertainty in the PDF.\n" - "'max-fraction' - Fraction of max value in PDF.\n" - "'ptp-fraction' - Fraction of max minus min value " - "in the PDF.\n" - "'dG-fraction' - Fraction of dG reported by PDF.\n" - "If '--dg' is specified but mode is not, then mode " - "ia absolute. Otherwise, 'dG-fraction' is default " - "if the PDF reports uncertaintes, and 'max-fraction' " - "ia default if it does not.") - group.add_option("--dg", dest="dg", type="float", - help="Perform extraction assuming uncertainty dg. " - "Defaults depend on --dg-mode as follows:\n" - "'absolute'=%s\n" - "'max-fraction'=%s\n" - "'ptp-fraction'=%s\n" - "'dG-fraction'=%s" %(dg_defaults['absolute'], - dg_defaults['max-fraction'], - dg_defaults['ptp-fraction'], - dg_defaults['dG-fraction'])) -# group.add_option("--multimodel", nargs=3, dest="multimodel", type="float", -# metavar="dg_min dg_max n", -# help="Generate n models from dg_min to dg_max (given by " -# "--dg-mode) and perform multimodel analysis. " -# "This overrides any value given for --dg") + group = OptionGroup( + parser, + "Uncertainty Options", + "Ideally a PDF reports the accurate experimentally " + "determined uncertainty. In practice, many PDFs " + "report none, while for others the reported values " + "are not necessarily reliable. (If in doubt, ask your " + "friendly neighborhood diffraction expert!) Even when " + "uncertainties are accurate, it can be " + "pragmatically useful to see how the results of " + "peak extraction change when assuming a different " + "value. Nevertheless, the primary determinant of " + "model complexity in SrMise is the uncertainty, so an " + "ad hoc uncertainty yields ad hoc model complexity. " + "See the SrMise documentation for further discussion, " + "including methods to mitigate this issue with " + "multimodel selection.", + ) + group.add_option( + "--dg-mode", + dest="dg_mode", + type="choice", + choices=["absolute", "data", "max-fraction", "ptp-fraction", "dG-fraction"], + help="Define how values passed to '--dg' are treated. " + "Possible values are: \n" + "'absolute' - The actual uncertainty in the PDF.\n" + "'max-fraction' - Fraction of max value in PDF.\n" + "'ptp-fraction' - Fraction of max minus min value " + "in the PDF.\n" + "'dG-fraction' - Fraction of dG reported by PDF.\n" + "If '--dg' is specified but mode is not, then mode " + "ia absolute. Otherwise, 'dG-fraction' is default " + "if the PDF reports uncertaintes, and 'max-fraction' " + "ia default if it does not.", + ) + group.add_option( + "--dg", + dest="dg", + type="float", + help="Perform extraction assuming uncertainty dg. " + "Defaults depend on --dg-mode as follows:\n" + "'absolute'=%s\n" + "'max-fraction'=%s\n" + "'ptp-fraction'=%s\n" + "'dG-fraction'=%s" + % ( + dg_defaults["absolute"], + dg_defaults["max-fraction"], + dg_defaults["ptp-fraction"], + dg_defaults["dG-fraction"], + ), + ) + # group.add_option("--multimodel", nargs=3, dest="multimodel", type="float", + # metavar="dg_min dg_max n", + # help="Generate n models from dg_min to dg_max (given by " + # "--dg-mode) and perform multimodel analysis. " + # "This overrides any value given for --dg") parser.add_option_group(group) - - group = OptionGroup(parser, "Saving and Plotting Options", - "") - group.add_option("--pwa", dest="pwafile", metavar="FILE", - help="Save summary of result to FILE (.pwa format).") - group.add_option("--save", dest="savefile", metavar="FILE", - help="Save result of extraction to FILE (.srmise " - "format).") - group.add_option("--plot", "-p", action="store_true", dest="plot", - help="Plot extracted peaks.") - group.add_option("--liveplot", "-l", action="store_true", dest="liveplot", - help="(Experimental) Plot extracted peaks when fitting.") - group.add_option("--wait", "-w", action="store_true", dest="wait", - help="(Experimental) When using liveplot wait for user " - "after plotting.") + group = OptionGroup(parser, "Saving and Plotting Options", "") + group.add_option( + "--pwa", + dest="pwafile", + metavar="FILE", + help="Save summary of result to FILE (.pwa format).", + ) + group.add_option( + "--save", + dest="savefile", + metavar="FILE", + help="Save result of extraction to FILE (.srmise " "format).", + ) + group.add_option( + "--plot", "-p", action="store_true", dest="plot", help="Plot extracted peaks." + ) + group.add_option( + "--liveplot", + "-l", + action="store_true", + dest="liveplot", + help="(Experimental) Plot extracted peaks when fitting.", + ) + group.add_option( + "--wait", + "-w", + action="store_true", + dest="wait", + help="(Experimental) When using liveplot wait for user " "after plotting.", + ) parser.add_option_group(group) - - group = OptionGroup(parser, "Verbosity Options", - "Control detail printed to console.") - group.add_option("--informative", "-i", action="store_const", const="info", - dest="verbosity", - help="Summary of progress.") - group.add_option("--quiet", "-q", action="store_const", const="warning", - dest="verbosity", - help="[Default] Show minimal summary.") - group.add_option("--silent", "-s", action="store_const", const="critical", - dest="verbosity", - help="No non-critical output.") - group.add_option("--verbose", "-v", action="store_const", const="debug", - dest="verbosity", - help="Show verbose output.") + group = OptionGroup( + parser, "Verbosity Options", "Control detail printed to console." + ) + group.add_option( + "--informative", + "-i", + action="store_const", + const="info", + dest="verbosity", + help="Summary of progress.", + ) + group.add_option( + "--quiet", + "-q", + action="store_const", + const="warning", + dest="verbosity", + help="[Default] Show minimal summary.", + ) + group.add_option( + "--silent", + "-s", + action="store_const", + const="critical", + dest="verbosity", + help="No non-critical output.", + ) + group.add_option( + "--verbose", + "-v", + action="store_const", + const="debug", + dest="verbosity", + help="Show verbose output.", + ) parser.add_option_group(group) - group = OptionGroup(parser, "Deprecated Options", - "Not for general use.") - group.add_option("--scale", action="store_true", dest="scale", - help="(Deprecated) Scale supersampled uncertainties by " - "sqrt(oversampling) in intermediate steps when " - "Nyquist sampling.") - group.add_option("--no-scale", action="store_false", dest="scale", - help="(Deprecated) Never rescale uncertainties.") + group = OptionGroup(parser, "Deprecated Options", "Not for general use.") + group.add_option( + "--scale", + action="store_true", + dest="scale", + help="(Deprecated) Scale supersampled uncertainties by " + "sqrt(oversampling) in intermediate steps when " + "Nyquist sampling.", + ) + group.add_option( + "--no-scale", + action="store_false", + dest="scale", + help="(Deprecated) Never rescale uncertainties.", + ) parser.add_option_group(group) - (options, args) = parser.parse_args() if len(args) != 1: - parser.error("Exactly one argument required. \n"+usage) - + parser.error("Exactly one argument required. \n" + usage) from diffpy.srmise import srmiselog + srmiselog.setlevel(options.verbosity) from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction @@ -275,30 +430,34 @@ def main(): if options.peakfunction is not None: from diffpy.srmise import peaks + try: - options.peakfunction = eval("peaks."+options.peakfunction) - except Exception, err: - print err - print "Could not create peak function '%s'. Exiting." \ - %options.peakfunction + options.peakfunction = eval("peaks." + options.peakfunction) + except Exception as err: + print(err) + print( + "Could not create peak function '%s'. Exiting." % options.peakfunction + ) return if options.modelevaluator is not None: from diffpy.srmise import modelevaluators + try: - options.modelevaluator = \ - eval("modelevaluators."+options.modelevaluator) - except Exception, err: - print err - print "Could not find ModelEvaluator '%s'. Exiting." \ - %options.modelevaluator + options.modelevaluator = eval("modelevaluators." + options.modelevaluator) + except Exception as err: + print(err) + print( + "Could not find ModelEvaluator '%s'. Exiting." % options.modelevaluator + ) return if options.bcrystal is not None: from diffpy.srmise.baselines import Polynomial + bl = Polynomial(degree=1) - options.baseline = parsepars(bl, [options.bcrystal, '0c']) - options.baseline.pars[0] = -4*np.pi*options.baseline.pars[0] + options.baseline = parsepars(bl, [options.bcrystal, "0c"]) + options.baseline.pars[0] = -4 * np.pi * options.baseline.pars[0] elif options.bsrmise is not None: # use baseline from existing file blext = PDFPeakExtraction() @@ -306,31 +465,37 @@ def main(): options.baseline = blext.extracted.baseline elif options.bpoly0 is not None: from diffpy.srmise.baselines import Polynomial + bl = Polynomial(degree=0) options.baseline = parsepars(bl, [options.bpoly0]) elif options.bpoly1 is not None: from diffpy.srmise.baselines import Polynomial + bl = Polynomial(degree=1) options.baseline = parsepars(bl, options.bpoly1) elif options.bpoly2 is not None: from diffpy.srmise.baselines import Polynomial + bl = Polynomial(degree=2) options.baseline = parsepars(bl, options.bpoly2) elif options.bseq is not None: from diffpy.srmise.baselines import FromSequence + bl = FromSequence(options.bseq) options.baseline = bl.actualize([], "internal") elif options.bspherical is not None: from diffpy.srmise.baselines import NanoSpherical + bl = NanoSpherical() options.baseline = parsepars(bl, options.bspherical) elif options.baseline is not None: from diffpy.srmise import baselines + try: - options.baseline = eval("baselines."+options.baseline) - except Exception, err: - print err - print "Could not create baseline '%s'. Exiting." %options.baseline + options.baseline = eval("baselines." + options.baseline) + except Exception as err: + print(err) + print("Could not create baseline '%s'. Exiting." % options.baseline) return filename = args[0] @@ -359,17 +524,19 @@ def main(): if options.dg is None: options.dg = dg_defaults[options.dg_mode] if options.dg_mode == "absolute": - pdict["effective_dy"] = options.dg*np.ones(len(ext.x)) + pdict["effective_dy"] = options.dg * np.ones(len(ext.x)) elif options.dg_mode == "max-fraction": - pdict["effective_dy"] = options.dg*ext.y.max()*np.ones(len(ext.x)) + pdict["effective_dy"] = options.dg * ext.y.max() * np.ones(len(ext.x)) elif options.dg_mode == "ptp-fraction": - pdict["effective_dy"] = options.dg*ext.y.ptp()*np.ones(len(ext.y)) + pdict["effective_dy"] = options.dg * ext.y.ptp() * np.ones(len(ext.y)) elif options.dg_mode == "dG-fraction": - pdict["effective_dy"] = options.dg*ext.dy + pdict["effective_dy"] = options.dg * ext.dy if options.rng is not None: pdict["rng"] = list(options.rng) if options.qmax is not None: - pdict["qmax"] = options.qmax if options.qmax == "automatic" else float(options.qmax) + pdict["qmax"] = ( + options.qmax if options.qmax == "automatic" else float(options.qmax) + ) if options.nyquist is not None: pdict["nyquist"] = options.nyquist if options.supersample is not None: @@ -381,6 +548,7 @@ def main(): if options.liveplot: from diffpy.srmise import srmiselog + srmiselog.liveplotting(True, options.wait) ext.setvars(**pdict) @@ -392,30 +560,30 @@ def main(): if options.savefile is not None: try: ext.write(options.savefile) - except SrMiseFileError, err: - print err - print "Could not save result to '%s'." %options.savefile - + except SrMiseFileError as err: + print(err) + print("Could not save result to '%s'." % options.savefile) if options.pwafile is not None: try: ext.writepwa(options.pwafile) - except SrMiseFileError, err: - print err - print "Could not save pwa summary to '%s'." %options.pwafile + except SrMiseFileError as err: + print(err) + print("Could not save pwa summary to '%s'." % options.pwafile) - - print ext + print(ext) if cov is not None: - print cov + print(cov) if options.plot: from diffpy.srmise.applications.plot import makeplot + makeplot(ext) plt.show() elif options.liveplot: plt.show() + def parsepars(mp, parseq): """Return actualized model from sequence of strings. @@ -430,7 +598,7 @@ def parsepars(mp, parseq): pars = [] free = [] for p in parseq: - if p[-1] == 'c': + if p[-1] == "c": pars.append(float(p[0:-1])) free.append(False) else: @@ -448,60 +616,63 @@ def parsepars(mp, parseq): class IndentedHelpFormatterWithNL(IndentedHelpFormatter): - def _format_text(self, text): - if not text: return "" - text_width = self.width - self.current_indent - indent = " "*self.current_indent -# the above is still the same - bits = text.split('\n') - formatted_bits = [ - textwrap.fill(bit, - text_width, - initial_indent=indent, - subsequent_indent=indent) - for bit in bits] - result = "\n".join(formatted_bits) + "\n" - return result - - def format_option(self, option): - # The help for each option consists of two parts: - # * the opt strings and metavars - # eg. ("-x", or "-fFILENAME, --file=FILENAME") - # * the user-supplied help string - # eg. ("turn on expert mode", "read data from FILENAME") - # - # If possible, we write both of these on the same line: - # -x turn on expert mode - # - # But if the opt string list is too long, we put the help - # string on a second line, indented to the same column it would - # start in if it fit on the first line. - # -fFILENAME, --file=FILENAME - # read data from FILENAME - result = [] - opts = self.option_strings[option] - opt_width = self.help_position - self.current_indent - 2 - if len(opts) > opt_width: - opts = "%*s%s\n" % (self.current_indent, "", opts) - indent_first = self.help_position - else: # start help on same line as opts - opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) - indent_first = 0 - result.append(opts) - if option.help: - help_text = self.expand_default(option) -# Everything is the same up through here - help_lines = [] - for para in help_text.split("\n"): - help_lines.extend(textwrap.wrap(para, self.help_width)) -# Everything is the same after here - result.append("%*s%s\n" % ( - indent_first, "", help_lines[0])) - result.extend(["%*s%s\n" % (self.help_position, "", line) - for line in help_lines[1:]]) - elif opts[-1] != "\n": - result.append("\n") - return "".join(result) + def _format_text(self, text): + if not text: + return "" + text_width = self.width - self.current_indent + indent = " " * self.current_indent + # the above is still the same + bits = text.split("\n") + formatted_bits = [ + textwrap.fill( + bit, text_width, initial_indent=indent, subsequent_indent=indent + ) + for bit in bits + ] + result = "\n".join(formatted_bits) + "\n" + return result + + def format_option(self, option): + # The help for each option consists of two parts: + # * the opt strings and metavars + # eg. ("-x", or "-fFILENAME, --file=FILENAME") + # * the user-supplied help string + # eg. ("turn on expert mode", "read data from FILENAME") + # + # If possible, we write both of these on the same line: + # -x turn on expert mode + # + # But if the opt string list is too long, we put the help + # string on a second line, indented to the same column it would + # start in if it fit on the first line. + # -fFILENAME, --file=FILENAME + # read data from FILENAME + result = [] + opts = self.option_strings[option] + opt_width = self.help_position - self.current_indent - 2 + if len(opts) > opt_width: + opts = "%*s%s\n" % (self.current_indent, "", opts) + indent_first = self.help_position + else: # start help on same line as opts + opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) + indent_first = 0 + result.append(opts) + if option.help: + help_text = self.expand_default(option) + # Everything is the same up through here + help_lines = [] + for para in help_text.split("\n"): + help_lines.extend(textwrap.wrap(para, self.help_width)) + # Everything is the same after here + result.append("%*s%s\n" % (indent_first, "", help_lines[0])) + result.extend( + ["%*s%s\n" % (self.help_position, "", line) for line in help_lines[1:]] + ) + elif opts[-1] != "\n": + result.append("\n") + return "".join(result) + + ### End class if __name__ == "__main__": diff --git a/diffpy/srmise/applications/plot.py b/diffpy/srmise/applications/plot.py index b696aa8..14f167e 100755 --- a/diffpy/srmise/applications/plot.py +++ b/diffpy/srmise/applications/plot.py @@ -18,6 +18,7 @@ import matplotlib.pyplot as plt import mpl_toolkits.axisartist as AA import numpy as np +from matplotlib.ticker import MultipleLocator from mpl_toolkits.axes_grid1 import make_axes_locatable from mpl_toolkits.axes_grid1.inset_locator import inset_axes @@ -27,49 +28,63 @@ # For a given figure, returns a label of interest labeldict = {} -default_gobs_style = {'color' : 'b', 'linestyle' : '', - 'markeredgecolor' : 'b', 'marker' : 'o', - 'markerfacecolor' : 'none', 'markersize' : 4} - -default_gfit_style = {'color' : 'g'} -default_gind_style = {'facecolor' : 'green', 'alpha' : 0.2} +default_gobs_style = { + "color": "b", + "linestyle": "", + "markeredgecolor": "b", + "marker": "o", + "markerfacecolor": "none", + "markersize": 4, +} + +default_gfit_style = {"color": "g"} +default_gind_style = {"facecolor": "green", "alpha": 0.2} default_gres_style = {} default_ep_style = {} default_ip_style = {} -default_dg_style = {'linestyle' : 'none', 'color' : 'black', - 'marker' : 'o', 'markerfacecolor' : 'black', - 'markeredgecolor' : 'black', - 'markersize' : 1, 'antialiased': False} +default_dg_style = { + "linestyle": "none", + "color": "black", + "marker": "o", + "markerfacecolor": "black", + "markeredgecolor": "black", + "markersize": 1, + "antialiased": False, +} def setfigformat(figsize): from matplotlib import rc - rc('legend', numpoints=2) - rc('figure', figsize=figsize) - rc('axes', titlesize=12, labelsize=11) - rc('xtick', labelsize=10) - rc('ytick', labelsize=10) - rc('lines', linewidth=0.75, markeredgewidth=0.5) + + rc("legend", numpoints=2) + rc("figure", figsize=figsize) + rc("axes", titlesize=12, labelsize=11) + rc("xtick", labelsize=10) + rc("ytick", labelsize=10) + rc("lines", linewidth=0.75, markeredgewidth=0.5) return + def gr_formataxis(ax=None): - if ax is None: ax = plt.gca() + if ax is None: + ax = plt.gca() ax.xaxis.set_minor_locator(MultipleLocator(1)) - ax.yaxis.set_label_position('left') + ax.yaxis.set_label_position("left") ax.yaxis.tick_left() - ax.yaxis.set_ticks_position('both') + ax.yaxis.set_ticks_position("both") return + def comparepositions(ppe, ip=None, **kwds): ax = kwds.get("ax", plt.gca()) - base = kwds.get("base", 0.) - yideal = kwds.get("yideal", -1.) - yext = kwds.get("yext", 1.) + base = kwds.get("base", 0.0) + yideal = kwds.get("yideal", -1.0) + yext = kwds.get("yext", 1.0) ep_style = kwds.get("ep_style", default_ep_style) ip_style = kwds.get("ip_style", default_ip_style) - yideal_label = kwds.get("yideal_label", r'ideal') - yext_label = kwds.get("yext_label", r'found') + yideal_label = kwds.get("yideal_label", r"ideal") + yext_label = kwds.get("yext_label", r"found") pmin = kwds.get("pmin", -np.inf) pmax = kwds.get("pmax", np.inf) @@ -77,39 +92,39 @@ def comparepositions(ppe, ip=None, **kwds): ep = [p for p in ep if p >= pmin and p <= pmax] if ip is not None: - xi = np.NaN + np.zeros(3*len(ip)) + xi = np.NaN + np.zeros(3 * len(ip)) xi[0::3] = ip xi[1::3] = ip yi = np.zeros_like(xi) + base yi[1::3] += yideal - plt.plot(xi, yi, 'b', lw=1.5, **ip_style) + plt.plot(xi, yi, "b", lw=1.5, **ip_style) - xe = np.NaN + np.zeros(3*len(ep)) + xe = np.NaN + np.zeros(3 * len(ep)) xe[0::3] = ep xe[1::3] = ep ye = np.zeros_like(xe) + base ye[1::3] += yext - plt.plot(xe, ye, 'g', lw=1.5, **ep_style) + plt.plot(xe, ye, "g", lw=1.5, **ep_style) if ip is not None: yb = (base, base) - plt.axhline(base, linestyle=":", color="k" ) - ax.yaxis.set_ticks([base+.5*yideal, base+.5*yext]) + plt.axhline(base, linestyle=":", color="k") + ax.yaxis.set_ticks([base + 0.5 * yideal, base + 0.5 * yext]) ax.yaxis.set_ticklabels([yideal_label, yext_label]) else: - ax.yaxis.set_ticks([base+.5*yext]) + ax.yaxis.set_ticks([base + 0.5 * yext]) ax.yaxis.set_ticklabels([yext_label]) # Set ylim explicitly, for case where yext is empty. if ip is not None: - plt.ylim(base+yideal, base+yext) + plt.ylim(base + yideal, base + yext) else: - plt.ylim(base, base+yext) + plt.ylim(base, base + yext) for tick in ax.yaxis.get_major_ticks(): tick.tick1line.set_markersize(0) tick.tick2line.set_markersize(0) - tick.label1.set_verticalalignment('center') + tick.label1.set_verticalalignment("center") tick.label1.set_fontsize(8) ticks = ax.yaxis.get_major_ticks() ticks[-1].label1.set_color("green") @@ -117,37 +132,45 @@ def comparepositions(ppe, ip=None, **kwds): ticks[0].label1.set_color("blue") return + def dgseries(stability, **kwds): ax = kwds.get("ax", plt.gca()) dg_style = kwds.get("dg_style", default_dg_style) - scale = kwds.get("scale", 1.) + scale = kwds.get("scale", 1.0) - dgmin = kwds.get("dgmin", stability.results[0][0])*scale - dgmax = kwds.get("dgmax", stability.results[-1][0])*scale + dgmin = kwds.get("dgmin", stability.results[0][0]) * scale + dgmax = kwds.get("dgmax", stability.results[-1][0]) * scale - pmin = kwds.get("pmin", 0.) + pmin = kwds.get("pmin", 0.0) pmax = kwds.get("pmax", np.inf) x = [] y = [] for dg, peaks, bl, dr in stability.results: - if dg*scale < dgmin or dg*scale > dgmax: + if dg * scale < dgmin or dg * scale > dgmax: continue peakpos = [p["position"] for p in peaks] peakpos = [p for p in peakpos if p >= pmin and p <= pmax] x.extend(peakpos) - y.extend(np.zeros_like(peakpos) + dg*scale) + y.extend(np.zeros_like(peakpos) + dg * scale) plt.plot(x, y, **dg_style) + def labelallsubplots(): rv = [] - for i, c in enumerate('abcd'): + for i, c in enumerate("abcd"): plt.subplot(221 + i) s = "(%s)" % c - ht = plt.text(0.04, 0.95, s, - horizontalalignment='left', verticalalignment='top', - transform=gca().transAxes, weight='bold') + ht = plt.text( + 0.04, + 0.95, + s, + horizontalalignment="left", + verticalalignment="top", + transform=plt.gca().transAxes, + weight="bold", + ) rv.append(ht) return rv @@ -164,14 +187,16 @@ def makeplot(ppe_or_stability, ip=None, **kwds): if ppe.extracted is None: # Makeplot requires a ModelCluster, so whip one up. from diffpy.srmise import ModelCluster - ppe.defaultvars() # Make sure everything has some setting. This - # shouldn't have harmful side effects. + + ppe.defaultvars() # Make sure everything has some setting. This + # shouldn't have harmful side effects. rangeslice = ppe.getrangeslice() x = ppe.x[rangeslice] y = ppe.y[rangeslice] dy = ppe.effective_dy[rangeslice] - mcluster = ModelCluster(ppe.initial_peaks, ppe.baseline, x, y, \ - dy, None, ppe.error_method, ppe.pf) + mcluster = ModelCluster( + ppe.initial_peaks, ppe.baseline, x, y, dy, None, ppe.error_method, ppe.pf + ) ext = mcluster else: ext = ppe.extracted @@ -189,14 +214,14 @@ def makeplot(ppe_or_stability, ip=None, **kwds): # Define heights and interstitial offsets # All values in percent of main axis. - top_offset = kwds.get("top_offset", 0.) - dg_height = kwds.get("dg_height", 15. if stability is not None else 0.) - cmp_height = kwds.get("cmp_height", 15. if ip is not None else 7.5) - datatop_offset = kwds.get("datatop_offset", 3.) + top_offset = kwds.get("top_offset", 0.0) + dg_height = kwds.get("dg_height", 15.0 if stability is not None else 0.0) + cmp_height = kwds.get("cmp_height", 15.0 if ip is not None else 7.5) + datatop_offset = kwds.get("datatop_offset", 3.0) # <- Data appears here -> - databottom_offset = kwds.get("databottom_offset", 3.) + databottom_offset = kwds.get("databottom_offset", 3.0) # <- Residual appears here -> - bottom_offset = kwds.get("bottom_offset", 3.) + bottom_offset = kwds.get("bottom_offset", 3.0) # Style options dg_style = kwds.get("dg_style", default_dg_style) @@ -208,83 +233,94 @@ def makeplot(ppe_or_stability, ip=None, **kwds): ip_style = kwds.get("ip_style", default_ip_style) # Label options - userxlabel = kwds.get("xlabel", r'r ($\mathrm{\AA}$)') - userylabel = kwds.get("ylabel", r'G ($\mathrm{\AA^{-2}}$)') - datalabelx = kwds.get("datalabelx", .04) - yideal_label = kwds.get("yideal_label", r'ideal') - yext_label = kwds.get("yext_label", r'found') + userxlabel = kwds.get("xlabel", r"r ($\mathrm{\AA}$)") + userylabel = kwds.get("ylabel", r"G ($\mathrm{\AA^{-2}}$)") + datalabelx = kwds.get("datalabelx", 0.04) + yideal_label = kwds.get("yideal_label", r"ideal") + yext_label = kwds.get("yext_label", r"found") # Other options datalabel = kwds.get("datalabel", None) - dgformatstr = kwds.get("dgformatstr", r'$\delta$g=%f') - dgformatpost = kwds.get("dgformatpost", None) #->userfunction(string) + dgformatstr = kwds.get("dgformatstr", r"$\delta$g=%f") + dgformatpost = kwds.get("dgformatpost", None) # ->userfunction(string) show_fit = kwds.get("show_fit", True) show_individual = kwds.get("show_individual", True) fill_individual = kwds.get("fill_individual", True) show_observed = kwds.get("show_observed", True) show_residual = kwds.get("show_residual", True) - mask_residual = kwds.get("mask_residual", False) #-> number + mask_residual = kwds.get("mask_residual", False) # -> number show_annotation = kwds.get("show_annotation", True) - scale = kwds.get("scale", 1.) # Apply a global scaling factor to the data - - + scale = kwds.get("scale", 1.0) # Apply a global scaling factor to the data # Define the various data which will be plotted r = ext.r_cluster - dr = (r[-1]-r[0])/len(r) - rexpand = np.concatenate((np.arange(r[0]-dr, xlo, -dr)[::-1], r, np.arange(r[-1]+dr, xhi+dr, dr))) - rfine = np.arange(r[0], r[-1], .1*dr) - gr_obs = np.array(resample(ppe.x, ppe.y, rexpand))*scale - #gr_fit = resample(r, ext.value(), rfine) - gr_fit = np.array(ext.value(rfine))*scale - gr_fit_baseline = np.array(ext.valuebl(rfine))*scale - gr_fit_ind = [gr_fit_baseline + np.array(p.value(rfine))*scale for p in ext.model] - gr_res = np.array(ext.residual())*scale + dr = (r[-1] - r[0]) / len(r) + rexpand = np.concatenate( + (np.arange(r[0] - dr, xlo, -dr)[::-1], r, np.arange(r[-1] + dr, xhi + dr, dr)) + ) + rfine = np.arange(r[0], r[-1], 0.1 * dr) + gr_obs = np.array(resample(ppe.x, ppe.y, rexpand)) * scale + # gr_fit = resample(r, ext.value(), rfine) + gr_fit = np.array(ext.value(rfine)) * scale + gr_fit_baseline = np.array(ext.valuebl(rfine)) * scale + gr_fit_ind = [gr_fit_baseline + np.array(p.value(rfine)) * scale for p in ext.model] + gr_res = np.array(ext.residual()) * scale if mask_residual: gr_res = np.ma.masked_outside(gr_res, -mask_residual, mask_residual) all_gr = [] - if show_fit: all_gr.append(gr_fit) - #if show_individual: all_gr.extend([gr_fit_baseline, gr_fit_ind]) + if show_fit: + all_gr.append(gr_fit) + # if show_individual: all_gr.extend([gr_fit_baseline, gr_fit_ind]) if show_individual: all_gr.append(gr_fit_baseline) if len(gr_fit_ind) > 0: all_gr.extend(gr_fit_ind) - if show_observed: all_gr.append(gr_obs) + if show_observed: + all_gr.append(gr_obs) # gr_fit_ind is a list of lists, so use np.min/max # The funky bit with scale makes sure that a user-specified value # has scale applied to it, without messing up the default values, # which are calculated from already scaled quantities. - min_gr = kwds.get("min_gr", np.min([np.min(gr) for gr in all_gr])/scale)*scale - max_gr = kwds.get("max_gr", np.max([np.max(gr) for gr in all_gr])/scale)*scale - + min_gr = kwds.get("min_gr", np.min([np.min(gr) for gr in all_gr]) / scale) * scale + max_gr = kwds.get("max_gr", np.max([np.max(gr) for gr in all_gr]) / scale) * scale if show_residual: min_res = np.min(gr_res) max_res = np.max(gr_res) else: - min_res = 0. - max_res = 0. + min_res = 0.0 + max_res = 0.0 # Derive various y limits based on all the offsets - rel_height = 100. - top_offset - dg_height - cmp_height - datatop_offset - databottom_offset - bottom_offset - abs_height = 100*((max_gr - min_gr) + (max_res - min_res))/rel_height - - yhi = max_gr + (top_offset + dg_height + cmp_height + datatop_offset)*abs_height/100 + rel_height = ( + 100.0 + - top_offset + - dg_height + - cmp_height + - datatop_offset + - databottom_offset + - bottom_offset + ) + abs_height = 100 * ((max_gr - min_gr) + (max_res - min_res)) / rel_height + + yhi = ( + max_gr + + (top_offset + dg_height + cmp_height + datatop_offset) * abs_height / 100 + ) ylo = yhi - abs_height yhi = kwds.get("yhi", yhi) ylo = kwds.get("ylo", ylo) - datatop = yhi - (yhi-ylo)*.01*(top_offset + dg_height + cmp_height) - datalabeltop = 1 - .01*(top_offset + dg_height + cmp_height + datatop_offset) - resbase = ylo + bottom_offset*abs_height/100 - min_res + datatop = yhi - (yhi - ylo) * 0.01 * (top_offset + dg_height + cmp_height) + datalabeltop = 1 - 0.01 * (top_offset + dg_height + cmp_height + datatop_offset) + resbase = ylo + bottom_offset * abs_height / 100 - min_res resbase = kwds.get("resbase", resbase) - fig = kwds.get("figure", plt.gcf()) fig.clf() ax_data = AA.Subplot(fig, 111) @@ -301,8 +337,8 @@ def makeplot(ppe_or_stability, ip=None, **kwds): for peak in gr_fit_ind: plt.fill_between(rfine, gr_fit_baseline, peak, **gind_style) if show_residual: - plt.plot(r, gr_res + resbase, 'r-', **gres_style) - plt.plot((xlo, xhi), 2*[resbase], 'k:') + plt.plot(r, gr_res + resbase, "r-", **gres_style) + plt.plot((xlo, xhi), 2 * [resbase], "k:") # Format ax_data plt.xlim(xlo, xhi) @@ -310,27 +346,41 @@ def makeplot(ppe_or_stability, ip=None, **kwds): plt.xlabel(userxlabel) plt.ylabel(userylabel) ax_data.xaxis.set_minor_locator(plt.MultipleLocator(1)) - #ax_data.yaxis.set_minor_locator(plt.MultipleLocator(np.max([1,int((yhi-ylo)/20)]))) - ax_data.yaxis.set_label_position('left') + # ax_data.yaxis.set_minor_locator(plt.MultipleLocator(np.max([1,int((yhi-ylo)/20)]))) + ax_data.yaxis.set_label_position("left") ax_data.yaxis.tick_left() - ax_data.yaxis.set_ticks_position('both') + ax_data.yaxis.set_ticks_position("both") # Remove labels above where insets begin - #ax_data.yaxis.set_ticklabels([str(int(loc)) for loc in ax_data.yaxis.get_majorticklocs() if loc < datatop]) - ax_data.yaxis.set_ticks([loc for loc in ax_data.yaxis.get_majorticklocs() if (loc < datatop and loc >= ylo)]) - + # ax_data.yaxis.set_ticklabels([str(int(loc)) for loc in ax_data.yaxis.get_majorticklocs() if loc < datatop]) + ax_data.yaxis.set_ticks( + [ + loc + for loc in ax_data.yaxis.get_majorticklocs() + if (loc < datatop and loc >= ylo) + ] + ) # Dataset label if datalabel is not None: - dl = plt.text(datalabelx, datalabeltop, datalabel, ha='left', va='top', - transform=ax_data.transAxes, weight='bold') + dl = plt.text( + datalabelx, + datalabeltop, + datalabel, + ha="left", + va="top", + transform=ax_data.transAxes, + weight="bold", + ) else: dl = None figdict["datalabel"] = dl # Create new x axis at bottom edge of compare inset ax_data.axis["top"].set_visible(False) - ax_data.axis["newtop"] = ax_data.new_floating_axis(0, datatop, axis_direction="bottom") # "top" bugged? + ax_data.axis["newtop"] = ax_data.new_floating_axis( + 0, datatop, axis_direction="bottom" + ) # "top" bugged? ax_data.axis["newtop"].toggle(all=False, ticks=True) ax_data.axis["newtop"].major_ticks.set_tick_out(True) ax_data.axis["newtop"].minor_ticks.set_tick_out(True) @@ -339,37 +389,57 @@ def makeplot(ppe_or_stability, ip=None, **kwds): # The original label is invisible, but we use its (dynamic) x position # to update the new label, which we define have the correct y position. # A bit of a tradeoff for the nice insets and ability to define new axes. - newylabel = plt.text(-.1, .5*(datatop-ylo)/(yhi-ylo), userylabel, - ha='center', va='center', rotation='vertical', transform=ax_data.transAxes) - labeldict[fig] = newylabel # so we can find the correct text object - fig.canvas.mpl_connect('draw_event', on_draw) # original label invisibility and updating + newylabel = plt.text( + -0.1, + 0.5 * (datatop - ylo) / (yhi - ylo), + userylabel, + ha="center", + va="center", + rotation="vertical", + transform=ax_data.transAxes, + ) + labeldict[fig] = newylabel # so we can find the correct text object + fig.canvas.mpl_connect( + "draw_event", on_draw + ) # original label invisibility and updating # Compare extracted (and ideal, if provided) peak positions clearly. if cmp_height > 0: - ax_cmp = inset_axes(ax_data, - width="100%", - height="%s%%" %cmp_height, - loc=2, - bbox_to_anchor=(0., -.01*(top_offset+dg_height), 1, 1), - bbox_transform=ax_data.transAxes, - borderpad=0) + ax_cmp = inset_axes( + ax_data, + width="100%", + height="%s%%" % cmp_height, + loc=2, + bbox_to_anchor=(0.0, -0.01 * (top_offset + dg_height), 1, 1), + bbox_transform=ax_data.transAxes, + borderpad=0, + ) figdict["cmp"] = ax_cmp plt.axes(ax_cmp) - comparepositions(ext, ip, ep_style=ep_style, ip_style=ip_style, yideal_label=yideal_label, yext_label=yext_label) + comparepositions( + ext, + ip, + ep_style=ep_style, + ip_style=ip_style, + yideal_label=yideal_label, + yext_label=yext_label, + ) plt.xlim(xlo, xhi) ax_cmp.set_xticks([]) # Show how extracted peak positions change as dg is changed if dg_height > 0: - ax_dg = inset_axes(ax_data, - width="100%", - height="%s%%" %dg_height, - loc=2, - bbox_to_anchor=(0, -.01*top_offset, 1, 1), - bbox_transform=ax_data.transAxes, - borderpad=0) + ax_dg = inset_axes( + ax_data, + width="100%", + height="%s%%" % dg_height, + loc=2, + bbox_to_anchor=(0, -0.01 * top_offset, 1, 1), + bbox_transform=ax_data.transAxes, + borderpad=0, + ) figdict["dg"] = ax_dg plt.axes(ax_dg) @@ -384,31 +454,42 @@ def makeplot(ppe_or_stability, ip=None, **kwds): plt.xlim(xlo, xhi) ax_dg.xaxis.set_major_locator(plt.NullLocator()) ax_dg.yaxis.set_major_locator(plt.MaxNLocator(3)) - plt.ylabel(r'$\delta$g') + plt.ylabel(r"$\delta$g") # Annotate the actual dg shown if show_annotation: - dg = np.mean(ext.error_cluster)*scale - dgstr = dgformatstr %dg - if "dgformatpost" in kwds: #post-processing on dg annotation + dg = np.mean(ext.error_cluster) * scale + dgstr = dgformatstr % dg + if "dgformatpost" in kwds: # post-processing on dg annotation dgstr = kwds["dgformatpost"](dgstr) if len(ext.model) > 0: - xpos = np.mean([xlo, ext.model[0]["position"]]) # OK for now. + xpos = np.mean([xlo, ext.model[0]["position"]]) # OK for now. else: - xpos = xlo + .1*(xhi-xlo) + xpos = xlo + 0.1 * (xhi - xlo) if dg_height > 0 and cmp_height > 0: # Arrow, text in compare distances line ylo2, yhi2 = ax_dg.get_ylim() if ip is not None: - ypos = ylo2 - .25*cmp_height/dg_height*(yhi2-ylo2) + ypos = ylo2 - 0.25 * cmp_height / dg_height * (yhi2 - ylo2) else: - ypos = ylo2 - .5*cmp_height/dg_height*(yhi2-ylo2) - plt.annotate(dgstr, xy=(xlo, dg), xycoords='data', va='center', ha='center', - xytext=(xpos,ypos), textcoords='data', size=8, color='green', - arrowprops=dict(arrowstyle="->", - connectionstyle="angle,angleA=90,angleB=0,rad=10", - color="green")) + ypos = ylo2 - 0.5 * cmp_height / dg_height * (yhi2 - ylo2) + plt.annotate( + dgstr, + xy=(xlo, dg), + xycoords="data", + va="center", + ha="center", + xytext=(xpos, ypos), + textcoords="data", + size=8, + color="green", + arrowprops=dict( + arrowstyle="->", + connectionstyle="angle,angleA=90,angleB=0,rad=10", + color="green", + ), + ) elif dg_height > 0: # Arrow, and text located somewhere in main plot region @@ -419,8 +500,8 @@ def makeplot(ppe_or_stability, ip=None, **kwds): # Must change axes plt.axes(ax_cmp) ylo2, yhi2 = ax_cmp.get_ylim() - ypos = yhi2/2. - plt.text(xpos, ypos, dgstr, va='center', ha='center', size=8, color='green') + ypos = yhi2 / 2.0 + plt.text(xpos, ypos, dgstr, va="center", ha="center", size=8, color="green") else: # Text only in main plot region # Must change axes @@ -439,6 +520,8 @@ def makeplot(ppe_or_stability, ip=None, **kwds): # invisiblelabel must be temporarily made visible to update # its values. _lastxpos = {} + + def on_draw(event): global _lastxpos fig = event.canvas.figure @@ -458,7 +541,7 @@ def on_draw(event): # If it is kept visible the whole time this problem doesn't occur. # This problem doesn't occur onscreen (TkAgg) or printing PDFs, and # didn't occur in matplotlib 1.0.0. - if abs(xpos - _lastxpos.get(fig, 0)) > .001: + if abs(xpos - _lastxpos.get(fig, 0)) > 0.001: _lastxpos[fig] = xpos plt.draw() else: @@ -466,33 +549,35 @@ def on_draw(event): invisiblelabel.set_visible(False) xpos_old = visiblelabel.get_position()[0] - if abs(xpos - xpos_old) > .001: + if abs(xpos - xpos_old) > 0.001: labeldict[fig].set_x(xpos) plt.draw() return + def readcompare(filename): """Returns a list of distances read from filename, otherwise None.""" from diffpy.srmise.srmiseerrors import SrMiseDataFormatError, SrMiseFileError # TODO: Make this safer try: - datastring = open(filename,'rb').read() - except Exception, err: + datastring = open(filename, "rb").read() + except Exception as err: raise err import re - res = re.search(r'^[^#]', datastring, re.M) + + res = re.search(r"^[^#]", datastring, re.M) if res: - datastring = datastring[res.end():].strip() + datastring = datastring[res.end() :].strip() distances = [] try: for line in datastring.split("\n"): distances.append(float(line)) - except (ValueError, IndexError), err: - print "Could not read distances from '%s'. Ignoring file." %filename + except (ValueError, IndexError) as err: + print("Could not read distances from '%s'. Ignoring file." % filename) if len(distances) == 0: return None @@ -502,31 +587,43 @@ def readcompare(filename): def main(): # configure options parsing - usage = ("%prog srmise_file [options]\n" - "srmise_file can be an extraction file saved by SrMise, " - "or a data file saved by PeakStability.") - descr = ("A very basic tool for somewhat prettier plotting than provided by " - "the basic SrMise classes. Can be used to compare peak positions " - "with those from a list.\n" - "NOTE: At this time the utility only works with peaks extracted using diffpy.srmise.PDFPeakExtraction.") + usage = ( + "%prog srmise_file [options]\n" + "srmise_file can be an extraction file saved by SrMise, " + "or a data file saved by PeakStability." + ) + descr = ( + "A very basic tool for somewhat prettier plotting than provided by " + "the basic SrMise classes. Can be used to compare peak positions " + "with those from a list.\n" + "NOTE: At this time the utility only works with peaks extracted using diffpy.srmise.PDFPeakExtraction." + ) parser = optparse.OptionParser(usage=usage, description=descr) - parser.add_option("--compare", type="string", - help="Compare extracted distances to distances listed (1/line) in this file.") - parser.add_option("--model", type="int", - help="Plot given model from set. Ignored if srmise_file is not a PeakStability file.") - parser.add_option("--show", action="store_true", - help="execute pylab.show() blocking call") - parser.add_option("-o", "--output", type="string", - help="save plot to the specified file") - parser.add_option("--format", type="string", default="eps", - help="output format for plot saving") + parser.add_option( + "--compare", + type="string", + help="Compare extracted distances to distances listed (1/line) in this file.", + ) + parser.add_option( + "--model", + type="int", + help="Plot given model from set. Ignored if srmise_file is not a PeakStability file.", + ) + parser.add_option( + "--show", action="store_true", help="execute pylab.show() blocking call" + ) + parser.add_option( + "-o", "--output", type="string", help="save plot to the specified file" + ) + parser.add_option( + "--format", type="string", default="eps", help="output format for plot saving" + ) parser.allow_interspersed_args = True opts, args = parser.parse_args(sys.argv[1:]) - if len(args) != 1: - parser.error("Exactly one argument required. \n"+usage) + parser.error("Exactly one argument required. \n" + usage) filename = args[0] @@ -534,26 +631,28 @@ def main(): toplot = PDFPeakExtraction() try: toplot.read(filename) - except (Exception): + except Exception: toplot = PeakStability() try: toplot.load(filename) except Exception: - print "File '%s' is not a .srmise or PeakStability data file." %filename + print( + "File '%s' is not a .srmise or PeakStability data file." % filename + ) return if opts.model is not None: try: toplot.setcurrent(opts.model) - except (Exception): - print "Ignoring model, %s is not a PeakStability file." %filename + except Exception: + print("Ignoring model, %s is not a PeakStability file." % filename) distances = None if opts.compare is not None: # use baseline from existing file distances = readcompare(opts.compare) - setfigformat(figsize=(6., 4.0)) + setfigformat(figsize=(6.0, 4.0)) figdict = makeplot(toplot, distances) if opts.output: plt.savefig(opts.output, format=opts.format, dpi=600) @@ -564,5 +663,5 @@ def main(): return -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/diffpy/srmise/baselines/arbitrary.py b/diffpy/srmise/baselines/arbitrary.py index 2588740..b661944 100644 --- a/diffpy/srmise/baselines/arbitrary.py +++ b/diffpy/srmise/baselines/arbitrary.py @@ -17,12 +17,14 @@ import numpy as np import diffpy.srmise.srmiselog +from diffpy.srmise.baselines import Polynomial from diffpy.srmise.baselines.base import BaselineFunction from diffpy.srmise.srmiseerrors import SrMiseEstimationError logger = logging.getLogger("diffpy.srmise") -class Arbitrary (BaselineFunction): + +class Arbitrary(BaselineFunction): """Methods for evaluating a baseline from an arbitrary function. Supports baseline calculations with arbitrary functions. These functions, @@ -64,10 +66,10 @@ def __init__(self, npars, valuef, jacobianf=None, estimatef=None, Cache=None): # Define parameterdict # e.g. {"a_0":0, "a_1":1, "a_2":2, "a_3":3} if npars is 4. parameterdict = {} - for d in range(self.testnpars+1): - parameterdict["a_"+str(d)] = d - formats = ['internal'] - default_formats = {'default_input':'internal', 'default_output':'internal'} + for d in range(self.testnpars + 1): + parameterdict["a_" + str(d)] = d + formats = ["internal"] + default_formats = {"default_input": "internal", "default_output": "internal"} # Check that the provided functions are at least callable if valuef is None or callable(valuef): @@ -93,7 +95,9 @@ def __init__(self, npars, valuef, jacobianf=None, estimatef=None, Cache=None): metadict["valuef"] = (valuef, repr) metadict["jacobianf"] = (jacobianf, repr) metadict["estimatef"] = (estimatef, repr) - BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) + BaselineFunction.__init__( + self, parameterdict, formats, default_formats, metadict, None, Cache + ) #### Methods required by BaselineFunction #### @@ -114,9 +118,8 @@ def estimate_parameters(self, r, y): # TODO: check that estimatef returns something proper? try: return self.estimatef(r, y) - except Exception, e: - emsg = "Error within estimation routine provided to Arbitrary:\n"+\ - str(e) + except Exception as e: + emsg = "Error within estimation routine provided to Arbitrary:\n" + str(e) raise SrMiseEstimationError(emsg) def _jacobianraw(self, pars, r, free): @@ -137,10 +140,10 @@ def _jacobianraw(self, pars, r, free): emsg = "No jacobian routine provided to Arbitrary." raise NotImplementedError(emsg) if len(pars) != self.npars: - emsg = "Argument pars must have "+str(self.npars)+" elements." + emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) if len(free) != self.npars: - emsg = "Argument free must have "+str(self.npars)+" elements." + emsg = "Argument free must have " + str(self.npars) + " elements." raise ValueError(emsg) # Allow an arbitrary function without a Jacobian provided act as @@ -149,7 +152,7 @@ def _jacobianraw(self, pars, r, free): # large performance implications if all other functions used while # fitting a function define a Jacobian. if nfree == 0: - return [None for p in range(len(par))] + return [None for p in range(len(pars))] # TODO: check that jacobianf returns something proper? return self.jacobianf(pars, r, free) @@ -170,15 +173,17 @@ def _transform_parametersraw(self, pars, in_format, out_format): if in_format == "internal": pass else: - raise ValueError("Argument 'in_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'in_format' must be one of %s." % self.parformats + ) # Convert to specified output format from "internal" format. if out_format == "internal": pass else: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'out_format' must be one of %s." % self.parformats + ) return temp def _valueraw(self, pars, r): @@ -192,7 +197,7 @@ def _valueraw(self, pars, r): ... r: sequence or scalar over which pars is evaluated""" if len(pars) != self.npars: - emsg = "Argument pars must have "+str(self.npars)+" elements." + emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) # TODO: check that valuef returns something proper? @@ -201,21 +206,22 @@ def _valueraw(self, pars, r): def getmodule(self): return __name__ -#end of class Polynomial + +# end of class Polynomial # simple test code -if __name__ == '__main__': +if __name__ == "__main__": - f = Polynomial(degree = 3) + f = Polynomial(degree=3) r = np.arange(5) pars = np.array([3, 0, 1, 2]) free = np.array([True, False, True, True]) - print f._valueraw(pars, r) - print f._jacobianraw(pars, r, free) + print(f._valueraw(pars, r)) + print(f._jacobianraw(pars, r, free)) - f = Polynomial(degree = -1) + f = Polynomial(degree=-1) r = np.arange(5) pars = np.array([]) free = np.array([]) - print f._valueraw(pars, r) - print f._jacobianraw(pars, r, free) + print(f._valueraw(pars, r)) + print(f._jacobianraw(pars, r, free)) diff --git a/diffpy/srmise/baselines/base.py b/diffpy/srmise/baselines/base.py index d84160a..985cf9f 100644 --- a/diffpy/srmise/baselines/base.py +++ b/diffpy/srmise/baselines/base.py @@ -176,7 +176,7 @@ def factory(baselinestr, ownerlist): from diffpy.srmise.modelcluster import ModelCluster from diffpy.srmise.modelevaluators import AICc - from diffpy.srmise.peaks import GaussianOverR + from diffpy.srmise.peaks import GaussianOverR, Peaks res = 0.01 r = np.arange(2, 4, res) diff --git a/diffpy/srmise/baselines/fromsequence.py b/diffpy/srmise/baselines/fromsequence.py index c06e2de..c75a8aa 100644 --- a/diffpy/srmise/baselines/fromsequence.py +++ b/diffpy/srmise/baselines/fromsequence.py @@ -22,7 +22,8 @@ logger = logging.getLogger("diffpy.srmise") -class FromSequence (BaselineFunction): + +class FromSequence(BaselineFunction): """Methods for evaluation of a baseline from discrete data via interpolation. FromSequence uses cubic spline interpolation (no smoothing) on discrete @@ -46,13 +47,15 @@ def __init__(self, *args, **kwds): or file: Name of file with column of x values and column of y values. """ - if len(args)==1 and len(kwds)==0: + if len(args) == 1 and len(kwds) == 0: # load from file x, y = self.readxy(args[0]) - elif len(args) == 0 and ("file" in kwds and "x" not in kwds and "y" not in kwds): + elif len(args) == 0 and ( + "file" in kwds and "x" not in kwds and "y" not in kwds + ): # load file x, y = self.readxy(kwds["file"]) - elif len(args)==2 and len(kwds)==0: + elif len(args) == 2 and len(kwds) == 0: # Load x, y directly from arguments x = args[0] y = args[1] @@ -69,15 +72,17 @@ def __init__(self, *args, **kwds): emsg = "Sequences x and y must have the same length." raise ValueError(emsg) parameterdict = {} - formats = ['internal'] - default_formats = {'default_input':'internal', 'default_output':'internal'} + formats = ["internal"] + default_formats = {"default_input": "internal", "default_output": "internal"} self.spline = spi.InterpolatedUnivariateSpline(x, y) self.minx = x[0] self.maxx = x[-1] metadict = {} metadict["x"] = (x, self.xyrepr) metadict["y"] = (y, self.xyrepr) - BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache=None) + BaselineFunction.__init__( + self, parameterdict, formats, default_formats, metadict, None, Cache=None + ) #### Methods required by BaselineFunction #### @@ -102,10 +107,10 @@ def _jacobianraw(self, pars, r, free): r: sequence or scalar over which pars is evaluated free: Empty sequence.""" if len(pars) != self.npars: - emsg = "Argument pars must have "+str(self.npars)+" elements." + emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) if len(free) != self.npars: - emsg = "Argument free must have "+str(self.npars)+" elements." + emsg = "Argument free must have " + str(self.npars) + " elements." raise ValueError(emsg) return [] @@ -125,15 +130,17 @@ def _transform_parametersraw(self, pars, in_format, out_format): if in_format == "internal": pass else: - raise ValueError("Argument 'in_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'in_format' must be one of %s." % self.parformats + ) # Convert to specified output format from "internal" format. if out_format == "internal": pass else: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'out_format' must be one of %s." % self.parformats + ) return temp def _valueraw(self, pars, r): @@ -143,18 +150,22 @@ def _valueraw(self, pars, r): pars: Empty sequence r: sequence or scalar over which pars is evaluated""" if len(pars) != self.npars: - emsg = "Argument pars must have "+str(self.npars)+" elements." + emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) try: if r[0] < self.minx or r[-1] > self.maxx: - logger.warn("Warning: Evaluating interpolating function over %s, outside safe range of %s.", - [r[0], r[-1]], - [self.minx, self.maxx]) - except IndexError, TypeError: + logger.warn( + "Warning: Evaluating interpolating function over %s, outside safe range of %s.", + [r[0], r[-1]], + [self.minx, self.maxx], + ) + except (IndexError, TypeError) as e: if r < self.minx or r > self.maxx: - logger.warn("Warning: Evaluating interpolating function at %s, outside safe range of %s.", - r, - [self.minx, self.maxx]) + logger.warn( + "Warning: Evaluating interpolating function at %s, outside safe range of %s.", + r, + [self.minx, self.maxx], + ) return self.spline(r) def getmodule(self): @@ -162,7 +173,7 @@ def getmodule(self): def xyrepr(self, var): """Safe string output of x and y, compatible with eval()""" - return "[%s]" %", ".join([repr(v) for v in var]) + return "[%s]" % ", ".join([repr(v) for v in var]) def readxy(self, filename): """ """ @@ -170,38 +181,40 @@ def readxy(self, filename): # TODO: Make this safer try: - datastring = open(filename,'rb').read() - except Exception, err: + datastring = open(filename, "rb").read() + except Exception as err: raise err import re - res = re.search(r'^[^#]', datastring, re.M) + + res = re.search(r"^[^#]", datastring, re.M) if res: - datastring = datastring[res.end():].strip() + datastring = datastring[res.end() :].strip() - x=[] - y=[] + x = [] + y = [] try: for line in datastring.split("\n"): v = line.split() x.append(float(v[0])) y.append(float(v[1])) - except (ValueError, IndexError), err: + except (ValueError, IndexError) as err: raise SrMiseDataFormatError(str(err)) return (np.array(x), np.array(y)) -#end of class FromSequence + +# end of class FromSequence # simple test code -if __name__ == '__main__': +if __name__ == "__main__": - r = np.arange(0, 9.42413, .2) - b = -(np.tanh(.5*r) + np.sin(.5*r)) + r = np.arange(0, 9.42413, 0.2) + b = -(np.tanh(0.5 * r) + np.sin(0.5 * r)) f = FromSequence(r, b) pars = np.array([]) free = np.array([]) - r2 = np.arange(0, 9.42413, .5) + r2 = np.arange(0, 9.42413, 0.5) b2 = f._valueraw(pars, r2) From a4221ff9adeb66d46744f2b59d02d654098b3fea Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:54:30 +0800 Subject: [PATCH 02/65] lint check & change to python3 & pre-commit check (#19) * lint check & change to python3 & pre-commit check * [pre-commit.ci] auto fixes from pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- diffpy/srmise/baselines/nanospherical.py | 119 +++++++++++++---------- diffpy/srmise/baselines/polynomial.py | 93 ++++++++++-------- diffpy/srmise/modelevaluators/aic.py | 58 ++++++----- diffpy/srmise/modelevaluators/aicc.py | 60 ++++++------ 4 files changed, 181 insertions(+), 149 deletions(-) diff --git a/diffpy/srmise/baselines/nanospherical.py b/diffpy/srmise/baselines/nanospherical.py index 04c1d43..57f7444 100644 --- a/diffpy/srmise/baselines/nanospherical.py +++ b/diffpy/srmise/baselines/nanospherical.py @@ -22,7 +22,8 @@ logger = logging.getLogger("diffpy.srmise") -class NanoSpherical (BaselineFunction): + +class NanoSpherical(BaselineFunction): """Methods for evaluation of baseline of spherical nanoparticle of uniform density. Allowed formats are @@ -47,29 +48,31 @@ def __init__(self, Cache=None): evaluations. """ # Define parameterdict - parameterdict = {'scale':0, 'radius':1} - formats = ['internal'] - default_formats = {'default_input':'internal', 'default_output':'internal'} + parameterdict = {"scale": 0, "radius": 1} + formats = ["internal"] + default_formats = {"default_input": "internal", "default_output": "internal"} metadict = {} - BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) + BaselineFunction.__init__( + self, parameterdict, formats, default_formats, metadict, None, Cache + ) #### Methods required by BaselineFunction #### -# def estimate_parameters(self, r, y): -# """Estimate parameters for spherical baseline. (Not implemented!) -# -# Parameters -# r - array along r from which to estimate -# y - array along y from which to estimate -# -# Returns Numpy array of parameters in the default internal format. -# Raises NotImplementedError if estimation is not implemented for this -# degree, or SrMiseEstimationError if parameters cannot be estimated for -# any other reason. -# """ -# if len(r) != len(y): -# emsg = "Arrays r, y must have equal length." -# raise ValueError(emsg) + # def estimate_parameters(self, r, y): + # """Estimate parameters for spherical baseline. (Not implemented!) + # + # Parameters + # r - array along r from which to estimate + # y - array along y from which to estimate + # + # Returns Numpy array of parameters in the default internal format. + # Raises NotImplementedError if estimation is not implemented for this + # degree, or SrMiseEstimationError if parameters cannot be estimated for + # any other reason. + # """ + # if len(r) != len(y): + # emsg = "Arrays r, y must have equal length." + # raise ValueError(emsg) def _jacobianraw(self, pars, r, free): """Return the Jacobian of the spherical baseline. @@ -83,22 +86,26 @@ def _jacobianraw(self, pars, r, free): needed. True for evaluation, False for no evaluation. """ if len(pars) != self.npars: - emsg = "Argument pars must have "+str(self.npars)+" elements." + emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) if len(free) != self.npars: - emsg = "Argument free must have "+str(self.npars)+" elements." + emsg = "Argument free must have " + str(self.npars) + " elements." raise ValueError(emsg) jacobian = [None for p in range(self.npars)] if (free == False).sum() == self.npars: return jacobian if np.isscalar(r): - if r <= 0. or r >= 2.*pars[1]: - if free[0]: jacobian[0] = 0. - if free[1]: jacobian[1] = 0. + if r <= 0.0 or r >= 2.0 * pars[1]: + if free[0]: + jacobian[0] = 0.0 + if free[1]: + jacobian[1] = 0.0 else: - if free[0]: jacobian[0] = self._jacobianrawscale(pars, r) - if free[1]: jacobian[1] = self._jacobianrawradius(pars, r) + if free[0]: + jacobian[0] = self._jacobianrawscale(pars, r) + if free[1]: + jacobian[1] = self._jacobianrawradius(pars, r) else: s = self._getdomain(pars, r) if free[0]: @@ -120,12 +127,12 @@ def _jacobianrawscale(self, pars, r): """ s = np.abs(pars[0]) R = np.abs(pars[1]) - rdivR = r/R + rdivR = r / R # From abs'(s) in derivative, which is equivalent to sign(s) except at 0 where it # is undefined. Since s=0 is equivalent to the absence of a nanoparticle, sign will # be fine. sign = np.sign(pars[1]) - return -sign*r*(1-(3./4.)*rdivR+(1./16.)*rdivR**3) + return -sign * r * (1 - (3.0 / 4.0) * rdivR + (1.0 / 16.0) * rdivR**3) def _jacobianrawradius(self, pars, r): """Return partial Jacobian wrt radius without bounds checking. @@ -141,7 +148,7 @@ def _jacobianrawradius(self, pars, r): # From abs'(R) in derivative, which is equivalent to sign(R) except at 0 where it # is undefined. Since R=0 is a singularity anyway, sign will be fine. sign = np.sign(pars[1]) - return sign*s*(3*r**2*(r**2-4*R**2))/(16*R**4) + return sign * s * (3 * r**2 * (r**2 - 4 * R**2)) / (16 * R**4) def _transform_parametersraw(self, pars, in_format, out_format): """Convert parameter values from in_format to out_format. @@ -162,15 +169,17 @@ def _transform_parametersraw(self, pars, in_format, out_format): temp[0] = np.abs(temp[0]) temp[1] = np.abs(temp[1]) else: - raise ValueError("Argument 'in_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'in_format' must be one of %s." % self.parformats + ) # Convert to specified output format from "internal" format. if out_format == "internal": pass else: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'out_format' must be one of %s." % self.parformats + ) return temp def _valueraw(self, pars, r): @@ -185,11 +194,11 @@ def _valueraw(self, pars, r): r - sequence or scalar over which pars is evaluated. """ if len(pars) != self.npars: - emsg = "Argument pars must have "+str(self.npars)+" elements." + emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) if np.isscalar(r): - if r <= 0. or r >= 2.*pars[1]: - return 0. + if r <= 0.0 or r >= 2.0 * pars[1]: + return 0.0 else: return self._valueraw2(pars, r) else: @@ -209,38 +218,44 @@ def _valueraw2(self, pars, r): """ s = np.abs(pars[0]) R = np.abs(pars[1]) - rdivR = r/R - return -s*r*(1-(3./4.)*rdivR+(1./16.)*rdivR**3) + rdivR = r / R + return -s * r * (1 - (3.0 / 4.0) * rdivR + (1.0 / 16.0) * rdivR**3) def _getdomain(self, pars, r): """Return slice object for which r > 0 and r < twice the radius""" - low = r.searchsorted(0., side='right') - high = r.searchsorted(2.*pars[1], side='left') + low = r.searchsorted(0.0, side="right") + high = r.searchsorted(2.0 * pars[1], side="left") return slice(low, high) def getmodule(self): return __name__ -#end of class NanoSpherical + +# end of class NanoSpherical # simple test code -if __name__ == '__main__': +if __name__ == "__main__": f = NanoSpherical() r = np.arange(-5, 10) - pars = np.array([-1., 7.]) + pars = np.array([-1.0, 7.0]) free = np.array([False, True]) - print "Testing nanoparticle spherical baseline" - print "Scale: %f, Radius: %f" %(pars[0], pars[1]) - print "-----------------------------------------" + print("Testing nanoparticle spherical baseline") + print("Scale: %f, Radius: %f" % (pars[0], pars[1])) + print("-----------------------------------------") val = f._valueraw(pars, r) - jac = f._jacobianraw(pars, r, free) - outjac = [j if j is not None else [None]*len(r) for j in jac] - print "r".center(10), "value".center(10), "jac(scale)".center(10), "jac(radius)".center(10) + jac = f._jacobianraw(pars, r, free) + outjac = [j if j is not None else [None] * len(r) for j in jac] + print( + "r".center(10), + "value".center(10), + "jac(scale)".center(10), + "jac(radius)".center(10), + ) for tup in zip(r, val, *outjac): for t in tup: if t is None: - print ("%s" %None).ljust(10), + print("%s" % None).ljust(10), else: - print ("% .3g" %t).ljust(10), + print("% .3g" % t).ljust(10), print diff --git a/diffpy/srmise/baselines/polynomial.py b/diffpy/srmise/baselines/polynomial.py index 4d95c4f..a2cd4d5 100644 --- a/diffpy/srmise/baselines/polynomial.py +++ b/diffpy/srmise/baselines/polynomial.py @@ -22,7 +22,8 @@ logger = logging.getLogger("diffpy.srmise") -class Polynomial (BaselineFunction): + +class Polynomial(BaselineFunction): """Methods for evaluation and parameter estimation of a polynomial baseline.""" def __init__(self, degree, Cache=None): @@ -41,17 +42,19 @@ def __init__(self, degree, Cache=None): emsg = "Argument degree must be an integer." raise ValueError(emsg) if self.degree < 0: - self.degree = -1 # interpreted as negative infinity + self.degree = -1 # interpreted as negative infinity # Define parameterdict # e.g. {"a_0":3, "a_1":2, "a_2":1, "a_3":0} if degree is 3. parameterdict = {} - for d in range(self.degree+1): - parameterdict["a_"+str(d)] = self.degree - d - formats = ['internal'] - default_formats = {'default_input':'internal', 'default_output':'internal'} + for d in range(self.degree + 1): + parameterdict["a_" + str(d)] = self.degree - d + formats = ["internal"] + default_formats = {"default_input": "internal", "default_output": "internal"} metadict = {} metadict["degree"] = (degree, repr) - BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) + BaselineFunction.__init__( + self, parameterdict, formats, default_formats, metadict, None, Cache + ) #### Methods required by BaselineFunction #### @@ -81,7 +84,7 @@ def estimate_parameters(self, r, y): return np.array([]) if self.degree == 0: - return np.array([0.]) + return np.array([0.0]) if self.degree == 1: # Estimate degree=1 baseline. @@ -90,15 +93,16 @@ def estimate_parameters(self, r, y): # lies above the baseline. # TODO: Make this more sophisticated. try: - cut = np.max([len(y)/10, 1]) + cut = np.max([len(y) / 10, 1]) cut_idx = y.argsort()[:cut] import numpy.linalg as la + A = np.array([r[cut_idx]]).T slope = la.lstsq(A, y[cut_idx])[0][0] - return np.array([slope, 0.]) - except Exception, e: - emsg = "Error during estimation -- "+str(e) + return np.array([slope, 0.0]) + except Exception as e: + emsg = "Error during estimation -- " + str(e) raise raise SrMiseEstimationError(emsg) @@ -116,10 +120,10 @@ def _jacobianraw(self, pars, r, free): needed. True for evaluation, False for no evaluation. """ if len(pars) != self.npars: - emsg = "Argument pars must have "+str(self.npars)+" elements." + emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) if len(free) != self.npars: - emsg = "Argument free must have "+str(self.npars)+" elements." + emsg = "Argument free must have " + str(self.npars) + " elements." raise ValueError(emsg) jacobian = [None for p in range(self.npars)] if (free == False).sum() == self.npars: @@ -148,15 +152,17 @@ def _transform_parametersraw(self, pars, in_format, out_format): if in_format == "internal": pass else: - raise ValueError("Argument 'in_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'in_format' must be one of %s." % self.parformats + ) # Convert to specified output format from "internal" format. if out_format == "internal": pass else: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'out_format' must be one of %s." % self.parformats + ) return temp def _valueraw(self, pars, r): @@ -171,51 +177,54 @@ def _valueraw(self, pars, r): If degree is negative infinity, pars is an empty sequence. r: sequence or scalar over which pars is evaluated""" if len(pars) != self.npars: - emsg = "Argument pars must have "+str(self.npars)+" elements." + emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) return np.polyval(pars, r) def getmodule(self): return __name__ -#end of class Polynomial + +# end of class Polynomial # simple test code -if __name__ == '__main__': +if __name__ == "__main__": # Test polynomial of degree 3 - print "Testing degree 3 polynomial" - print "---------------------------" - f = Polynomial(degree = 3) + print("Testing degree 3 polynomial") + print("---------------------------") + f = Polynomial(degree=3) r = np.arange(5) pars = np.array([3, 0, 1, 2]) free = np.array([True, False, True, True]) val = f._valueraw(pars, r) - jac = f._jacobianraw(pars, r, free) - print "Value:\n", val - print "Jacobian: " - for j in jac: print " %s" %j + jac = f._jacobianraw(pars, r, free) + print("Value:\n", val) + print("Jacobian: ") + for j in jac: + print(" %s" % j) # Test polynomial of degree -oo - print "\nTesting degree -oo polynomial (== 0)" - print "------------------------------------" - f = Polynomial(degree = -1) + print("\nTesting degree -oo polynomial (== 0)") + print("------------------------------------") + f = Polynomial(degree=-1) r = np.arange(5) pars = np.array([]) free = np.array([]) val = f._valueraw(pars, r) - jac = f._jacobianraw(pars, r, free) - print "Value:\n", val - print "Jacobian: " - for j in jac: print " %s" %j + jac = f._jacobianraw(pars, r, free) + print("Value:\n", val) + print("Jacobian: ") + for j in jac: + print(" %s" % j) # Test linear estimation - print "\nTesting linear baseline estimation" - print "------------------------------------" - f = Polynomial(degree = 1) + print("\nTesting linear baseline estimation") + print("------------------------------------") + f = Polynomial(degree=1) pars = np.array([1, 0]) - r = np.arange(0, 10, .1) - y = -r + 10*np.exp(-(r-5)**2) + np.random.rand(len(r)) + r = np.arange(0, 10, 0.1) + y = -r + 10 * np.exp(-((r - 5) ** 2)) + np.random.rand(len(r)) est = f.estimate_parameters(r, y) - print "Actual baseline: ", np.array([-1, 0.]) - print "Estimated baseline: ", est + print("Actual baseline: ", np.array([-1, 0.0])) + print("Estimated baseline: ", est) diff --git a/diffpy/srmise/modelevaluators/aic.py b/diffpy/srmise/modelevaluators/aic.py index 915cace..1bb8f9a 100644 --- a/diffpy/srmise/modelevaluators/aic.py +++ b/diffpy/srmise/modelevaluators/aic.py @@ -21,7 +21,8 @@ logger = logging.getLogger("diffpy.srmise") -class AIC (ModelEvaluator): + +class AIC(ModelEvaluator): """Evaluate and compare models with the AIC statistic. Akaike's Information Criterion (AIC) is a method for comparing statistical @@ -52,11 +53,11 @@ def __init__(self): def evaluate(self, fit, count_fixed=False, kshift=0): """Return quality of fit for given ModelCluster using AIC (Akaike's Information Criterion). - Parameters - fit: A ModelCluster - count_fixed: Whether fixed parameters are considered. - kshift: (0) Treat the model has having this many additional - parameters. Negative values also allowed.""" + Parameters + fit: A ModelCluster + count_fixed: Whether fixed parameters are considered. + kshift: (0) Treat the model has having this many additional + parameters. Negative values also allowed.""" # Number of parameters. By default, fixed parameters are ignored. k = fit.model.npars(count_fixed=count_fixed) + kshift if k < 0: @@ -77,7 +78,6 @@ def evaluate(self, fit, count_fixed=False, kshift=0): return self.stat - def minpoints(self, npars): """Calculates the minimum number of points required to make an estimate of a model's quality.""" @@ -86,36 +86,39 @@ def minpoints(self, npars): def parpenalty(self, k, n): """Returns the cost for adding k parameters to the current model cluster.""" - #Weight the penalty for additional parameters. - #If this isn't 1 there had better be a good reason. - fudgefactor = 1. + # Weight the penalty for additional parameters. + # If this isn't 1 there had better be a good reason. + fudgefactor = 1.0 - return (2*k)*fudgefactor + return (2 * k) * fudgefactor def growth_justified(self, fit, k_prime): """Returns whether adding k_prime parameters to the given model (ModelCluster) is justified given the current quality of the fit. - The assumption is that adding k_prime parameters will result in "effectively 0" chiSquared cost, and so adding it is justified - if the cost of adding these parameters is less than the current chiSquared cost. The validity of this assumption (which - depends on an unknown chiSquared value) and the impact of the errors used should be examined more thoroughly in the future.""" + The assumption is that adding k_prime parameters will result in "effectively 0" chiSquared cost, and so adding it is justified + if the cost of adding these parameters is less than the current chiSquared cost. The validity of this assumption (which + depends on an unknown chiSquared value) and the impact of the errors used should be examined more thoroughly in the future. + """ if self.chisq is None: self.chisq = self.chi_squared(fit.value(), fit.y_cluster, fit.error_cluster) - k_actual = fit.model.npars(count_fixed=False) #parameters in current fit - k_test = k_actual + k_prime #parameters in prospective fit - n = fit.size #the number of data points included in the fit + k_actual = fit.model.npars(count_fixed=False) # parameters in current fit + k_test = k_actual + k_prime # parameters in prospective fit + n = fit.size # the number of data points included in the fit # If there are too few points to calculate AIC with the requested number of parameter # then clearly that increase in parameters is not justified. if n < self.minpoints(k_test): return False - #assert n >= self.minPoints(kActual) #check that AIC is defined for the actual fit + # assert n >= self.minPoints(kActual) #check that AIC is defined for the actual fit if n < self.minpoints(k_actual): - logger.warn("AIC.growth_justified(): too few data to evaluate quality reliably.") - n=self.minpoints(k_actual) + logger.warn( + "AIC.growth_justified(): too few data to evaluate quality reliably." + ) + n = self.minpoints(k_actual) - penalty=self.parpenalty(k_test, n) - self.parpenalty(k_actual, n) + penalty = self.parpenalty(k_test, n) - self.parpenalty(k_actual, n) return penalty < self.chisq @@ -125,24 +128,25 @@ def akaikeweights(aics): aic_stats = np.array([aic.stat for aic in aics]) aic_min = min(aic_stats) - return np.exp(-(aic_stats-aic_min)/2.) + return np.exp(-(aic_stats - aic_min) / 2.0) @staticmethod def akaikeprobs(aics): """Return sequence of Akaike probabilities for sequence of AICs""" aic_weights = AIC.akaikeweights(aics) - return aic_weights/np.sum(aic_weights) + return aic_weights / np.sum(aic_weights) + # end of class AIC # simple test code -if __name__ == '__main__': +if __name__ == "__main__": - m1=AIC() - m2=AIC() + m1 = AIC() + m2 = AIC() m1.stat = 20 m2.stat = 30 - print m2 > m1 + print(m2 > m1) diff --git a/diffpy/srmise/modelevaluators/aicc.py b/diffpy/srmise/modelevaluators/aicc.py index 57f0713..2e17e76 100644 --- a/diffpy/srmise/modelevaluators/aicc.py +++ b/diffpy/srmise/modelevaluators/aicc.py @@ -21,7 +21,8 @@ logger = logging.getLogger("diffpy.srmise") -class AICc (ModelEvaluator): + +class AICc(ModelEvaluator): """Evaluate and compare models with the AICc statistic. Akaike's Information Criterion w/ 2nd order correction for small sample @@ -50,13 +51,13 @@ def __init__(self): def evaluate(self, fit, count_fixed=False, kshift=0): """Return quality of fit for given ModelCluster using AICc (Akaike's Information Criterion - with 2nd order correction for small sample size). + with 2nd order correction for small sample size). - Parameters - fit: A ModelCluster - count_fixed: Whether fixed parameters are considered. - kshift: (0) Treat the model has having this many additional - parameters. Negative values also allowed.""" + Parameters + fit: A ModelCluster + count_fixed: Whether fixed parameters are considered. + kshift: (0) Treat the model has having this many additional + parameters. Negative values also allowed.""" # Number of parameters. By default, fixed parameters are ignored. k = fit.model.npars(count_fixed=count_fixed) + kshift if k < 0: @@ -77,7 +78,6 @@ def evaluate(self, fit, count_fixed=False, kshift=0): return self.stat - def minpoints(self, npars): """Calculates the minimum number of points required to make an estimate of a model's quality.""" @@ -88,36 +88,39 @@ def minpoints(self, npars): def parpenalty(self, k, n): """Returns the cost for adding k parameters to the current model cluster.""" - #Weight the penalty for additional parameters. - #If this isn't 1 there had better be a good reason. - fudgefactor = 1. + # Weight the penalty for additional parameters. + # If this isn't 1 there had better be a good reason. + fudgefactor = 1.0 - return (2*k+float(2*k*(k+1))/(n-k-1))*fudgefactor + return (2 * k + float(2 * k * (k + 1)) / (n - k - 1)) * fudgefactor def growth_justified(self, fit, k_prime): """Returns whether adding k_prime parameters to the given model (ModelCluster) is justified given the current quality of the fit. - The assumption is that adding k_prime parameters will result in "effectively 0" chiSquared cost, and so adding it is justified - if the cost of adding these parameters is less than the current chiSquared cost. The validity of this assumption (which - depends on an unknown chiSquared value) and the impact of the errors used should be examined more thoroughly in the future.""" + The assumption is that adding k_prime parameters will result in "effectively 0" chiSquared cost, and so adding it is justified + if the cost of adding these parameters is less than the current chiSquared cost. The validity of this assumption (which + depends on an unknown chiSquared value) and the impact of the errors used should be examined more thoroughly in the future. + """ if self.chisq is None: self.chisq = self.chi_squared(fit.value(), fit.y_cluster, fit.error_cluster) - k_actual = fit.model.npars(count_fixed=False) #parameters in current fit - k_test = k_actual + k_prime #parameters in prospective fit - n = fit.size #the number of data points included in the fit + k_actual = fit.model.npars(count_fixed=False) # parameters in current fit + k_test = k_actual + k_prime # parameters in prospective fit + n = fit.size # the number of data points included in the fit # If there are too few points to calculate AICc with the requested number of parameter # then clearly that increase in parameters is not justified. if n < self.minpoints(k_test): return False - #assert n >= self.minPoints(kActual) #check that AICc is defined for the actual fit + # assert n >= self.minPoints(kActual) #check that AICc is defined for the actual fit if n < self.minpoints(k_actual): - logger.warn("AICc.growth_justified(): too few data to evaluate quality reliably.") - n=self.minpoints(k_actual) + logger.warn( + "AICc.growth_justified(): too few data to evaluate quality reliably." + ) + n = self.minpoints(k_actual) - penalty=self.parpenalty(k_test, n) - self.parpenalty(k_actual, n) + penalty = self.parpenalty(k_test, n) - self.parpenalty(k_actual, n) return penalty < self.chisq @@ -127,24 +130,25 @@ def akaikeweights(aics): aic_stats = np.array([aic.stat for aic in aics]) aic_min = min(aic_stats) - return np.exp(-(aic_stats-aic_min)/2.) + return np.exp(-(aic_stats - aic_min) / 2.0) @staticmethod def akaikeprobs(aics): """Return sequence of Akaike probabilities for sequence of AICs""" aic_weights = AICc.akaikeweights(aics) - return aic_weights/np.sum(aic_weights) + return aic_weights / np.sum(aic_weights) + # end of class AICc # simple test code -if __name__ == '__main__': +if __name__ == "__main__": - m1=AICc() - m2=AICc() + m1 = AICc() + m2 = AICc() m1.stat = 20 m2.stat = 30 - print m2 > m1 + print(m2 > m1) From 866e71487963ac95b905adb9adca0a24b5f4e787 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:56:56 +0800 Subject: [PATCH 03/65] lint check and fix print and exception python2 issue (#20) --- devutils/prep.py | 2 +- diffpy/srmise/basefunction.py | 141 ++++--- diffpy/srmise/dataclusters.py | 168 +++++--- diffpy/srmise/modelcluster.py | 565 +++++++++++++++------------ diffpy/srmise/modelparts.py | 234 ++++++----- diffpy/srmise/multimodelselection.py | 248 +++++++----- diffpy/srmise/pdfdataset.py | 175 +++++---- 7 files changed, 916 insertions(+), 617 deletions(-) diff --git a/devutils/prep.py b/devutils/prep.py index b765f7d..8ecea6b 100644 --- a/devutils/prep.py +++ b/devutils/prep.py @@ -108,6 +108,6 @@ def rm(directory, filerestr): ### Convert output of example files to Unix-style endlines for sdist. if os.linesep != '\n': - print "==== Scrubbing Endlines ====" + print"==== Scrubbing Endlines ====" # All *.srmise and *.pwa files in examples directory. scrubeol("../doc/examples/output", r".*(\.srmise|\.pwa)") diff --git a/diffpy/srmise/basefunction.py b/diffpy/srmise/basefunction.py index 18a55e4..e9af1ec 100644 --- a/diffpy/srmise/basefunction.py +++ b/diffpy/srmise/basefunction.py @@ -17,12 +17,14 @@ import sys import numpy as np +from numpy.compat import unicode from diffpy.srmise.modelparts import ModelPart, ModelParts from diffpy.srmise.srmiseerrors import * logger = logging.getLogger("diffpy.srmise") + class BaseFunction(object): """Base class for mathematical functions which model numeric sequences. @@ -61,7 +63,15 @@ class BaseFunction(object): transform_parameters() """ - def __init__(self, parameterdict, parformats, default_formats, metadict, base=None, Cache=None): + def __init__( + self, + parameterdict, + parformats, + default_formats, + metadict, + base=None, + Cache=None, + ): """Set parameterdict defined by subclass Parameters @@ -96,23 +106,31 @@ def __init__(self, parameterdict, parformats, default_formats, metadict, base=No vals = self.parameterdict.values() vals.sort() if vals != range(self.npars): - emsg = "Argument parameterdict's values must uniquely specify "+\ - "the index of each parameter defined by its keys." + emsg = ( + "Argument parameterdict's values must uniquely specify " + + "the index of each parameter defined by its keys." + ) raise ValueError(emsg) self.parformats = parformats # Check validity of default_formats self.default_formats = default_formats - if not ("default_input" in self.default_formats and - "default_output" in self.default_formats): - emsg = "Argument default_formats must specify 'default_input' "+\ - "and 'default_output' as keys." + if not ( + "default_input" in self.default_formats + and "default_output" in self.default_formats + ): + emsg = ( + "Argument default_formats must specify 'default_input' " + + "and 'default_output' as keys." + ) raise ValueError(emsg) for f in self.default_formats.values(): if not f in self.parformats: - emsg = "Keys of argument default_formats must map to a "+\ - "value within argument parformats." + emsg = ( + "Keys of argument default_formats must map to a " + + "value within argument parformats." + ) raise ValueError() # Set metadictionary @@ -126,12 +144,11 @@ def __init__(self, parameterdict, parformats, default_formats, metadict, base=No # of PeakFunction. # Object to cache: (basefunctioninstance, tuple of parameters) if Cache is not None: - #self.value = Cache(self.value, "value") - #self.jacobian = Cache(self.jacobian, "jacobian") + # self.value = Cache(self.value, "value") + # self.jacobian = Cache(self.jacobian, "jacobian") pass return - #### "Virtual" class methods #### def actualize(self, *args, **kwds): @@ -164,7 +181,6 @@ def _valueraw(self, *args, **kwds): emsg = "_valueraw must() be implemented in a BaseFunction subclass." raise NotImplementedError(emsg) - #### Class methods #### def jacobian(self, p, r, rng=None): @@ -179,8 +195,10 @@ def jacobian(self, p, r, rng=None): previously calculated values instead. """ if self is not p._owner: - emsg = "Argument 'p' must be evaluated by the BaseFunction "+\ - "subclass which owns it." + emsg = ( + "Argument 'p' must be evaluated by the BaseFunction " + + "subclass which owns it." + ) raise ValueError(emsg) # normally r will be a sequence, but also allow single numeric values @@ -192,7 +210,7 @@ def jacobian(self, p, r, rng=None): output = [None for j in jac] for idx in range(len(output)): if jac[idx] is not None: - output[idx] = r * 0. + output[idx] = r * 0.0 output[idx][rng] = jac[idx] return output except TypeError: @@ -201,10 +219,10 @@ def jacobian(self, p, r, rng=None): def transform_derivatives(self, pars, in_format=None, out_format=None): """Return gradient matrix for pars converted from in_format to out_format. - Parameters - pars - Sequence of parameters - in_format - A format defined for this class - out_format - A format defined for this class + Parameters + pars - Sequence of parameters + in_format - A format defined for this class + out_format - A format defined for this class """ # Map unspecified formats to specific formats defined in default_formats if in_format is None: @@ -223,25 +241,29 @@ def transform_derivatives(self, pars, in_format=None, out_format=None): out_format = self.default_formats["default_input"] if not in_format in self.parformats: - raise ValueError("Argument 'in_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'in_format' must be one of %s." % self.parformats + ) if not out_format in self.parformats: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'out_format' must be one of %s." % self.parformats + ) if in_format == out_format: return np.identity(self.npars) - return self._transform_derivativesraw(pars, in_format=in_format, out_format=out_format) + return self._transform_derivativesraw( + pars, in_format=in_format, out_format=out_format + ) def transform_parameters(self, pars, in_format=None, out_format=None): """Return new sequence with pars converted from in_format to out_format. - Also restores parameters to a preferred range if it permits multiple - values that correspond to the same physical result. + Also restores parameters to a preferred range if it permits multiple + values that correspond to the same physical result. - Parameters - pars - Sequence of parameters - in_format - A format defined for this class - out_format - A format defined for this class + Parameters + pars - Sequence of parameters + in_format - A format defined for this class + out_format - A format defined for this class """ # Map unspecified formats to specific formats defined in default_formats if in_format is None: @@ -260,15 +282,18 @@ def transform_parameters(self, pars, in_format=None, out_format=None): out_format = self.default_formats["default_input"] if not in_format in self.parformats: - raise ValueError("Argument 'in_format' must be one of %s." \ - % self.parformats) + raise ValueError( + "Argument 'in_format' must be one of %s." % self.parformats + ) if not out_format in self.parformats: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) - #if in_format == out_format: + raise ValueError( + "Argument 'out_format' must be one of %s." % self.parformats + ) + # if in_format == out_format: # return pars - return self._transform_parametersraw(pars, in_format=in_format, out_format=out_format) - + return self._transform_parametersraw( + pars, in_format=in_format, out_format=out_format + ) def value(self, p, r, rng=None): """Calculate value of ModelPart over r, possibly restricted by range. @@ -282,8 +307,10 @@ def value(self, p, r, rng=None): previously calculated values instead. """ if self is not p._owner: - emsg = "Argument 'p' must be evaluated by the BaseFunction "+\ - "subclass which owns it." + emsg = ( + "Argument 'p' must be evaluated by the BaseFunction " + + "subclass which owns it." + ) raise ValueError(emsg) # normally r will be a sequence, but also allow single numeric values @@ -291,7 +318,7 @@ def value(self, p, r, rng=None): if rng is None: rng = slice(0, len(r)) rpart = r[rng] - output = r * 0. + output = r * 0.0 output[rng] = self._valueraw(p.pars, rpart) return output except TypeError: @@ -338,17 +365,17 @@ def writestr(self, baselist): raise ValueError("emsg") lines = [] # Write function type - lines.append("function=%s" %repr(self.__class__.__name__)) - lines.append("module=%s" %repr(self.getmodule())) + lines.append("function=%s" % repr(self.__class__.__name__)) + lines.append("module=%s" % repr(self.getmodule())) # Write base if self.base is not None: - lines.append("base=%s" %repr(baselist.index(self.base))) + lines.append("base=%s" % repr(baselist.index(self.base))) else: - lines.append("base=%s" %repr(None)) + lines.append("base=%s" % repr(None)) # Write all other metadata for k, (v, f) in self.metadict.iteritems(): - lines.append("%s=%s" %(k, f(v))) - datastring = "\n".join(lines)+"\n" + lines.append("%s=%s" % (k, f(v))) + datastring = "\n".join(lines) + "\n" return datastring @staticmethod @@ -367,19 +394,19 @@ def factory(functionstr, baselist): # populate dictionary with parameter definition # "key=value"->{"key":"value"} - data = re.split(r'(?:[\r\n]+|\A)(\S+)=', data) + data = re.split(r"(?:[\r\n]+|\A)(\S+)=", data) ddict = {} - for i in range(len(data)/2): - ddict[data[2*i+1]] = data[2*i+2] + for i in range(len(data) / 2): + ddict[data[2 * i + 1]] = data[2 * i + 2] # dictionary of parameters pdict = {} - for (k, v) in ddict.items(): + for k, v in ddict.items(): try: pdict[k] = eval(v) - except Exception, e: + except Exception as e: logger.exception(e) - emsg = ("Invalid parameter: %s=%s" %(k,v)) + emsg = "Invalid parameter: %s=%s" % (k, v) raise SrMiseDataFormatError(emsg) function_name = pdict["function"] @@ -438,9 +465,9 @@ def safefunction(f, fsafe): return -#end of class BaseFunction +# end of class BaseFunction -if __name__ == '__main__': +if __name__ == "__main__": from diffpy.srmise.peaks import GaussianOverR, TerminationRipples @@ -451,7 +478,7 @@ def safefunction(f, fsafe): pt = TerminationRipples(p, 20) outstr2 = pt.writestr([p]) - print outstr + print(outstr) pt2 = BaseFunction.factory(outstr2, [p]) - print type(pt2) + print(type(pt2)) diff --git a/diffpy/srmise/dataclusters.py b/diffpy/srmise/dataclusters.py index 077c1a8..0bf968b 100644 --- a/diffpy/srmise/dataclusters.py +++ b/diffpy/srmise/dataclusters.py @@ -21,6 +21,7 @@ logger = logging.getLogger("diffpy.srmise") + class DataClusters: """Find clusters corresponding to peaks in numerical x-, y-value arrays. @@ -51,7 +52,7 @@ def __init__(self, x, y, res): y - corresponding sequence of y-values res - clustering 'resolution' """ - #Track internal state of clustering. + # Track internal state of clustering. self.INIT = 0 self.READY = 1 self.CLUSTERING = 2 @@ -62,14 +63,12 @@ def __init__(self, x, y, res): return - # This iterator operates not over found clusters, but over the process of # clustering. This behavior could cause confusion and should perhaps be # altered. def __iter__(self): return self - def clear(self): """Clear all members, including user data.""" self.x = np.array([]) @@ -85,7 +84,7 @@ def clear(self): def reset_clusters(self): """Reset all progress on clustering.""" - self.clusters = np.array([[self.data_order[-1],self.data_order[-1]]]) + self.clusters = np.array([[self.data_order[-1], self.data_order[-1]]]) self.current_idx = self.data_order.size - 1 self.lastcluster_idx = 0 self.lastpoint_idx = self.data_order[-1] @@ -102,7 +101,7 @@ def setdata(self, x, y, res): y - corresponding sequence of y-values res - clustering 'resolution' """ - #Test for error conditions + # Test for error conditions # 1) Length mismatch # 2) Bound errors for res # 3) r isn't sorted? @@ -116,7 +115,7 @@ def setdata(self, x, y, res): self.y = y self.res = res - self.data_order = self.y.argsort() # Defines order of clustering + self.data_order = self.y.argsort() # Defines order of clustering self.clusters = np.array([[self.data_order[-1], self.data_order[-1]]]) self.current_idx = len(self.data_order) - 1 self.lastcluster_idx = 0 @@ -169,9 +168,10 @@ def next(self): self.lastcluster_idx = nearest_cluster[0] else: # insert right of nearest cluster - self.lastcluster_idx = nearest_cluster[0]+1 - self.clusters = np.insert(self.clusters, self.lastcluster_idx, - [test_idx, test_idx], 0) + self.lastcluster_idx = nearest_cluster[0] + 1 + self.clusters = np.insert( + self.clusters, self.lastcluster_idx, [test_idx, test_idx], 0 + ) return self def makeclusters(self): @@ -200,10 +200,10 @@ def find_nearest_cluster2(self, x): return self.find_nearest_cluster(idx) else: # Choose adjacent index nearest to x - if (self.x[idx] - x) < (x - self.x[idx-1]): + if (self.x[idx] - x) < (x - self.x[idx - 1]): return self.find_nearest_cluster(idx) else: - return self.find_nearest_cluster(idx-1) + return self.find_nearest_cluster(idx - 1) def find_nearest_cluster(self, idx): """Return [cluster index, distance] for cluster nearest to x[idx]. @@ -225,23 +225,27 @@ def find_nearest_cluster(self, idx): return None flat_idx = clusters_flat.searchsorted(idx) - near_idx = flat_idx/2 + near_idx = flat_idx / 2 if flat_idx == len(clusters_flat): - #test_idx is right of the last cluster - return [near_idx-1, self.x[idx]-self.x[self.clusters[-1, 1]]] - if clusters_flat[flat_idx] == idx or flat_idx%2 == 1: + # test_idx is right of the last cluster + return [near_idx - 1, self.x[idx] - self.x[self.clusters[-1, 1]]] + if clusters_flat[flat_idx] == idx or flat_idx % 2 == 1: # idx is within some cluster return [near_idx, 0.0] if flat_idx == 0: # idx is left of the first cluster - return [near_idx, self.x[idx]-self.x[self.clusters[0,0]]] + return [near_idx, self.x[idx] - self.x[self.clusters[0, 0]]] # Calculate which of the two nearest clusters is closer - distances=np.array([self.x[idx]-self.x[self.clusters[near_idx-1, 1]], - self.x[idx]-self.x[self.clusters[near_idx, 0]]]) + distances = np.array( + [ + self.x[idx] - self.x[self.clusters[near_idx - 1, 1]], + self.x[idx] - self.x[self.clusters[near_idx, 0]], + ] + ) if distances[0] < np.abs(distances[1]): - return [near_idx-1, distances[0]] + return [near_idx - 1, distances[0]] else: return [near_idx, distances[1]] @@ -255,15 +259,17 @@ def cluster_is_full(self, cluster_idx): cluster_idx - The index of the cluster to test """ if cluster_idx > 0: - low = self.clusters[cluster_idx-1, 1] + 1 + low = self.clusters[cluster_idx - 1, 1] + 1 else: low = 0 if cluster_idx < len(self.clusters) - 1: - high = self.clusters[cluster_idx+1, 0] - 1 + high = self.clusters[cluster_idx + 1, 0] - 1 else: high = len(self.data_order) - 1 - return self.clusters[cluster_idx, 0] == low \ - and self.clusters[cluster_idx, 1] == high + return ( + self.clusters[cluster_idx, 0] == low + and self.clusters[cluster_idx, 1] == high + ) def combine_clusters(self, combine): """Combine clusters specified by each subarray of cluster indices. @@ -283,15 +289,35 @@ def combine_clusters(self, combine): # Test that all clusters are contiguous and adjacent first = c[0] for i in range(c[0], c[-1]): - if c[i+1-first]-1 != c[i-first]: - raise ValueError(''.join(["Clusters ", str(c[i]), " and ", str(c[i+1]), " are not contiguous and/or increasing."])) - if self.clusters[i+1, 0]-self.clusters[i, 1] != 1: - raise ValueError(''.join(["Clusters ", str(c[i]), " and ", str(c[i+1]), " have unclustered points between them."])) - - #update cluster endpoints + if c[i + 1 - first] - 1 != c[i - first]: + raise ValueError( + "".join( + [ + "Clusters ", + str(c[i]), + " and ", + str(c[i + 1]), + " are not contiguous and/or increasing.", + ] + ) + ) + if self.clusters[i + 1, 0] - self.clusters[i, 1] != 1: + raise ValueError( + "".join( + [ + "Clusters ", + str(c[i]), + " and ", + str(c[i + 1]), + " have unclustered points between them.", + ] + ) + ) + + # update cluster endpoints self.clusters[c[0], 1] = self.clusters[c[-1], 1] todelete = np.array([c[1:] for c in combine]).ravel() - self.clusters = np.delete(self.clusters, todelete ,0) + self.clusters = np.delete(self.clusters, todelete, 0) def find_adjacent_clusters(self): """Return all cluster indices with no unclustered points between them. @@ -306,20 +332,26 @@ def find_adjacent_clusters(self): adj = [] left_idx = 0 - while left_idx < len(self.clusters)-1: - while left_idx < len(self.clusters)-1 and self.clusters[left_idx+1, 0] - self.clusters[left_idx, 1] !=1: + while left_idx < len(self.clusters) - 1: + while ( + left_idx < len(self.clusters) - 1 + and self.clusters[left_idx + 1, 0] - self.clusters[left_idx, 1] != 1 + ): left_idx += 1 # Not left_idx+1 since left_idx=len(self.clusters)-2 even if no # clusters are actually adjacent. right_idx = left_idx - while right_idx < len(self.clusters)-1 and self.clusters[right_idx+1, 0] - self.clusters[right_idx, 1] == 1: + while ( + right_idx < len(self.clusters) - 1 + and self.clusters[right_idx + 1, 0] - self.clusters[right_idx, 1] == 1 + ): right_idx += 1 if right_idx > left_idx: - adj.append(range(left_idx, right_idx+1)) - left_idx = right_idx+1 # set for next possible left_idx + adj.append(range(left_idx, right_idx + 1)) + left_idx = right_idx + 1 # set for next possible left_idx return np.array(adj) def cut(self, idx): @@ -331,24 +363,23 @@ def cut(self, idx): data_ids = self.clusters[idx] if len(data_ids) == data_ids.size: # idx is a scalar, so give single slice object - return slice(data_ids[0], data_ids[1]+1) + return slice(data_ids[0], data_ids[1] + 1) else: # idx is a list/slice, so give list of slice objects - return [slice(c[0], c[1]+1) for c in data_ids] + return [slice(c[0], c[1] + 1) for c in data_ids] def cluster_boundaries(self): """Return sequence with (x,y) of all cluster boundaries.""" boundaries = [] for l in self.clusters: - xlo = np.mean(self.x[l[0]-1:l[0]+1]) - ylo = np.mean(self.y[l[0]-1:l[0]+1]) - xhi = np.mean(self.x[l[1]:l[1]+2]) - yhi = np.mean(self.y[l[1]:l[1]+2]) + xlo = np.mean(self.x[l[0] - 1 : l[0] + 1]) + ylo = np.mean(self.y[l[0] - 1 : l[0] + 1]) + xhi = np.mean(self.x[l[1] : l[1] + 2]) + yhi = np.mean(self.y[l[1] : l[1] + 2]) boundaries.append((xlo, ylo)) boundaries.append((xhi, yhi)) return np.unique(boundaries) - def plot(self, *args, **kwds): """Plot the data with vertical lines at the cluster divisions. @@ -362,7 +393,7 @@ def plot(self, *args, **kwds): boundaries = self.cluster_boundaries() (ymin, ymax) = ax.get_ylim() for b in boundaries: - plt.axvline(b[0], 0, (b[1]-ymin)/(ymax-ymin), color='k') + plt.axvline(b[0], 0, (b[1] - ymin) / (ymax - ymin), color="k") plt.ion() ax.figure.canvas.draw() return @@ -376,18 +407,22 @@ def animate(self): status = self.status self.reset_clusters() + fig, ax = plt.subplots() + canvas = fig.canvas + background = canvas.copy_from_bbox(ax.bbox) + ymin, ymax = ax.get_ylim() all_lines = [] for i in self: canvas.restore_region(background) boundaries = self.cluster_boundaries() for i, b in enumerate(boundaries): - height = (b[1]-ymin)/(ymax-ymin) + height = (b[1] - ymin) / (ymax - ymin) if i < len(all_lines): all_lines[i].set_xdata([b[0], b[0]]) all_lines[i].set_ydata([0, height]) ax.draw_artist(all_lines[i]) else: - l = plt.axvline(b[0], 0, height, color='k', animated=True) + l = plt.axvline(b[0], 0, height, color="k", animated=True) ax.draw_artist(l) all_lines.append(l) canvas.blit(ax.bbox) @@ -399,21 +434,42 @@ def animate(self): self.status = status return -#End of class DataClusters - -# simple test code -if __name__ == '__main__': +# End of class DataClusters - x = np.array([-2., -1.5, -1., -0.5, 0., 0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5, 5.]) - y = np.array([0.0183156, 0.105399, 0.36788, 0.778806, 1.00012, 0.780731, 0.386195, 0.210798, 0.386195, 0.780731, 1.00012, 0.778806, 0.36788, 0.105399, 0.0183156]) - testcluster = DataClusters(x, y, .1) +# simple test code +if __name__ == "__main__": + + x = np.array( + [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] + ) + y = np.array( + [ + 0.0183156, + 0.105399, + 0.36788, + 0.778806, + 1.00012, + 0.780731, + 0.386195, + 0.210798, + 0.386195, + 0.780731, + 1.00012, + 0.778806, + 0.36788, + 0.105399, + 0.0183156, + ] + ) + + testcluster = DataClusters(x, y, 0.1) testcluster.makeclusters() - print testcluster.clusters + print(testcluster.clusters) adj = testcluster.find_adjacent_clusters() - print adj - if len(adj) >0: + print(adj) + if len(adj) > 0: testcluster.combine_clusters(adj) - print testcluster.clusters + print(testcluster.clusters) diff --git a/diffpy/srmise/modelcluster.py b/diffpy/srmise/modelcluster.py index 10c0677..1b4bfe0 100644 --- a/diffpy/srmise/modelcluster.py +++ b/diffpy/srmise/modelcluster.py @@ -57,8 +57,10 @@ class ModelCovariance(object): def __init__(self, *args, **kwds): """Intialize object.""" - self.cov = None # The raw covariance matrix - self.model = None # ModelParts instance, so both peaks and baseline (if present) + self.cov = None # The raw covariance matrix + self.model = ( + None # ModelParts instance, so both peaks and baseline (if present) + ) # Map i->[n1,n2,...] of the jth ModelPart to the n_i parameters in cov. self.mmap = {} @@ -96,11 +98,14 @@ def setcovariance(self, model, cov): emsg = "Parameter 'cov' must be a square matrix." raise ValueError(emsg) - - if tempcov.shape[0] != model.npars(True) and tempcov.shape[0] != model.npars(False): - emsg = ["Parameter 'cov' must be an nxn matrix, where n is equal to the number of free ", - "parameters in the model, or the total number of parameters (fixed and free) of ", - "the model."] + if tempcov.shape[0] != model.npars(True) and tempcov.shape[0] != model.npars( + False + ): + emsg = [ + "Parameter 'cov' must be an nxn matrix, where n is equal to the number of free ", + "parameters in the model, or the total number of parameters (fixed and free) of ", + "the model.", + ] raise ValueError("".join(emsg)) self.model = model.copy() @@ -113,8 +118,8 @@ def setcovariance(self, model, cov): for i, m in enumerate(model): self.mmap[i] = n + np.arange(m.npars(True)) for j, p in enumerate(m): - self.pmap[(i,j)] = n - self.ipmap[n] = (i,j) + self.pmap[(i, j)] = n + self.ipmap[n] = (i, j) n += 1 if n == tempcov.shape[0]: @@ -122,21 +127,20 @@ def setcovariance(self, model, cov): self.cov = tempcov else: # Create new covariance matrix, making sure to account for fixed pars - self.cov = np.matrix(np.zeros((n,n))) + self.cov = np.matrix(np.zeros((n, n))) - i=0 - rawi=0 + i = 0 + rawi = 0 for i in range(n): j = 0 rawj = 0 if free[i]: for j in range(n): if free[j]: - self.cov[i,j] = cov[rawi,rawj] + self.cov[i, j] = cov[rawi, rawj] rawj += 1 rawi += 1 - def transform(self, in_format, out_format, **kwds): """Transform parameters and covariance matrix under specified change of variables. @@ -168,7 +172,7 @@ def transform(self, in_format, out_format, **kwds): if "parts" in kwds: if kwds["parts"] == "peaks": - parts = range(len(self.model)-1) + parts = range(len(self.model) - 1) elif kwds["parts"] == "baseline": parts = [-1] else: @@ -189,33 +193,41 @@ def transform(self, in_format, out_format, **kwds): for i in parts: start = self.mmap[i][0] - stop = self.mmap[i][-1]+1 + stop = self.mmap[i][-1] + 1 p = self.model[i] try: subg = p.owner().transform_derivatives(p.pars, in_format, out_format) except NotImplementedError: - logger.warning("Transformation gradient not implemented for part %i: %s. Ignoring transformation." %(i, str(p))) + logger.warning( + "Transformation gradient not implemented for part %i: %s. Ignoring transformation." + % (i, str(p)) + ) subg = np.identity(p.npars(True)) except Exception as e: - logger.warning("Transformation gradient failed for part %i: %s. Failed with message %s. Ignoring transformation." %(i, str(p), str(e))) + logger.warning( + "Transformation gradient failed for part %i: %s. Failed with message %s. Ignoring transformation." + % (i, str(p), str(e)) + ) subg = np.identity(p.npars(True)) # Now transform the parameters to match try: p.pars = p.owner().transform_parameters(p.pars, in_format, out_format) except Exception as e: - logger.warning("Parameter transformation failed for part %i: %s. Failed with message %s. Ignoring transformation." %(i, str(p), str(e))) + logger.warning( + "Parameter transformation failed for part %i: %s. Failed with message %s. Ignoring transformation." + % (i, str(p), str(e)) + ) subg = np.identity(p.npars(True)) # Update the global gradient matrix g[start:stop, start:stop] = subg g = np.matrix(g) - self.cov = np.array(g*np.matrix(self.cov).transpose()*g) + self.cov = np.array(g * np.matrix(self.cov).transpose() * g) return - def getcorrelation(self, i, j): """Return the correlation between variables i and j, Corr_ij=Cov_ij/(sigma_i sigma_j) @@ -233,10 +245,12 @@ def getcorrelation(self, i, j): i1 = self.pmap[i] if i in self.pmap else i j1 = self.pmap[j] if j in self.pmap else j - if self.cov[i1,i1] == 0. or self.cov[j1,j1] == 0.: - return 0. # Avoiding undefined quantities is sensible in this context. + if self.cov[i1, i1] == 0.0 or self.cov[j1, j1] == 0.0: + return 0.0 # Avoiding undefined quantities is sensible in this context. else: - return self.cov[i1,j1]/(np.sqrt(self.cov[i1,i1])*np.sqrt(self.cov[j1,j1])) + return self.cov[i1, j1] / ( + np.sqrt(self.cov[i1, i1]) * np.sqrt(self.cov[j1, j1]) + ) def getvalue(self, i): """Return value of parameter i. @@ -254,7 +268,7 @@ def getuncertainty(self, i): which indicate the mth parameter of modelpart l. """ (l, m) = i if i in self.pmap else self.ipmap[i] - return np.sqrt(self.getcovariance(i,i)) + return np.sqrt(self.getcovariance(i, i)) def getcovariance(self, i, j): """Return the covariance between variables i and j. @@ -270,7 +284,7 @@ def getcovariance(self, i, j): i1 = self.pmap[i] if i in self.pmap else i j1 = self.pmap[j] if j in self.pmap else j - return self.cov[i1,j1] + return self.cov[i1, j1] def get(self, i): """Return (value, uncertainty) tuple for parameter i. @@ -298,9 +312,9 @@ def correlationwarning(self, threshold=0.8): correlated = [] for i in range(self.cov.shape[0]): - for j in range(i+1, self.cov.shape[0]): - c = self.getcorrelation(i,j) - if c and np.abs(c) > threshold: # filter out None values + for j in range(i + 1, self.cov.shape[0]): + c = self.getcorrelation(i, j) + if c and np.abs(c) > threshold: # filter out None values correlated.append((self.ipmap[i], self.ipmap[j], c)) return correlated @@ -310,7 +324,7 @@ def __str__(self): return "Model and/or Covariance matrix undefined." lines = [] for i, m in enumerate(self.model): - lines.append(" ".join([self.prettypar((i,j)) for j in range(len(m))])) + lines.append(" ".join([self.prettypar((i, j)) for j in range(len(m))])) return "\n".join(lines) def prettypar(self, i): @@ -322,11 +336,12 @@ def prettypar(self, i): if self.model is None or self.cov is None: return "Model and/or Covariance matrix undefined." k = i if i in self.ipmap else self.pmap[i] - return "%.5e (%.5e)" %(self.getvalue(k), np.sqrt(self.getcovariance(k,k))) + return "%.5e (%.5e)" % (self.getvalue(k), np.sqrt(self.getcovariance(k, k))) # End of class ModelCovariance + class ModelCluster(object): """Associate a contiguous cluster of data with an appropriate model. @@ -399,7 +414,7 @@ def __init__(self, model, *args, **kwds): self.error_method = orig.error_method self.peak_funcs = list(orig.peak_funcs) return - else: # Explicit creation + else: # Explicit creation if model is None: self.model = Peaks([]) else: @@ -468,26 +483,28 @@ def writestr(self, **kwds): if self.peak_funcs is None: lines.append("peak_funcs=None") else: - lines.append("peak_funcs=%s" %repr([pfbaselist.index(p) for p in self.peak_funcs])) + lines.append( + "peak_funcs=%s" % repr([pfbaselist.index(p) for p in self.peak_funcs]) + ) if self.error_method is None: - lines.append('ModelEvaluator=None') + lines.append("ModelEvaluator=None") else: - lines.append('ModelEvaluator=%s' %self.error_method.__name__) + lines.append("ModelEvaluator=%s" % self.error_method.__name__) - lines.append("slice=%s" %repr(self.slice)) + lines.append("slice=%s" % repr(self.slice)) # Indexed baseline functions (unless externally provided) if writeblf: lines.append("## BaselineFunctions") for i, bf in enumerate(blfbaselist): - lines.append('# BaselineFunction %s' %i) + lines.append("# BaselineFunction %s" % i) lines.append(bf.writestr(blfbaselist)) # Indexed peak functions (unless externally provided) if writepf: lines.append("## PeakFunctions") for i, pf in enumerate(pfbaselist): - lines.append('# PeakFunction %s' %i) + lines.append("# PeakFunction %s" % i) lines.append(pf.writestr(pfbaselist)) lines.append("# BaselineObject") @@ -501,17 +518,16 @@ def writestr(self, **kwds): lines.append("None") else: for m in self.model: - lines.append('# ModelPeak') + lines.append("# ModelPeak") lines.append(m.writestr(pfbaselist)) # Raw data in modelcluster. - lines.append('### start data') - lines.append('#L r y dy') + lines.append("### start data") + lines.append("#L r y dy") for i in range(len(self.r_data)): - lines.append('%g %g %g' % \ - (self.r_data[i], self.y_data[i], self.y_error[i]) ) + lines.append("%g %g %g" % (self.r_data[i], self.y_data[i], self.y_error[i])) - datastring = "\n".join(lines)+"\n" + datastring = "\n".join(lines) + "\n" return datastring @staticmethod @@ -545,101 +561,98 @@ def factory(mcstr, **kwds): # - StartData # find data section, and what information it contains - res = re.search(r'^#+ start data\s*(?:#.*\s+)*', mcstr, re.M) + res = re.search(r"^#+ start data\s*(?:#.*\s+)*", mcstr, re.M) if res: - start_data = mcstr[res.end():].strip() - start_data_info = mcstr[res.start():res.end()] - header = mcstr[:res.start()] - res = re.search(r'^(#+L.*)$', start_data_info, re.M) + start_data = mcstr[res.end() :].strip() + start_data_info = mcstr[res.start() : res.end()] + header = mcstr[: res.start()] + res = re.search(r"^(#+L.*)$", start_data_info, re.M) if res: - start_data_info = start_data_info[res.start():res.end()].strip() + start_data_info = start_data_info[res.start() : res.end()].strip() hasr = False hasy = False hasdy = False - res = re.search(r'\br\b', start_data_info) + res = re.search(r"\br\b", start_data_info) if res: hasr = True - res = re.search(r'\by\b', start_data_info) + res = re.search(r"\by\b", start_data_info) if res: hasy = True - res = re.search(r'\bdy\b', start_data_info) + res = re.search(r"\bdy\b", start_data_info) if res: hasdy = True # Model - res = re.search(r'^#+ ModelPeaks.*$', header, re.M) + res = re.search(r"^#+ ModelPeaks.*$", header, re.M) if res: - model_peaks = header[res.end():].strip() - header = header[:res.start()] + model_peaks = header[res.end() :].strip() + header = header[: res.start()] # Baseline Object - res = re.search(r'^#+ BaselineObject\s*(?:#.*\s+)*', header, re.M) + res = re.search(r"^#+ BaselineObject\s*(?:#.*\s+)*", header, re.M) if res: - baselineobject = header[res.end():].strip() - header = header[:res.start()] + baselineobject = header[res.end() :].strip() + header = header[: res.start()] # Peak functions if readpf: - res = re.search(r'^#+ PeakFunctions.*$', header, re.M) + res = re.search(r"^#+ PeakFunctions.*$", header, re.M) if res: - peakfunctions = header[res.end():].strip() - header = header[:res.start()] + peakfunctions = header[res.end() :].strip() + header = header[: res.start()] # Baseline functions if readblf: - res = re.search(r'^#+ BaselineFunctions.*$', header, re.M) + res = re.search(r"^#+ BaselineFunctions.*$", header, re.M) if res: - baselinefunctions = header[res.end():].strip() - header = header[:res.start()] + baselinefunctions = header[res.end() :].strip() + header = header[: res.start()] ### Instantiating baseline functions if readblf: blfbaselist = [] - res = re.split(r'(?m)^#+ BaselineFunction \d+\s*(?:#.*\s+)*', baselinefunctions) + res = re.split( + r"(?m)^#+ BaselineFunction \d+\s*(?:#.*\s+)*", baselinefunctions + ) for s in res[1:]: blfbaselist.append(BaseFunction.factory(s, blfbaselist)) ### Instantiating peak functions if readpf: pfbaselist = [] - res = re.split(r'(?m)^#+ PeakFunction \d+\s*(?:#.*\s+)*', peakfunctions) + res = re.split(r"(?m)^#+ PeakFunction \d+\s*(?:#.*\s+)*", peakfunctions) for s in res[1:]: pfbaselist.append(BaseFunction.factory(s, pfbaselist)) - ### Instantiating header data # peak_funcs - res = re.search(r'^peak_funcs=(.*)$', header, re.M) + res = re.search(r"^peak_funcs=(.*)$", header, re.M) peak_funcs = eval(res.groups()[0].strip()) if peak_funcs is not None: peak_funcs = [pfbaselist[i] for i in peak_funcs] # error_method - res = re.search(r'^ModelEvaluator=(.*)$', header, re.M) + res = re.search(r"^ModelEvaluator=(.*)$", header, re.M) __import__("diffpy.srmise.modelevaluators") module = sys.modules["diffpy.srmise.modelevaluators"] error_method = getattr(module, res.groups()[0].strip()) # slice - res = re.search(r'^slice=(.*)$', header, re.M) + res = re.search(r"^slice=(.*)$", header, re.M) cluster_slice = eval(res.groups()[0].strip()) - ### Instantiating BaselineObject - if re.match(r'^None$', baselineobject): + if re.match(r"^None$", baselineobject): baseline = None else: baseline = Baseline.factory(baselineobject, blfbaselist) - ### Instantiating model model = Peaks() - res = re.split(r'(?m)^#+ ModelPeak\s*(?:#.*\s+)*', model_peaks) + res = re.split(r"(?m)^#+ ModelPeak\s*(?:#.*\s+)*", model_peaks) for s in res[1:]: model.append(Peak.factory(s, pfbaselist)) - - ### Instantiating start data # read actual data - r, y, dy arrays = [] @@ -663,10 +676,13 @@ def factory(mcstr, **kwds): for line in start_data.split("\n"): l = line.split() if len(arrays) != len(l): - emsg = ("Number of value fields does not match that given by '%s'" %start_data_info) + emsg = ( + "Number of value fields does not match that given by '%s'" + % start_data_info + ) for a, v in zip(arrays, line.split()): a.append(float(v)) - except (ValueError, IndexError), err: + except (ValueError, IndexError) as err: raise SrMiseDataFormatError(err) if hasr: r_data = np.array(r_data) @@ -675,8 +691,16 @@ def factory(mcstr, **kwds): if hasdy: y_error = np.array(y_error) - return ModelCluster(model, baseline, r_data, y_data, y_error, cluster_slice, error_method, peak_funcs) - + return ModelCluster( + model, + baseline, + r_data, + y_data, + y_error, + cluster_slice, + error_method, + peak_funcs, + ) @staticmethod def join_adjacent(m1, m2): @@ -724,11 +748,11 @@ def join_adjacent(m1, m2): if not right_ids[0] == left_ids[1]: raise ValueError("Given ModelClusters are not adjacent.") - new_slice=slice(left_ids[0], right_ids[1], 1) + new_slice = slice(left_ids[0], right_ids[1], 1) # Approximately where the clusters meet. - border_x = .5*(left.r_data[left_ids[1]-1] + right.r_data[right_ids[0]]) - border_y = .5*(left.y_data[left_ids[1]-1] + right.y_data[right_ids[0]]) + border_x = 0.5 * (left.r_data[left_ids[1] - 1] + right.r_data[right_ids[0]]) + border_y = 0.5 * (left.y_data[left_ids[1] - 1] + right.y_data[right_ids[0]]) if len(m1.model) > 0 and len(m2.model) > 0: new_model = left.model.copy() @@ -741,24 +765,40 @@ def join_adjacent(m1, m2): # border_x are removed. The highly unlikely case of two peaks # exactly at the border is also handled. for i in reversed(range(len(new_model))): - if new_model[i]["position"] == border_x and \ - i > 0 and new_model[i-1]["position"] == border_x: + if ( + new_model[i]["position"] == border_x + and i > 0 + and new_model[i - 1]["position"] == border_x + ): del new_model[i] elif new_ids[i] != i: - if (new_model[i]["position"] > border_x and new_ids[i] < len(left.model)) and \ - (new_model[i]["position"] < border_x and new_ids[i] >= len(left.model)): + if ( + new_model[i]["position"] > border_x + and new_ids[i] < len(left.model) + ) and ( + new_model[i]["position"] < border_x + and new_ids[i] >= len(left.model) + ): del new_model[i] # Likely to improve any future fitting new_model.match_at(border_x, border_y) elif len(m1.model) > 0: new_model = m1.model.copy() - else: # Only m2 has entries, or both are empty + else: # Only m2 has entries, or both are empty new_model = m2.model.copy() - peak_funcs = list(set(m1.peak_funcs) | set(m2.peak_funcs)) # "Union" - return ModelCluster(new_model, m1.baseline, m1.r_data, m1.y_data, - m1.y_error, new_slice, m1.error_method, peak_funcs) + peak_funcs = list(set(m1.peak_funcs) | set(m2.peak_funcs)) # "Union" + return ModelCluster( + new_model, + m1.baseline, + m1.r_data, + m1.y_data, + m1.y_error, + new_slice, + m1.error_method, + peak_funcs, + ) def change_slice(self, new_slice): """Change the slice which represents the extent of a cluster.""" @@ -786,11 +826,15 @@ def change_slice(self, new_slice): # check if slice has expanded on the left if self.never_fit and self.slice.start < old_slice.start: left_slice = slice(self.slice.start, old_slice.start) - self.never_fit = max(y_data_nobl[left_slice] - self.y_error[left_slice]) < 0 + self.never_fit = ( + max(y_data_nobl[left_slice] - self.y_error[left_slice]) < 0 + ) # check if slice has expanded on the right if self.never_fit and self.slice.stop > old_slice.stop: right_slice = slice(old_slice.stop, self.slice.stop) - self.never_fit = max(y_data_nobl[right_slice] - self.y_error[right_slice]) < 0 + self.never_fit = ( + max(y_data_nobl[right_slice] - self.y_error[right_slice]) < 0 + ) return @@ -808,7 +852,7 @@ def npars(self, count_baseline=True, count_fixed=True): n += self.baseline.npars(count_fixed=count_fixed) return n - def replacepeaks(self, newpeaks, delslice=slice(0,0)): + def replacepeaks(self, newpeaks, delslice=slice(0, 0)): """Replace peaks given by delslice by those in newpeaks. Parameters @@ -824,7 +868,7 @@ def replacepeaks(self, newpeaks, delslice=slice(0,0)): def deletepeak(self, idx): """Delete the peak at the given index.""" - self.replacepeaks([], slice(idx,idx+1)) + self.replacepeaks([], slice(idx, idx + 1)) def estimatepeak(self): """Attempt to add single peak to empty cluster. Return True if successful.""" @@ -840,18 +884,27 @@ def estimatepeak(self): # throw some exception pass selected = self.peak_funcs[0] - estimate = selected.estimate_parameters(self.r_cluster, self.y_cluster - self.valuebl()) - + estimate = selected.estimate_parameters( + self.r_cluster, self.y_cluster - self.valuebl() + ) if estimate is not None: newpeak = selected.actualize(estimate, "internal") - logger.info("Estimate: %s" %newpeak) + logger.info("Estimate: %s" % newpeak) self.replacepeaks(Peaks([newpeak])) return True else: return False - def fit(self, justify=False, ntrials=0, fitbaseline=False, estimate=True, cov=None, cov_format="default_output"): + def fit( + self, + justify=False, + ntrials=0, + fitbaseline=False, + estimate=True, + cov=None, + cov_format="default_output", + ): """Perform a chi-square fit of the model to data in cluster. Parameters @@ -871,11 +924,11 @@ def fit(self, justify=False, ntrials=0, fitbaseline=False, estimate=True, cov=No if self.never_fit: return None if len(self.model) == 0: - #Attempt to add a first peak to the cluster + # Attempt to add a first peak to the cluster if estimate: try: self.estimatepeak() - except SrMiseEstimationError, e: + except SrMiseEstimationError as e: logger.info("Fit: No model to fit, estimation not possible.") return else: @@ -890,7 +943,11 @@ def fit(self, justify=False, ntrials=0, fitbaseline=False, estimate=True, cov=No orig_baseline = self.baseline.copy() self.last_fit_size = self.size - if fitbaseline and self.baseline is not None and self.baseline.npars(count_fixed=False) > 0: + if ( + fitbaseline + and self.baseline is not None + and self.baseline.npars(count_fixed=False) > 0 + ): y_datafit = self.y_data fmodel = ModelParts(self.model) fmodel.append(self.baseline) @@ -899,20 +956,28 @@ def fit(self, justify=False, ntrials=0, fitbaseline=False, estimate=True, cov=No fmodel = self.model try: - fmodel.fit(self.r_data, - y_datafit, - self.y_error, - self.slice, - ntrials, - cov, - cov_format) - except SrMiseFitError, e: - logger.debug("Error while fitting cluster: %s\nReverting to original model.", e) + fmodel.fit( + self.r_data, + y_datafit, + self.y_error, + self.slice, + ntrials, + cov, + cov_format, + ) + except SrMiseFitError as e: + logger.debug( + "Error while fitting cluster: %s\nReverting to original model.", e + ) self.model = orig_model self.baseline = orig_baseline return None - if fitbaseline and self.baseline is not None and self.baseline.npars(count_fixed=False) > 0: + if ( + fitbaseline + and self.baseline is not None + and self.baseline.npars(count_fixed=False) > 0 + ): self.model = Peaks(fmodel[:-1]) self.baseline = fmodel[-1] else: @@ -925,17 +990,15 @@ def fit(self, justify=False, ntrials=0, fitbaseline=False, estimate=True, cov=No # Test for fit improvement if new_qual < orig_qual: # either fit blew up (and leastsq didn't notice) or the fit had already converged. - msg = ["ModelCluster.fit() warning: fit seems not to have improved.", - "Reverting to original model.", - "----------", - "New Quality: %s", - "Original Quality: %s" - "%s", - "----------"] - logger.debug("\n".join(msg), - new_qual.stat, - orig_qual.stat, - self.model) + msg = [ + "ModelCluster.fit() warning: fit seems not to have improved.", + "Reverting to original model.", + "----------", + "New Quality: %s", + "Original Quality: %s" "%s", + "----------", + ] + logger.debug("\n".join(msg), new_qual.stat, orig_qual.stat, self.model) self.model = orig_model self.baseline = orig_baseline @@ -953,9 +1016,11 @@ def fit(self, justify=False, ntrials=0, fitbaseline=False, estimate=True, cov=No # original fit is less likely to obscure any hidden peaks. if justify and len(self.model) == 1 and len(orig_model) > 0: min_npars = min([p.npars for p in self.peak_funcs]) - if new_qual.growth_justified(self, min_npars): - msg = ["ModelCluster.fit(): Fit over current cluster better explained by additional peaks.", - "Reverting to original model."] + if new_qual.growth_justified(self, min_npars): + msg = [ + "ModelCluster.fit(): Fit over current cluster better explained by additional peaks.", + "Reverting to original model.", + ] logger.debug("\n".join(msg)) self.model = orig_model @@ -974,28 +1039,35 @@ def contingent_fit(self, minpoints, growth_threshold): """ if self.never_fit: return None - if (self.last_fit_size > 0 and float(self.size)/self.last_fit_size >= growth_threshold) \ - or (self.last_fit_size == 0 and self.size >= minpoints): + if ( + self.last_fit_size > 0 + and float(self.size) / self.last_fit_size >= growth_threshold + ) or (self.last_fit_size == 0 and self.size >= minpoints): return self.fit(justify=True) return None def cleanfit(self): """Remove poor-quality peaks in the fit. Return number removed.""" - #Find peaks located outside the cluster + # Find peaks located outside the cluster pos = np.array([p["position"] for p in self.model]) left_idx = pos.searchsorted(self.r_cluster[0]) right_idx = pos.searchsorted(self.r_cluster[-1]) outside_idx = range(0, left_idx) outside_idx.extend(range(right_idx, len(self.model))) - #inside_idx = range(left_idx, right_idx) + # inside_idx = range(left_idx, right_idx) # Identify outside peaks that contribute < error everywhere in cluster. # Must check entire cluster and not just nearest endpoint because not # every peak function can be assumed to have its greatest contribution # there, and errors are not necessarily constant. - outside_idx = [i for i in outside_idx \ - if (self.model[i].removable \ - and max(self.model[i].value(self.r_cluster) - self.error_cluster) < 0)] + outside_idx = [ + i + for i in outside_idx + if ( + self.model[i].removable + and max(self.model[i].value(self.r_cluster) - self.error_cluster) < 0 + ) + ] # TODO: Check for peaks that have blown up. # Remember to check if a peak is removable. @@ -1003,10 +1075,14 @@ def cleanfit(self): # NaN is too serious not to remove, even if removable is False, but I should look # into better handling anyway. - nan_idx = [i for i in range(len(self.model)) if np.isnan(self.model[i].pars).any()] + nan_idx = [ + i for i in range(len(self.model)) if np.isnan(self.model[i].pars).any() + ] if len(outside_idx) > 0: - msg = ["Following peaks outside cluster made no contribution within it and were removed:"] + msg = [ + "Following peaks outside cluster made no contribution within it and were removed:" + ] msg.extend([str(self.model[i]) for i in outside_idx]) logger.debug("\n".join(msg)) @@ -1015,11 +1091,11 @@ def cleanfit(self): msg.extend([str(self.model[i]) for i in nan_idx]) logger.debug("\n".join(msg)) -# # TODO: Uncomment when there's a point! -# if len(blown_idx) > 0: -# msg = ["Following peaks inside cluster were too large and had to be removed:"] -# msg.extend([str(self.model[i]) for i in blown_idx]) -# logger.info("\n".join(msg)) + # # TODO: Uncomment when there's a point! + # if len(blown_idx) > 0: + # msg = ["Following peaks inside cluster were too large and had to be removed:"] + # msg.extend([str(self.model[i]) for i in blown_idx]) + # logger.info("\n".join(msg)) # A peak can only be removed once. to_remove = list(set(outside_idx) | set(blown_idx) | set(nan_idx)) @@ -1048,7 +1124,7 @@ def reduce_to(self, x, y): logger.debug("reduce_to: No reduction necessary.") return None orig_model = self.model.copy() - self.model.match_at(x, y-self.valuebl(x)) + self.model.match_at(x, y - self.valuebl(x)) quality = self.fit() # Did reduction help? @@ -1067,13 +1143,15 @@ def reduce_to(self, x, y): def value(self, r=None): """Return value of baseline+model over cluster.""" - if len(self.model)==0: + if len(self.model) == 0: return self.valuebl(r) else: if r is None: - return self.valuebl(r)+(self.model.value(self.r_data, self.slice)[self.slice]) + return self.valuebl(r) + ( + self.model.value(self.r_data, self.slice)[self.slice] + ) else: - return self.valuebl(r)+(self.model.value(r)) + return self.valuebl(r) + (self.model.value(r)) def valuebl(self, r=None): """Return baseline's value over cluster. @@ -1088,10 +1166,10 @@ def valuebl(self, r=None): if r is None: return np.zeros(self.size) else: - return r*0. + return r * 0.0 else: if r is None: - return (self.baseline.value(self.r_data, self.slice)[self.slice]) + return self.baseline.value(self.r_data, self.slice)[self.slice] else: return self.baseline.value(r) @@ -1130,9 +1208,9 @@ def plottable(self, joined=False): else: toreturn = [self.r_cluster, self.y_cluster] bl = self.valuebl() - toreturn.extend([self.r_cluster for i in range(2*len(self.model))]) + toreturn.extend([self.r_cluster for i in range(2 * len(self.model))]) for i, p in enumerate(self.model): - toreturn[2*i+3] = bl + p.value(self.r_data, self.slice)[self.slice] + toreturn[2 * i + 3] = bl + p.value(self.r_data, self.slice)[self.slice] return toreturn def plottable_residual(self): @@ -1149,16 +1227,15 @@ def augment(self, source): best_qual = self.quality() source_model = source.model.copy() - msg = ["==== Augmenting model ====", - "Original fit:", - "%s", - "w/ quality: %s", - "New model fits:", - "%s"] - logger.debug("\n".join(msg), - best_model, - best_qual.stat, - source_model) + msg = [ + "==== Augmenting model ====", + "Original fit:", + "%s", + "w/ quality: %s", + "New model fits:", + "%s", + ] + logger.debug("\n".join(msg), best_model, best_qual.stat, source_model) # Each iteration improves best_model by adding the peak from # source_model to best_model that most improves its quality, breaking @@ -1181,28 +1258,27 @@ def augment(self, source): best_model = test_models[args[-1]] del source_model[args[-1]] else: - break # Best possible model has been found. + break # Best possible model has been found. self.replacepeaks(best_model, slice(len(self.model))) # TODO: Do I need this? If test_model contains peaks # by reference, the fit peaks will change as well. self.fit() - msg = ["Best model after fit is:", - "%s", - "w/ quality: %s", - "================="] - logger.debug("\n".join(msg), - self.model, - best_qual.stat) + msg = ["Best model after fit is:", "%s", "w/ quality: %s", "================="] + logger.debug("\n".join(msg), self.model, best_qual.stat) return def __str__(self): """Return string representation of the cluster.""" - return '\n'.join(["Slice: %s" %self.slice, - "Quality: %s" %self.quality().stat, - "Baseline: %s" %self.baseline, - "Peaks:\n%s" %self.model]) + return "\n".join( + [ + "Slice: %s" % self.slice, + "Quality: %s" % self.quality().stat, + "Baseline: %s" % self.baseline, + "Peaks:\n%s" % self.model, + ] + ) def prune(self): """Remove peaks until model quality no longer improves. @@ -1223,10 +1299,19 @@ def prune(self): tracer.pushc() y_nobl = self.y_cluster - self.valuebl() - prune_mc = ModelCluster(None, None, self.r_cluster, y_nobl, self.error_cluster, None, self.error_method, self.peak_funcs) + prune_mc = ModelCluster( + None, + None, + self.r_cluster, + y_nobl, + self.error_cluster, + None, + self.error_method, + self.peak_funcs, + ) orig_model = self.model.copy() - peak_range = 3 # number of peaks on either side of deleted peak to fit + peak_range = 3 # number of peaks on either side of deleted peak to fit check_models = [] for m in orig_model: if m.removable: @@ -1237,16 +1322,11 @@ def prune(self): best_model = self.model.copy() best_qual = self.quality() - msg = ["====Pruning fits:====", - "Original model:", - "%s", - "w/ quality: %s"] - logger.info("\n".join(msg), - best_model, - best_qual.stat) + msg = ["====Pruning fits:====", "Original model:", "%s", "w/ quality: %s"] + logger.info("\n".join(msg), best_model, best_qual.stat) #### Main prune loop #### - while(check_models.count(None) < len(check_models)): + while check_models.count(None) < len(check_models): # Cache value of individual peaks for best current model. best_modely = [] @@ -1271,57 +1351,50 @@ def prune(self): for i in range(len(check_models)): if check_models[i] is not None: # Create model with ith peak removed, and distant peaks effectively fixed - lo = max(i-peak_range, 0) - hi = min(i+peak_range+1, len(best_model)) + lo = max(i - peak_range, 0) + hi = min(i + peak_range + 1, len(best_model)) check_models[i] = best_model[lo:i].copy() - check_models[i].extend(best_model[i+1:hi].copy()) + check_models[i].extend(best_model[i + 1 : hi].copy()) prune_mc.model = check_models[i] - msg = ["len(check_models): %s", - "len(best_model): %s", - "i: %s"] - logger.debug("\n".join(msg), - len(check_models), - len(best_model), - i) - - addpars = best_model.npars() - check_models[i].npars() - best_model[i].npars(count_fixed=False) + msg = ["len(check_models): %s", "len(best_model): %s", "i: %s"] + logger.debug("\n".join(msg), len(check_models), len(best_model), i) + addpars = ( + best_model.npars() + - check_models[i].npars() + - best_model[i].npars(count_fixed=False) + ) # Remove contribution of (effectively) fixed peaks y = np.array(y_nobl) if lo > 0: - logger.debug("len(sum): %s", len(np.sum(best_modely[:lo], axis=0))) + logger.debug( + "len(sum): %s", len(np.sum(best_modely[:lo], axis=0)) + ) y -= np.sum(best_modely[:lo], axis=0) if hi < len(best_modely): y -= np.sum(best_modely[hi:], axis=0) prune_mc.y_data = y prune_mc.y_cluster = y - msg = ["", - "--- %s ---", - "Removed peak: %s", - "Starting model:", - "%s"] - logger.debug("\n".join(msg), - i, - best_model[i], - prune_mc.model) + msg = [ + "", + "--- %s ---", + "Removed peak: %s", + "Starting model:", + "%s", + ] + logger.debug("\n".join(msg), i, best_model[i], prune_mc.model) - prune_mc.fit(ntrials=int(np.sqrt(len(y))+50), estimate=False) + prune_mc.fit(ntrials=int(np.sqrt(len(y)) + 50), estimate=False) qual = prune_mc.quality(kshift=addpars) check_qual = np.append(check_qual, qual) check_qualidx = np.append(check_qualidx, i) - msg = ["Found model:", - "%s", - "addpars: %s", - "qual: %s"] - logger.debug("\n".join(msg), - prune_mc.model, - addpars, - qual.stat) + msg = ["Found model:", "%s", "addpars: %s", "qual: %s"] + logger.debug("\n".join(msg), prune_mc.model, addpars, qual.stat) # Do not check this peak in the future if quality decreased. if qual < best_qual: @@ -1329,21 +1402,22 @@ def prune(self): arg = check_qual.argsort() - msg = [" - Finished round of pruning -", - "best_qual: %s", - "check_qual: %s", - "sorted check_qual: %s"] - logger.debug("\n".join(msg), - best_qual.stat, - [c.stat for c in check_qual], - arg) + msg = [ + " - Finished round of pruning -", + "best_qual: %s", + "check_qual: %s", + "sorted check_qual: %s", + ] + logger.debug( + "\n".join(msg), best_qual.stat, [c.stat for c in check_qual], arg + ) arg = arg[-1] newbest_qual = check_qual[arg] newbest_qualidx = check_qualidx[arg] if newbest_qual > best_qual: - lo = max(newbest_qualidx-peak_range, 0) - hi = min(newbest_qualidx+peak_range+1, len(orig_model)) + lo = max(newbest_qualidx - peak_range, 0) + hi = min(newbest_qualidx + peak_range + 1, len(orig_model)) bmtemp = best_model[:lo] bmtemp.extend(check_models[newbest_qualidx]) bmtemp.extend(best_model[hi:]) @@ -1360,12 +1434,8 @@ def prune(self): self.model = best_model tracer.emit(self) - msg = ["New best model:", - "%s", - "best_qual: %s"] - logger.debug("\n".join(msg), - best_model, - best_qual.stat) + msg = ["New best model:", "%s", "best_qual: %s"] + logger.debug("\n".join(msg), best_model, best_qual.stat) if len(best_model) > 0: del check_models[newbest_qualidx] @@ -1378,48 +1448,49 @@ def prune(self): else: break - msg = ["Best model after pruning is:", - "%s", - "w/ quality: %s", - "================="] - logger.info("\n".join(msg), - self.model, - self.quality().stat) + msg = [ + "Best model after pruning is:", + "%s", + "w/ quality: %s", + "=================", + ] + logger.info("\n".join(msg), self.model, self.quality().stat) tracer.popc() return + # simple test code -if __name__ == '__main__': +if __name__ == "__main__": from numpy.random import randn from diffpy.srmise.modelevaluators import AICc from diffpy.srmise.peaks import GaussianOverR - pf = GaussianOverR(.7) - res = .01 + pf = GaussianOverR(0.7) + res = 0.01 - pars = [[3, .2, 10], [3.5, .2, 10]] + pars = [[3, 0.2, 10], [3.5, 0.2, 10]] ideal_peaks = Peaks([pf.actualize(p, "pwa") for p in pars]) - r = np.arange(2,4,res) + r = np.arange(2, 4, res) y = ideal_peaks.value(r) + randn(len(r)) err = np.ones(len(r)) evaluator = AICc() - guesspars = [[2.9, .15, 5], [3.6, .3, 5]] + guesspars = [[2.9, 0.15, 5], [3.6, 0.3, 5]] guess_peaks = Peaks([pf.actualize(p, "pwa") for p in guesspars]) cluster = ModelCluster(guess_peaks, None, r, y, err, None, AICc, [pf]) - print "--- Actual Peak parameters ---" - print ideal_peaks + print("--- Actual Peak parameters ---") + print(ideal_peaks) - print "\n--- Before fit ---" - print cluster + print("\n--- Before fit ---") + print(cluster) cluster.fit() - print "\n--- After fit ---" - print cluster + print("\n--- After fit ---") + print(cluster) diff --git a/diffpy/srmise/modelparts.py b/diffpy/srmise/modelparts.py index 1cb6412..4943e0a 100644 --- a/diffpy/srmise/modelparts.py +++ b/diffpy/srmise/modelparts.py @@ -34,8 +34,8 @@ # Before it returned a scalar, later it returned an array of length 1. import pkg_resources as pr -__spv__ = pr.get_distribution('scipy').version -__oldleastsqbehavior__ = (pr.parse_version(__spv__) < pr.parse_version('0.8.0')) +__spv__ = pr.get_distribution("scipy").version +__oldleastsqbehavior__ = pr.parse_version(__spv__) < pr.parse_version("0.8.0") class ModelParts(list): @@ -57,7 +57,16 @@ class ModelParts(list): def __init__(self, *args, **kwds): list.__init__(self, *args, **kwds) - def fit(self, r, y, y_error, range=None, ntrials=0, cov=None, cov_format="default_output"): + def fit( + self, + r, + y, + y_error, + range=None, + ntrials=0, + cov=None, + cov_format="default_output", + ): """Chi-square fit of all free parameters to given data. There must be at least as many free parameters as data points. @@ -75,12 +84,17 @@ def fit(self, r, y, y_error, range=None, ntrials=0, cov=None, cov_format="defaul """ freepars = self.unpack_freepars() if len(freepars) >= len(r): - emsg = "Cannot fit model with " + str(len(freepars)) +\ - " free parametersbut only "+str(len(r)) + " data points." + emsg = ( + "Cannot fit model with " + + str(len(freepars)) + + " free parametersbut only " + + str(len(r)) + + " data points." + ) raise SrMiseFitError(emsg) if len(freepars) == 0: - #emsg = "Cannot fit model with no free parameters." - #raise SrMiseFitError(emsg) + # emsg = "Cannot fit model with no free parameters." + # raise SrMiseFitError(emsg) return if range == None: @@ -95,38 +109,48 @@ def fit(self, r, y, y_error, range=None, ntrials=0, cov=None, cov_format="defaul plt.cla() plt.title("Before") plt.plot(r, y, label="_nolabel_") - plt.plot(r, (y-self.value(r, range=range))-1.1*(max(y) - min(y)), label="_nolabel_") + plt.plot( + r, + (y - self.value(r, range=range)) - 1.1 * (max(y) - min(y)), + label="_nolabel_", + ) for p in self: plt.plot(r, p.value(r, range=range), label=str(p)) plt.ion() try: f = leastsq( - self.residual, # minimize this function - freepars, # initial parameters - args=args, # arguments to residual, residual_jacobian - Dfun=self.residual_jacobian, # explicit Jacobian - col_deriv=1, # order of derivatives in Jacobian + self.residual, # minimize this function + freepars, # initial parameters + args=args, # arguments to residual, residual_jacobian + Dfun=self.residual_jacobian, # explicit Jacobian + col_deriv=1, # order of derivatives in Jacobian full_output=1, - maxfev=ntrials) + maxfev=ntrials, + ) except NotImplementedError: # TODO: Figure out if is worth checking for residual_jacobian # before leastsq(). This exception will either occur almost never # or extremely frequently, and the extra evaluations will add up. logger.info("One or more functions do not define residual_jacobian().") f = leastsq( - self.residual, # minimize this function - freepars, # initial parameters - args=args, # arguments to residual - col_deriv=1, # order of derivatives in Jacobian + self.residual, # minimize this function + freepars, # initial parameters + args=args, # arguments to residual + col_deriv=1, # order of derivatives in Jacobian full_output=1, - maxfev=ntrials) + maxfev=ntrials, + ) except Exception: # Sadly, KeyboardInterrupt, etc. is reraised as minpack.error # Not much I can do about that, though. import traceback - emsg = "Unexpected error in modelparts.fit(). Original exception:\n" +\ - traceback.format_exc() + "End original exception." + + emsg = ( + "Unexpected error in modelparts.fit(). Original exception:\n" + + traceback.format_exc() + + "End original exception." + ) raise SrMiseFitError(emsg) result = f[0] @@ -144,22 +168,34 @@ def fit(self, r, y, y_error, range=None, ntrials=0, cov=None, cov_format="defaul plt.cla() plt.title("After") plt.ion() - plt.plot(r, y, - r, (y-self.value(r, range=range))-1.1*(max(y) - min(y)), - *[i for sublist in [[r, p.value(r, range=range)] for p in self] for i in sublist]) + plt.plot( + r, + y, + r, + (y - self.value(r, range=range)) - 1.1 * (max(y) - min(y)), + *[ + i + for sublist in [[r, p.value(r, range=range)] for p in self] + for i in sublist + ] + ) plt.draw() if srmiselog.wait: - print "Press 'Enter' to continue...", - raw_input() + print( + "Press 'Enter' to continue...", + ) + input() - if f[4] not in (1,2,3,4): + if f[4] not in (1, 2, 3, 4): emsg = "Fit did not succeed -- " + str(f[3]) raise SrMiseFitError(emsg) # clean up parameters for p in self: - p.pars = p.owner().transform_parameters(p.pars, in_format="internal", out_format="internal") + p.pars = p.owner().transform_parameters( + p.pars, in_format="internal", out_format="internal" + ) # Supply estimated covariance matrix if requested. # The precise relationship between f[1] and estimated covariance matrix is a little unclear from @@ -169,28 +205,28 @@ def fit(self, r, y, y_error, range=None, ntrials=0, cov=None, cov_format="defaul pcov = f[1] fvec = f[2]["fvec"] dof = len(r) - len(freepars) - cov.setcovariance(self, pcov*np.sum(fvec**2)/dof) + cov.setcovariance(self, pcov * np.sum(fvec**2) / dof) try: cov.transform(in_format="internal", out_format=cov_format) except SrMiseUndefinedCovarianceError as e: logger.warn("Covariance not defined. Fit may not have converged.") - return -#### Notes on the fit f -# f[0] = solution -# f[1] = Uses the fjac and ipvt optional outputs to construct an estimate of the jacobian around the solution. -# None if a singular matrix encountered (indicates very flat curvature in some direction). -# This matrix must be multiplied by the residual variance to get the covariance of the parameter -# estimates - see curve fit. -# f[2] = dictionary{nfev: int, fvec: array(), fjac: array(), ipvt: array(), qtf: array()} -# nfev - The number of function calls made -# fvec - function (residual) evaluated at solution -# fjac - "a permutation of the R matrix of a QR factorization of the final Jacobian." -# ipvt - integer array defining a permutation matrix P such that fjac*P=QR -# qtf - transpose(q)*fvec -# f[3] = message about results of fit -# f[4] = integer flag. Fit was successful on 1,2,3, or 4. Otherwise unsuccessful. + + #### Notes on the fit f + # f[0] = solution + # f[1] = Uses the fjac and ipvt optional outputs to construct an estimate of the jacobian around the solution. + # None if a singular matrix encountered (indicates very flat curvature in some direction). + # This matrix must be multiplied by the residual variance to get the covariance of the parameter + # estimates - see curve fit. + # f[2] = dictionary{nfev: int, fvec: array(), fjac: array(), ipvt: array(), qtf: array()} + # nfev - The number of function calls made + # fvec - function (residual) evaluated at solution + # fjac - "a permutation of the R matrix of a QR factorization of the final Jacobian." + # ipvt - integer array defining a permutation matrix P such that fjac*P=QR + # qtf - transpose(q)*fvec + # f[3] = message about results of fit + # f[4] = integer flag. Fit was successful on 1,2,3, or 4. Otherwise unsuccessful. def npars(self, count_fixed=True): """Return total number of parameters in all parts. @@ -201,7 +237,7 @@ def npars(self, count_fixed=True): """ n = 0 for p in self: - n+=p.npars(count_fixed=count_fixed) + n += p.npars(count_fixed=count_fixed) return n def pack_freepars(self, freepars): @@ -229,9 +265,9 @@ def residual(self, freepars, r, y_expected, y_error, range=None): try: if range is None: range = slice(0, len(r)) - return (y_expected[range]-total[range])/y_error[range] + return (y_expected[range] - total[range]) / y_error[range] except TypeError: - return (y_expected-total)/y_error + return (y_expected - total) / y_error def residual_jacobian(self, freepars, r, y_expected, y_error, range=None): """Calculate the Jacobian of freepars. @@ -245,22 +281,24 @@ def residual_jacobian(self, freepars, r, y_expected, y_error, range=None): All the data by default. """ if len(freepars) == 0: - raise ValueError("Argument freepars has length 0. The Jacobian " - "is only defined with >=1 free parameters.") + raise ValueError( + "Argument freepars has length 0. The Jacobian " + "is only defined with >=1 free parameters." + ) self.pack_freepars(freepars) - tempJac=[] + tempJac = [] for p in self: - tempJac[len(tempJac):] = p.jacobian(r, range) + tempJac[len(tempJac) :] = p.jacobian(r, range) # Since the residual is (expected - calculated) the jacobian # of the residual has a minus sign. - jac=-np.array([j for j in tempJac if j is not None]) + jac = -np.array([j for j in tempJac if j is not None]) try: if range is None: range = slice(0, len(r)) - return jac[:,range]/y_error[range] + return jac[:, range] / y_error[range] except TypeError: - return jac/y_error + return jac / y_error def value(self, r, range=None): """Calculate total value of all parts over range. @@ -270,14 +308,14 @@ def value(self, r, range=None): range - Slice object specifying region of r and y over which to fit. All the data by default. """ - total = r * 0. + total = r * 0.0 for p in self: total += p.value(r, range) return total def unpack_freepars(self): """Return array of all free parameters.""" - #To check: ravel() sometimes returns a reference and othertimes a copy. + # To check: ravel() sometimes returns a reference and othertimes a copy. # Do I need to use flatten() instead? return np.concatenate([p.compress() for p in self]).ravel() @@ -302,16 +340,16 @@ def covariance(self, format="internal", **kwds): try: idx = int(k[1:]) except ValueError: - emsg = "Invalid format keyword '%s'. They must be specified as 'f0', 'f1', etc." %k + emsg = ( + "Invalid format keyword '%s'. They must be specified as 'f0', 'f1', etc." + % k + ) raise ValueError(emsg) formats[int(k[1:])] = v - - return - def copy(self): """Return deep copy of this ModelParts. @@ -321,7 +359,7 @@ def copy(self): def __str__(self): """Return string representation of this ModelParts.""" - return ''.join([str(p)+"\n" for p in self]) + return "".join([str(p) + "\n" for p in self]) def __getslice__(self, i, j): """Extends list.__getslice__""" @@ -338,10 +376,15 @@ def transform(self, in_format="internal", out_format="internal"): try: p.pars = p.owner().transform_parameters(p.pars, in_format, out_format) except ValueError: - logger.info("Invalid parameter transformation: Ignoring %s->%s for function of type %s." %(in_format, out_format, p.owner().getmodule())) + logger.info( + "Invalid parameter transformation: Ignoring %s->%s for function of type %s." + % (in_format, out_format, p.owner().getmodule()) + ) + # End of class ModelParts + class ModelPart(object): """Represents a single part (instance of some function) of a model. @@ -388,18 +431,22 @@ def __init__(self, owner, pars, free=None, removable=True, static_owner=False): self._owner = owner if len(pars) != owner.npars: - emsg = "The length of pars must equal the number of parameters "+\ - "specified by the model part owner." + emsg = ( + "The length of pars must equal the number of parameters " + + "specified by the model part owner." + ) raise ValueError(emsg) - self.pars = np.array(pars[:]) # pars[:] in case pars is a ModelPart + self.pars = np.array(pars[:]) # pars[:] in case pars is a ModelPart if free is None: self.free = np.array([True for p in pars], dtype=bool) else: self.free = np.array(free, dtype=bool) if len(self.free) != owner.npars: - emsg = "The length of free must be equal to the number of "+\ - "parameters specified by the model part owner." + emsg = ( + "The length of free must be equal to the number of " + + "parameters specified by the model part owner." + ) raise ValueError(emsg) self.removable = removable @@ -419,8 +466,10 @@ def changeowner(self, owner): emsg = "Cannot change owner if static_owner is True." raise SrMiseStaticOwnerError(emsg) if self._owner.npars != owner.npars: - emsg = "New owner specifies different number of parameters than "+\ - "original owner." + emsg = ( + "New owner specifies different number of parameters than " + + "original owner." + ) raise SrMiseStaticOwnerError(emsg) self._owner = owner @@ -453,8 +502,8 @@ def update(self, freepars): """ numfree = self.npars(count_fixed=False) if len(freepars) < numfree: - pass # raise "freepars does not have enough elements to - # update every unheld parameter." + pass # raise "freepars does not have enough elements to + # update every unheld parameter." # TODO: Check if I need to make copies here, or if references # to parameters are safe. self.pars[self.free] = freepars[:numfree] @@ -475,7 +524,9 @@ def copy(self): The original and the copy are completely independent, except they both reference the same owner.""" - return type(self).__call__(self._owner, self.pars, self.free, self.removable, self.static_owner) + return type(self).__call__( + self._owner, self.pars, self.free, self.removable, self.static_owner + ) def __getitem__(self, key_or_idx): """Return parameter of peak corresponding with key_or_idx. @@ -529,20 +580,26 @@ def npars(self, count_fixed=True): def __str__(self): """Return string representation of ModelPart parameters.""" - return str(self._owner.transform_parameters(self.pars, in_format="internal", out_format="default_output")) + return str( + self._owner.transform_parameters( + self.pars, in_format="internal", out_format="default_output" + ) + ) def __eq__(self, other): - """ """ + """ """ if hasattr(other, "_owner"): - return ((self._owner is other._owner) and - np.all(self.pars == other.pars) and - np.all(self.free == other.free) and - self.removable == other.removable) + return ( + (self._owner is other._owner) + and np.all(self.pars == other.pars) + and np.all(self.free == other.free) + and self.removable == other.removable + ) else: return False def __ne__(self, other): - """ """ + """ """ return not self == other def writestr(self, ownerlist): @@ -557,19 +614,20 @@ def writestr(self, ownerlist): emsg = "ownerlist does not contain this ModelPart's owner." raise ValueError(emsg) lines = [] - lines.append("owner=%s" %repr(ownerlist.index(self._owner))) - - #Lists/numpy arrays don't give full representation of long lists - lines.append("pars=[%s]" %", ".join([repr(p) for p in self.pars])) - lines.append("free=[%s]" %", ".join([repr(f) for f in self.free])) - lines.append("removable=%s" %repr(self.removable)) - lines.append("static_owner=%s" %repr(self.static_owner)) - datastring = "\n".join(lines)+"\n" + lines.append("owner=%s" % repr(ownerlist.index(self._owner))) + + # Lists/numpy arrays don't give full representation of long lists + lines.append("pars=[%s]" % ", ".join([repr(p) for p in self.pars])) + lines.append("free=[%s]" % ", ".join([repr(f) for f in self.free])) + lines.append("removable=%s" % repr(self.removable)) + lines.append("static_owner=%s" % repr(self.static_owner)) + datastring = "\n".join(lines) + "\n" return datastring + # End of class ModelPart # simple test code -if __name__ == '__main__': +if __name__ == "__main__": pass diff --git a/diffpy/srmise/multimodelselection.py b/diffpy/srmise/multimodelselection.py index 35d9e08..01995e2 100644 --- a/diffpy/srmise/multimodelselection.py +++ b/diffpy/srmise/multimodelselection.py @@ -23,13 +23,15 @@ logger = logging.getLogger("diffpy.srmise") + def eatkwds(*args, **kwds): """Convenience function to remove all keywords in args from kwds.""" for k in args: if k in kwds: - print "Keyword %s=%s ignored." %(k, kwds.pop(k)) + print("Keyword %s=%s ignored." % (k, kwds.pop(k))) return kwds + class MultimodelSelection(PeakStability): """Quick and dirty multimodel selection using AIC and its offspring.""" @@ -52,7 +54,9 @@ def __init__(self): self.classweights = {} self.classprobs = {} self.sortedclassprobs = {} - self.sortedclasses = {} # dg->as self.classes, but with model indices sorted by best AIC + self.sortedclasses = ( + {} + ) # dg->as self.classes, but with model indices sorted by best AIC PeakStability.__init__(self) return @@ -67,7 +71,9 @@ def makeaics(self, dgs, dr, filename=None): nominal value. filename - Optional file to save pickled results """ - aics_out = {} # Version of self.aics that holds only the statistic, not the AIC object. + aics_out = ( + {} + ) # Version of self.aics that holds only the statistic, not the AIC object. self.dgs = np.array(dgs) for i, dg in enumerate(self.dgs): self.dgs_idx[dg] = i @@ -77,12 +83,11 @@ def makeaics(self, dgs, dr, filename=None): (r, y, dr, dy) = self.ppe.resampledata(dr) for model_idx in range(len(self.results)): - print "Testing model %s of %s." %(model_idx, len(self.results)) + print("Testing model %s of %s." % (model_idx, len(self.results))) result = self.results[model_idx] em = self.ppe.error_method - # This section dependent on spaghetti code elsewhere in srmise! # Short cut evaluation of AICs which doesn't require calculating chi-square # over and over again. This method assumes that the various uncertainties @@ -95,13 +100,17 @@ def makeaics(self, dgs, dr, filename=None): # modelevaluators subpackage are in need of a rewrite, and so it would be # best to do them all at once. dg0 = self.dgs[0] - mc = ModelCluster(result[1], result[2], r, y, dg0*np.ones(len(r)), None, em, self.ppe.pf) + mc = ModelCluster( + result[1], result[2], r, y, dg0 * np.ones(len(r)), None, em, self.ppe.pf + ) em0 = mc.quality() for dg in self.dgs: em_instance = em() - em_instance.chisq = em0.chisq*(dg0/dg)**2 # rescale chi-square - em_instance.evaluate(mc) # evaluate AIC without recalculating chi-square + em_instance.chisq = em0.chisq * (dg0 / dg) ** 2 # rescale chi-square + em_instance.evaluate( + mc + ) # evaluate AIC without recalculating chi-square self.aics[dg].append(em_instance) aics_out[dg].append(em_instance.stat) @@ -111,10 +120,10 @@ def makeaics(self, dgs, dr, filename=None): if filename is not None: try: - import cPickle as pickle + import cPickle as pickle except: - import pickle - out_s = open(filename, 'wb') + import pickle + out_s = open(filename, "wb") pickle.dump(aics_out, out_s) out_s.close() @@ -123,10 +132,10 @@ def makeaics(self, dgs, dr, filename=None): def loadaics(self, filename): """Load file containing results of the testall method.""" try: - import cPickle as pickle + import cPickle as pickle except: - import pickle - in_s = open(filename, 'rb') + import pickle + in_s = open(filename, "rb") aics_in = pickle.load(in_s) in_s.close() @@ -171,7 +180,7 @@ def makesortedprobs(self): for dg in self.dgs: self.sortedprobs[dg] = np.argsort(self.aicprobs[dg]).tolist() - def animate_probs(self, step=False, duration=0., **kwds): + def animate_probs(self, step=False, duration=0.0, **kwds): """Show animation of extracted peaks from first to last. Parameters: @@ -181,14 +190,17 @@ def animate_probs(self, step=False, duration=0., **kwds): Keywords passed to pyplot.plot()""" if duration > 0: import time - sleeptime = duration/len(self.dgs) + + sleeptime = duration / len(self.dgs) plt.ion() plt.subplot(211) best_idx = self.sortedprobs[self.dgs[0]][-1] - line, = plt.plot(self.dgs, self.aicprobs[self.dgs[0]]) + (line,) = plt.plot(self.dgs, self.aicprobs[self.dgs[0]]) vline = plt.axvline(self.dgs[0]) - dot, = plt.plot(self.dgs[best_idx],self.aicprobs[self.dgs[0]][best_idx],'ro') + (dot,) = plt.plot( + self.dgs[best_idx], self.aicprobs[self.dgs[0]][best_idx], "ro" + ) plt.subplot(212) self.setcurrent(best_idx) @@ -208,11 +220,11 @@ def animate_probs(self, step=False, duration=0., **kwds): plt.ion() plt.draw() if step: - raw_input() + input() if duration > 0: time.sleep(sleeptime) - def animate_classprobs(self, step=False, duration=0., **kwds): + def animate_classprobs(self, step=False, duration=0.0, **kwds): """Show animation of extracted peaks from first to last. Parameters: @@ -222,23 +234,31 @@ def animate_classprobs(self, step=False, duration=0., **kwds): Keywords passed to pyplot.plot()""" if duration > 0: import time - sleeptime = duration/len(self.dgs) + + sleeptime = duration / len(self.dgs) plt.ion() ax1 = plt.subplot(211) bestclass_idx = self.sortedclassprobs[self.dgs[0]][-1] best_idx = self.sortedclasses[self.dgs[0]][bestclass_idx][-1] - arrow_left = len(self.classes)-1 - arrow_right = arrow_left + .05*arrow_left - line, = plt.plot(range(len(self.classes)), self.classprobs[self.dgs[0]]) - dot, = plt.plot(self.dgs[best_idx],self.classprobs[self.dgs[0]][bestclass_idx],'ro') - plt.axvline(arrow_left, color='k') + arrow_left = len(self.classes) - 1 + arrow_right = arrow_left + 0.05 * arrow_left + (line,) = plt.plot(range(len(self.classes)), self.classprobs[self.dgs[0]]) + (dot,) = plt.plot( + self.dgs[best_idx], self.classprobs[self.dgs[0]][bestclass_idx], "ro" + ) + plt.axvline(arrow_left, color="k") ax2 = ax1.twinx() - ax2.set_ylim(self.dgs[0],self.dgs[-1]) + ax2.set_ylim(self.dgs[0], self.dgs[-1]) ax2.set_ylabel("dg") ax1.set_xlim(right=arrow_right) ax2.set_xlim(right=arrow_right) - dgarrow = ax2.annotate("",(arrow_right, self.dgs[0]), (arrow_left, self.dgs[0]), arrowprops=dict(arrowstyle="-|>")) + dgarrow = ax2.annotate( + "", + (arrow_right, self.dgs[0]), + (arrow_left, self.dgs[0]), + arrowprops=dict(arrowstyle="-|>"), + ) plt.subplot(212) self.setcurrent(best_idx) @@ -247,7 +267,7 @@ def animate_classprobs(self, step=False, duration=0., **kwds): minval = np.min(val[1::2]) [r, res] = tempcluster.plottable_residual() plt.plot(*val) - plt.plot(r, minval-np.max(res)+res) + plt.plot(r, minval - np.max(res) + res) for dg in self.dgs[1:]: plt.ioff() line.set_ydata(self.classprobs[dg]) @@ -263,13 +283,13 @@ def animate_classprobs(self, step=False, duration=0., **kwds): minval = np.min(val[1::2]) [r, res] = tempcluster.plottable_residual() plt.plot(*val) - plt.plot(r, minval-np.max(res)+res) + plt.plot(r, minval - np.max(res) + res) dot.set_xdata(bestclass_idx) dot.set_ydata(self.classprobs[dg][bestclass_idx]) plt.ion() plt.draw() if step: - raw_input() + input() if duration > 0: time.sleep(sleeptime) @@ -295,16 +315,18 @@ def classify(self, r, tolerance=0.05): self.classes_idx = {} self.class_tolerance = None - classes = [] # each element is a list of the models (result indices) in the class - classes_idx = {} # given an integer corresponding to a model, return its class - epsqval = {} # holds the squared value of each class' exemplar peaks - ebsqval = {} # holds the squared value of each class exemplar baselines + classes = ( + [] + ) # each element is a list of the models (result indices) in the class + classes_idx = {} # given an integer corresponding to a model, return its class + epsqval = {} # holds the squared value of each class' exemplar peaks + ebsqval = {} # holds the squared value of each class exemplar baselines for i in range(len(self.results)): peaks = self.results[i][1] baseline = self.results[i][2] - bsqval = baseline.value(r)**2 - psqval = [p.value(r)**2 for p in peaks] + bsqval = baseline.value(r) ** 2 + psqval = [p.value(r) ** 2 for p in peaks] added_to_class = False for c in range(len(classes)): @@ -318,10 +340,10 @@ def classify(self, r, tolerance=0.05): continue # check peak types and number of parameters - badpeak=False + badpeak = False if len(peaks) != len(exemplar_peaks): continue - for p, ep in zip(peaks,exemplar_peaks): + for p, ep in zip(peaks, exemplar_peaks): if type(p) != type(ep): badpeak = True break @@ -333,19 +355,23 @@ def classify(self, r, tolerance=0.05): # check peak values current_psqval = [] - for p, ep in zip(psqval,epsqval[c]): - basediff = np.abs(np.sum(p-ep)) - #if basediff > tolerance*np.sum(ep): - if basediff > tolerance*np.sum(ep) or basediff > tolerance*np.sum(p): + for p, ep in zip(psqval, epsqval[c]): + basediff = np.abs(np.sum(p - ep)) + # if basediff > tolerance*np.sum(ep): + if basediff > tolerance * np.sum( + ep + ) or basediff > tolerance * np.sum(p): badpeak = True break if badpeak: continue # check baseline values - basediff = np.abs(np.sum(bsqval-ebsqval[c])) - #if basediff > tolerance*np.sum(ebsqval[c]): - if basediff > tolerance*np.sum(ebsqval[c]) or basediff > tolerance*np.sum(bsqval): + basediff = np.abs(np.sum(bsqval - ebsqval[c])) + # if basediff > tolerance*np.sum(ebsqval[c]): + if basediff > tolerance * np.sum( + ebsqval[c] + ) or basediff > tolerance * np.sum(bsqval): continue # that's all the checks, add to current class @@ -357,7 +383,7 @@ def classify(self, r, tolerance=0.05): if added_to_class is False: # make a new class with the current model as exemplar classes.append([i]) - classnum = len(classes)-1 + classnum = len(classes) - 1 classes_idx[i] = classnum epsqval[classnum] = psqval ebsqval[classnum] = bsqval @@ -393,7 +419,9 @@ def makeclassweights(self): for dg in self.dgs: bestinclass = [cls[-1] for cls in self.sortedclasses[dg]] - self.classweights[dg] = em.akaikeweights([self.aics[dg][b] for b in bestinclass]) + self.classweights[dg] = em.akaikeweights( + [self.aics[dg][b] for b in bestinclass] + ) def makeclassprobs(self): self.classprobs = {} @@ -401,7 +429,9 @@ def makeclassprobs(self): for dg in self.dgs: bestinclass = [cls[-1] for cls in self.sortedclasses[dg]] - self.classprobs[dg] = em.akaikeprobs([self.aics[dg][b] for b in bestinclass]) + self.classprobs[dg] = em.akaikeprobs( + [self.aics[dg][b] for b in bestinclass] + ) def makesortedclassprobs(self): self.sortedclassprobs = {} @@ -411,7 +441,7 @@ def makesortedclassprobs(self): def dg_key(self, dg_in): """Return the dg value usable as a key nearest to dg_in.""" - idx = (np.abs(self.dgs-dg_in)).argmin() + idx = (np.abs(self.dgs - dg_in)).argmin() return self.dgs[idx] def bestclasses(self, dgs=None): @@ -489,7 +519,7 @@ def plot3dclassprobs(self, **kwds): from mpl_toolkits.mplot3d import Axes3D fig = kwds.pop("figure", plt.gcf()) - ax = fig.add_subplot(kwds.pop("subplot",111), projection='3d') + ax = fig.add_subplot(kwds.pop("subplot", 111), projection="3d") cbkwds = kwds.copy() @@ -497,16 +527,16 @@ def plot3dclassprobs(self, **kwds): dGs = kwds.pop("dGs", self.dgs) highlight = kwds.pop("highlight", []) classes = kwds.pop("classes", range(len(self.classes))) - probfilter = kwds.pop("probfilter", [0.,1.]) + probfilter = kwds.pop("probfilter", [0.0, 1.0]) class_size = kwds.pop("class_size", "number") norm = kwds.pop("norm", "auto") cmap = kwds.pop("cmap", cm.jet) highlight_cmap = kwds.pop("highlight_cmap", cm.gray) title = kwds.pop("title", True) p_alpha = kwds.pop("p_alpha", 0.7) - scale = kwds.pop("scale", 1.) + scale = kwds.pop("scale", 1.0) - xs = dGs*scale + xs = dGs * scale verts = [] zs = [] zlabels = [] @@ -515,22 +545,22 @@ def plot3dclassprobs(self, **kwds): maxys = np.max(ys) if maxys >= probfilter[0] and maxys <= probfilter[1]: - p0, p1 = ((xs[0], 0), (xs[-1],0)) # points to close the vertices - verts.append(np.concatenate([[p0], zip(xs,ys), [p1], [p0]])) + p0, p1 = ((xs[0], 0), (xs[-1], 0)) # points to close the vertices + verts.append(np.concatenate([[p0], zip(xs, ys), [p1], [p0]])) zlabels.append(i) ### Define face colors fc = np.array([len(self.classes[z]) for z in zlabels]) if class_size is "fraction": - fc = fc/float(len(self.results)) + fc = fc / float(len(self.results)) # Index the colormap if necessary if class_size is "number": if norm is "auto": - indexedcolors = cmap(np.linspace(0., 1., np.max(fc))) + indexedcolors = cmap(np.linspace(0.0, 1.0, np.max(fc))) cmap = colors.ListedColormap(indexedcolors) elif norm is "full": - indexedcolors = cmap(np.linspace(0., 1., len(self.results))) + indexedcolors = cmap(np.linspace(0.0, 1.0, len(self.results))) cmap = colors.ListedColormap(indexedcolors) # A user-specified norm cannot be used to index a colormap. @@ -540,45 +570,59 @@ def plot3dclassprobs(self, **kwds): mic = np.min(fc) mac = np.max(fc) nc = mac - mic + 1 - norm = colors.BoundaryNorm(np.linspace(mic, mac+1, nc+1), nc) + norm = colors.BoundaryNorm(np.linspace(mic, mac + 1, nc + 1), nc) if class_size is "fraction": norm = colors.Normalize() norm.autoscale(fc) elif norm is "full": mcolor = len(self.results) if class_size is "number": - norm = colors.BoundaryNorm(np.linspace(0, mcolor+1, mcolor+2), mcolor+1) + norm = colors.BoundaryNorm( + np.linspace(0, mcolor + 1, mcolor + 2), mcolor + 1 + ) if class_size is "fraction": - norm = colors.Normalize(0., 1.) + norm = colors.Normalize(0.0, 1.0) zs = np.arange(len(zlabels)) poly = PolyCollection(verts, facecolors=cmap(norm(fc)), closed=False) poly.set_alpha(p_alpha) - cax = ax.add_collection3d(poly, zs=zs, zdir='y') + cax = ax.add_collection3d(poly, zs=zs, zdir="y") # Highlight values of interest color_idx = np.linspace(0, 1, len(highlight)) for dG, ci in zip(highlight, color_idx): for z_logical, z_plot in zip(zlabels, zs): - ax.plot([dG, dG], [z_plot, z_plot], [0, self.classprobs[dG][z_logical]], color=highlight_cmap(ci), alpha=p_alpha) - - ax.set_xlabel('dG') - ax.set_xlim3d(dGs[0]*scale, dGs[-1]*scale) - ax.set_ylabel('Class') + ax.plot( + [dG, dG], + [z_plot, z_plot], + [0, self.classprobs[dG][z_logical]], + color=highlight_cmap(ci), + alpha=p_alpha, + ) + + ax.set_xlabel("dG") + ax.set_xlim3d(dGs[0] * scale, dGs[-1] * scale) + ax.set_ylabel("Class") ax.set_ylim3d(zs[0], zs[-1]) ax.set_yticks(zs) ax.set_yticklabels([str(z) for z in zlabels]) - ax.set_zlabel('Akaike probability') + ax.set_zlabel("Akaike probability") ax.set_zlim3d(0, 1) if title is True: - title = "Class probabilities\n\ + title = ( + "Class probabilities\n\ Max probabilities in %s\n\ - %i/%i classes with %i/%i models displayed"\ - %(probfilter, - len(zs), len(self.classes), - np.sum([len(self.classes[z]) for z in zlabels]), len(self.results) ) + %i/%i classes with %i/%i models displayed" + % ( + probfilter, + len(zs), + len(self.classes), + np.sum([len(self.classes[z]) for z in zlabels]), + len(self.results), + ) + ) if title is not False: figtitle = fig.suptitle(title) @@ -586,23 +630,26 @@ def plot3dclassprobs(self, **kwds): # Add colorbar if "cbpos" in kwds: cbpos = kwds.pop("cbpos") - aspect = cbpos[3]/cbpos[2] - plt.tight_layout() # do it before cbaxis, so colorbar is ignored. + aspect = cbpos[3] / cbpos[2] + plt.tight_layout() # do it before cbaxis, so colorbar is ignored. transAtoF = ax.transAxes + fig.transFigure.inverted() rect = transforms.Bbox.from_bounds(*cbpos).transformed(transAtoF).bounds cbaxis = fig.add_axes(rect) # Remove all colorbar.make_axes keywords except orientation - kwds = eatkwds("fraction", "pad", "shrink", "aspect", - "anchor", "panchor", **kwds) + kwds = eatkwds( + "fraction", "pad", "shrink", "aspect", "anchor", "panchor", **kwds + ) else: kwds.setdefault("shrink", 0.75) # In matplotlib 1.1.0 make_axes_gridspec ignores anchor and panchor keywords. # Eat these keywords for now. kwds = eatkwds("anchor", "panchor", **kwds) - cbaxis, kwds = colorbar.make_axes_gridspec(ax, **kwds) # gridspec allows tight_layout - plt.tight_layout() # do it after cbaxis, so colorbar isn't ignored + cbaxis, kwds = colorbar.make_axes_gridspec( + ax, **kwds + ) # gridspec allows tight_layout + plt.tight_layout() # do it after cbaxis, so colorbar isn't ignored cb = colorbar.ColorbarBase(cbaxis, cmap=cmap, norm=norm, **kwds) @@ -611,8 +658,7 @@ def plot3dclassprobs(self, **kwds): elif class_size is "fraction": cb.set_label("Fraction of models in class") - - return {"fig":fig, "axis":ax, "cb":cb, "cbaxis": cbaxis} + return {"fig": fig, "axis": ax, "cb": cb, "cbaxis": cbaxis} def get_model(self, dG, **kwds): """Return index of best model of best class at given dG. @@ -625,7 +671,8 @@ def get_model(self, dG, **kwds): corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). morder - Which model to get based on AIC. Ordered from best to worst from 0 (the default). Returns a model from a class, or from the collection of all models if classes are ignored. - cls - Override corder with a specific class index, or None to ignore classes entirely.""" + cls - Override corder with a specific class index, or None to ignore classes entirely. + """ corder = kwds.pop("corder", 0) morder = kwds.pop("morder", 0) if "cls" in kwds: @@ -634,9 +681,9 @@ def get_model(self, dG, **kwds): cls = self.get_class(dG, corder=corder) if cls is None: - return self.sortedprobs[dG][-1-morder] + return self.sortedprobs[dG][-1 - morder] else: - return self.sortedclasses[dG][cls][-1-morder] + return self.sortedclasses[dG][cls][-1 - morder] def get_class(self, dG, **kwds): """Return index of best class at given dG. @@ -646,9 +693,10 @@ def get_class(self, dG, **kwds): Keywords: - corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default).""" + corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). + """ corder = kwds.pop("corder", 0) - return self.sortedclassprobs[dG][-1-corder] # index of corderth best class + return self.sortedclassprobs[dG][-1 - corder] # index of corderth best class def get_prob(self, dG, **kwds): """Return Akaike probability of best model of best class at given dG. @@ -661,7 +709,8 @@ def get_prob(self, dG, **kwds): corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). morder - Which model to get based on AIC. Ordered from best to worst from 0 (the default). Returns a model from a class, or from the collection of all models if classes are ignored. - cls - Override corder with a specific class index, or None to ignore classes entirely.""" + cls - Override corder with a specific class index, or None to ignore classes entirely. + """ idx = self.get_model(dG, **kwds) if "cls" in kwds and kwds["cls"] is None: return self.aicprobs[dG][idx] @@ -680,7 +729,8 @@ def get_nfree(self, dG, **kwds): corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). morder - Which model to get based on AIC. Ordered from best to worst from 0 (the default). Returns a model from a class, or from the collection of all models if classes are ignored. - cls - Override corder with a specific class index, or None to ignore classes entirely.""" + cls - Override corder with a specific class index, or None to ignore classes entirely. + """ idx = self.get_model(dG, **kwds) model = self.results[idx][1] baseline = self.results[idx][2] @@ -697,7 +747,8 @@ def get_aic(self, dG, **kwds): corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). morder - Which model to get based on AIC. Ordered from best to worst from 0 (the default). Returns a model from a class, or from the collection of all models if classes are ignored. - cls - Override corder with a specific class index, or None to ignore classes entirely.""" + cls - Override corder with a specific class index, or None to ignore classes entirely. + """ idx = self.get_model(dG, **kwds) return self.aics[dG][idx].stat @@ -714,13 +765,16 @@ def get(self, dG, *args, **kwds): corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). morder - Which model to get based on AIC. Ordered from best to worst from 0 (the default). Returns a model from a class, or from the collection of all models if classes are ignored. - cls - Override corder with a specific class index, or None to ignore classes entirely.""" - fdict = {"aic": self.get_aic, - "class": self.get_class, - "dg": lambda x: x, - "model": self.get_model, - "nfree": self.get_nfree, - "prob": self.get_prob} + cls - Override corder with a specific class index, or None to ignore classes entirely. + """ + fdict = { + "aic": self.get_aic, + "class": self.get_class, + "dg": lambda x: x, + "model": self.get_model, + "nfree": self.get_nfree, + "prob": self.get_prob, + } values = [] for a in args: values.append(fdict[a].__call__(dG, **kwds)) diff --git a/diffpy/srmise/pdfdataset.py b/diffpy/srmise/pdfdataset.py index 2bdd9bf..feb64fa 100644 --- a/diffpy/srmise/pdfdataset.py +++ b/diffpy/srmise/pdfdataset.py @@ -31,6 +31,7 @@ class PDFComponent(object): """Common base class.""" + def __init__(self, name): """initialize @@ -38,13 +39,14 @@ def __init__(self, name): """ self.name = name - def close ( self, force = False ): + def close(self, force=False): """close myself force -- if forcibly (no wait) """ pass + class PDFDataSet(PDFComponent): """PDFDataSet is a class for experimental PDF data. @@ -75,9 +77,21 @@ class PDFDataSet(PDFComponent): refinableVars -- set (dict) of refinable variable names. """ - persistentItems = [ 'robs', 'Gobs', 'drobs', 'dGobs', 'stype', 'qmax', - 'qdamp', 'qbroad', 'dscale', 'rmin', 'rmax', 'metadata' ] - refinableVars = dict.fromkeys(('qdamp', 'qbroad', 'dscale')) + persistentItems = [ + "robs", + "Gobs", + "drobs", + "dGobs", + "stype", + "qmax", + "qdamp", + "qbroad", + "dscale", + "rmin", + "rmax", + "metadata", + ] + refinableVars = dict.fromkeys(("qdamp", "qbroad", "dscale")) def __init__(self, name): """Initialize. @@ -94,7 +108,7 @@ def clear(self): self.Gobs = [] self.drobs = [] self.dGobs = [] - self.stype = 'X' + self.stype = "X" # user must specify qmax to get termination ripples self.qmax = 0.0 self.qdamp = 0.001 @@ -149,16 +163,17 @@ def read(self, filename): returns self """ try: - self.readStr(open(filename,'rb').read()) - except PDFDataFormatError, err: + self.readStr(open(filename, "rb").read()) + except PDFDataFormatError as err: basename = os.path.basename(filename) - emsg = ("Could not open '%s' due to unsupported file format " + - "or corrupted data. [%s]") % (basename, err) + emsg = ( + "Could not open '%s' due to unsupported file format " + + "or corrupted data. [%s]" + ) % (basename, err) raise SrMiseFileError(emsg) self.filename = os.path.abspath(filename) return self - def readStr(self, datastring): """read experimental PDF data from a string @@ -168,15 +183,15 @@ def readStr(self, datastring): """ self.clear() # useful regex patterns: - rx = { 'f' : r'[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?' } + rx = {"f": r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?"} # find where does the data start - res = re.search(r'^#+ start data\s*(?:#.*\s+)*', datastring, re.M) + res = re.search(r"^#+ start data\s*(?:#.*\s+)*", datastring, re.M) # start_data is position where the first data line starts if res: start_data = res.end() else: # find line that starts with a floating point number - regexp = r'^\s*%(f)s' % rx + regexp = r"^\s*%(f)s" % rx res = re.search(regexp, datastring, re.M) if res: start_data = res.start() @@ -186,18 +201,18 @@ def readStr(self, datastring): databody = datastring[start_data:].strip() # find where the metadata starts - metadata = '' - res = re.search(r'^#+\ +metadata\b\n', header, re.M) + metadata = "" + res = re.search(r"^#+\ +metadata\b\n", header, re.M) if res: - metadata = header[res.end():] - header = header[:res.start()] + metadata = header[res.end() :] + header = header[: res.start()] # parse header # stype - if re.search('(x-?ray|PDFgetX)', header, re.I): - self.stype = 'X' - elif re.search('(neutron|PDFgetN)', header, re.I): - self.stype = 'N' + if re.search("(x-?ray|PDFgetX)", header, re.I): + self.stype = "X" + elif re.search("(neutron|PDFgetN)", header, re.I): + self.stype = "N" # qmax regexp = r"\bqmax *= *(%(f)s)\b" % rx res = re.search(regexp, header, re.I) @@ -227,12 +242,12 @@ def readStr(self, datastring): regexp = r"\b(?:temp|temperature|T)\ *=\ *(%(f)s)\b" % rx res = re.search(regexp, header) if res: - self.metadata['temperature'] = float(res.groups()[0]) + self.metadata["temperature"] = float(res.groups()[0]) # doping regexp = r"\b(?:x|doping)\ *=\ *(%(f)s)\b" % rx res = re.search(regexp, header) if res: - self.metadata['doping'] = float(res.groups()[0]) + self.metadata["doping"] = float(res.groups()[0]) # parsing gerneral metadata if metadata: @@ -241,12 +256,12 @@ def readStr(self, datastring): res = re.search(regexp, metadata, re.M) if res: self.metadata[res.groups()[0]] = float(res.groups()[1]) - metadata = metadata[res.end():] + metadata = metadata[res.end() :] else: break # read actual data - robs, Gobs, drobs, dGobs - inf_or_nan = re.compile('(?i)^[+-]?(NaN|Inf)\\b') + inf_or_nan = re.compile("(?i)^[+-]?(NaN|Inf)\\b") has_drobs = True has_dGobs = True # raise PDFDataFormatError if something goes wrong @@ -257,15 +272,13 @@ def readStr(self, datastring): self.robs.append(float(v[0])) self.Gobs.append(float(v[1])) # drobs is valid if all values are defined and positive - has_drobs = (has_drobs and - len(v) > 2 and not inf_or_nan.match(v[2])) + has_drobs = has_drobs and len(v) > 2 and not inf_or_nan.match(v[2]) if has_drobs: v2 = float(v[2]) has_drobs = v2 > 0.0 self.drobs.append(v2) # dGobs is valid if all values are defined and positive - has_dGobs = (has_dGobs and - len(v) > 3 and not inf_or_nan.match(v[3])) + has_dGobs = has_dGobs and len(v) > 3 and not inf_or_nan.match(v[3]) if has_dGobs: v3 = float(v[3]) has_dGobs = v3 > 0.0 @@ -274,15 +287,16 @@ def readStr(self, datastring): self.drobs = len(self.robs) * [0.0] if not has_dGobs: self.dGobs = len(self.robs) * [0.0] - except (ValueError, IndexError), err: + except (ValueError, IndexError) as err: raise PDFDataFormatError(err) self.rmin = self.robs[0] self.rmax = self.robs[-1] - if not has_drobs: self.drobs = len(self.robs) * [0.0] - if not has_dGobs: self.dGobs = len(self.robs) * [0.0] + if not has_drobs: + self.drobs = len(self.robs) * [0.0] + if not has_dGobs: + self.dGobs = len(self.robs) * [0.0] return self - def write(self, filename): """Write experimental PDF data to a file. @@ -291,7 +305,7 @@ def write(self, filename): No return value. """ bytes = self.writeStr() - f = open(filename, 'w') + f = open(filename, "w") f.write(bytes) f.close() return @@ -303,38 +317,43 @@ def writeStr(self): """ lines = [] # write metadata - lines.extend([ - 'History written: ' + time.ctime(), - 'produced by ' + getuser(), - '##### PDFgui' ]) + lines.extend( + [ + "History written: " + time.ctime(), + "produced by " + getuser(), + "##### PDFgui", + ] + ) # stype - if self.stype == 'X': - lines.append('stype=X x-ray scattering') - elif self.stype == 'N': - lines.append('stype=N neutron scattering') + if self.stype == "X": + lines.append("stype=X x-ray scattering") + elif self.stype == "N": + lines.append("stype=N neutron scattering") # qmax if self.qmax == 0: - qmax_line = 'qmax=0 correction not applied' + qmax_line = "qmax=0 correction not applied" else: - qmax_line = 'qmax=%.2f' % self.qmax + qmax_line = "qmax=%.2f" % self.qmax lines.append(qmax_line) # qdamp - lines.append('qdamp=%g' % self.qdamp) + lines.append("qdamp=%g" % self.qdamp) # qbroad - lines.append('qbroad=%g' % self.qbroad) + lines.append("qbroad=%g" % self.qbroad) # dscale - lines.append('dscale=%g' % self.dscale) + lines.append("dscale=%g" % self.dscale) # metadata if len(self.metadata) > 0: - lines.append('# metadata') + lines.append("# metadata") for k, v in self.metadata.iteritems(): - lines.append( "%s=%s" % (k,v) ) + lines.append("%s=%s" % (k, v)) # write data: - lines.append('##### start data') - lines.append('#L r(A) G(r) d_r d_Gr') + lines.append("##### start data") + lines.append("#L r(A) G(r) d_r d_Gr") for i in range(len(self.robs)): - lines.append('%g %g %g %g' % \ - (self.robs[i], self.Gobs[i], self.drobs[i], self.dGobs[i]) ) + lines.append( + "%g %g %g %g" + % (self.robs[i], self.Gobs[i], self.drobs[i], self.dGobs[i]) + ) # that should be it datastring = "\n".join(lines) + "\n" return datastring @@ -351,48 +370,62 @@ def copy(self, other=None): other.clear() # some attributes can be assigned, e.g., robs, Gobs, drobs, dGobs are # constant so they can be shared between copies. - assign_attributes = ( 'robs', 'Gobs', 'drobs', 'dGobs', 'stype', - 'qmax', 'qdamp', 'qbroad', 'dscale', - 'rmin', 'rmax', 'filename' ) + assign_attributes = ( + "robs", + "Gobs", + "drobs", + "dGobs", + "stype", + "qmax", + "qdamp", + "qbroad", + "dscale", + "rmin", + "rmax", + "filename", + ) # for others we will assign a copy - copy_attributes = ( 'metadata', ) + copy_attributes = ("metadata",) for a in assign_attributes: setattr(other, a, getattr(self, a)) import copy + for a in copy_attributes: setattr(other, a, copy.deepcopy(getattr(self, a))) return other + # End of class PDFDataSet class PDFDataFormatError(Exception): - """Exception class marking failure to proccess PDF data string. - """ + """Exception class marking failure to proccess PDF data string.""" + pass # simple test code -if __name__ == '__main__': +if __name__ == "__main__": import sys + filename = sys.argv[1] dataset = PDFDataSet("test") dataset.read(filename) - print "== metadata ==" + print("== metadata ==") for k, v in dataset.metadata.iteritems(): - print k, "=", v - print "== data members ==" + print(k, "=", v) + print("== data members ==") for k, v in dataset.__dict__.iteritems(): - if k in ('metadata', 'robs', 'Gobs', 'drobs', 'dGobs') or k[0] == "_": + if k in ("metadata", "robs", "Gobs", "drobs", "dGobs") or k[0] == "_": continue - print k, "=", v - print "== robs Gobs drobs dGobs ==" + print(k, "=", v) + print("== robs Gobs drobs dGobs ==") for i in range(len(dataset.robs)): - print dataset.robs[i], dataset.Gobs[i], dataset.drobs[i], dataset.dGobs[i] - print "== writeStr() ==" - print dataset.writeStr() - print "== datasetcopy.writeStr() ==" + print(dataset.robs[i], dataset.Gobs[i], dataset.drobs[i], dataset.dGobs[i]) + print("== writeStr() ==") + print(dataset.writeStr()) + print("== datasetcopy.writeStr() ==") datasetcopy = dataset.copy() - print datasetcopy.writeStr() + print(datasetcopy.writeStr()) # End of file From c28b97b28a5a97471805d74b3c2b68a390027f1a Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:58:09 +0800 Subject: [PATCH 04/65] lint check and fix python2 print and exception issues (#21) * lint check and fix python2 print and exception issues * [pre-commit.ci] auto fixes from pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- diffpy/srmise/pdfpeakextraction.py | 2 +- diffpy/srmise/peakextraction.py | 618 ++++++++++++++++++----------- diffpy/srmise/peakstability.py | 66 ++- diffpy/srmise/srmiselog.py | 81 ++-- 4 files changed, 487 insertions(+), 280 deletions(-) diff --git a/diffpy/srmise/pdfpeakextraction.py b/diffpy/srmise/pdfpeakextraction.py index 8f9ecd6..06bd2be 100644 --- a/diffpy/srmise/pdfpeakextraction.py +++ b/diffpy/srmise/pdfpeakextraction.py @@ -913,7 +913,7 @@ def find_qmax(r, y, showgraphs=False): plt.show() plt.ioff() - raw_input() + input() return (qmax, dq) diff --git a/diffpy/srmise/peakextraction.py b/diffpy/srmise/peakextraction.py index 73da8a2..8f1d404 100644 --- a/diffpy/srmise/peakextraction.py +++ b/diffpy/srmise/peakextraction.py @@ -58,12 +58,22 @@ def __init__(self, newvars=[]): Parameters newvars: Sequence of strings that represent additional extraction parameters.""" self.clear() - self.extractvars = dict.fromkeys(('effective_dy', 'rng', 'pf', 'initial_peaks', 'baseline', 'cres', 'error_method')) + self.extractvars = dict.fromkeys( + ( + "effective_dy", + "rng", + "pf", + "initial_peaks", + "baseline", + "cres", + "error_method", + ) + ) for k in newvars: if k not in self.extractvars: self.extractvars[k] = None else: - emsg = "Extraction variable %s conflicts with existing variable" %k + emsg = "Extraction variable %s conflicts with existing variable" % k raise ValueError(emsg) return @@ -104,7 +114,7 @@ def setdata(self, x, y, dx=None, dy=None): if len(self.x) != len(self.dx) or len(self.x) != len(self.dy): emsg = "Sequences dx and dy (if present) must have the same length as x" raise ValueError(emsg) - #self.defaultvars() + # self.defaultvars() return def setvars(self, quiet=False, **kwds): @@ -120,7 +130,8 @@ def setvars(self, quiet=False, **kwds): baseline: Baseline instance or BaselineFunction instance (use built-in estimation) error_method: ErrorEvaluator subclass instance used to compare models (default AIC) initial_peaks: Peaks instance. These peaks are present at the start of extraction. - rng: Sequence specifying the least and greatest x-values over which to extract peaks.""" + rng: Sequence specifying the least and greatest x-values over which to extract peaks. + """ for k, v in kwds.iteritems(): if k in self.extractvars: if quiet: @@ -159,24 +170,26 @@ def defaultvars(self, *args): Note that the default values of very important parameters like the uncertainty and clustering resolution are crude guesses at best. """ - if self.cres is None or 'cres' in args: - self.cres = 4*(self.x[-1] - self.x[0])/len(self.x) + if self.cres is None or "cres" in args: + self.cres = 4 * (self.x[-1] - self.x[0]) / len(self.x) - if self.effective_dy is None or 'effective_dy' in args: + if self.effective_dy is None or "effective_dy" in args: if np.all(self.dy > 0): # That is, all points positive uncertainty. self.effective_dy = self.dy else: # A terribly crude guess - self.effective_dy = .05*(np.max(self.y)-np.min(self.y))*np.ones(len(self.x)) + self.effective_dy = ( + 0.05 * (np.max(self.y) - np.min(self.y)) * np.ones(len(self.x)) + ) elif np.isscalar(self.effective_dy) and self.effective_dy > 0: - self.effective_dy = self.effective_dy*np.ones(len(self.x)) + self.effective_dy = self.effective_dy * np.ones(len(self.x)) if self.pf is None or "pf" in args: from diffpy.srmise.peaks import GaussianOverR # TODO: Make a more useful default. - self.pf = [GaussianOverR(self.x[-1]-self.x[0])] + self.pf = [GaussianOverR(self.x[-1] - self.x[0])] if self.rng is None or "rng" in args: self.rng = [self.x[0], self.x[-1]] @@ -192,37 +205,40 @@ def defaultvars(self, *args): s = self.getrangeslice() epars = self.baseline.estimate_parameters(self.x[s], self.y[s]) self.baseline = self.baseline.actualize(epars, "internal") - logger.info("Estimating baseline: %s" %self.baseline) + logger.info("Estimating baseline: %s" % self.baseline) except (NotImplementedError, SrMiseEstimationError): - logger.error("Could not estimate baseline from provided BaselineFunction, trying default values.") + logger.error( + "Could not estimate baseline from provided BaselineFunction, trying default values." + ) self.baseline = None if self.baseline is None or "baseline" in args: from diffpy.srmise.baselines import Polynomial - bl = Polynomial(degree = -1) + + bl = Polynomial(degree=-1) self.baseline = bl.actualize(np.array([]), "internal") if self.error_method is None or "error_method" in args: from diffpy.srmise.modelevaluators import AIC + self.error_method = AIC if self.initial_peaks is None or "initial_peaks" in args: self.initial_peaks = Peaks() - def __str__(self): """Return string summary of PeakExtraction.""" out = [] for k in self.extractvars: - out.append("%s: %s" %(k, getattr(self, k))) + out.append("%s: %s" % (k, getattr(self, k))) if self.extracted is not None: - out.append("Extraction type: %s" %self.extraction_type) + out.append("Extraction type: %s" % self.extraction_type) out.append("--- Extracted ---") out.append(str(self.extracted)) else: out.append("No extracted peaks exist.") - return '\n'.join(out)+'\n' + return "\n".join(out) + "\n" def plot(self, **kwds): """Convenience function to plot data and extracted peaks with matplotlib. @@ -242,10 +258,18 @@ def plot(self, **kwds): x = self.x[rangeslice] y = self.y[rangeslice] dy = self.dy[rangeslice] - mcluster = ModelCluster(self.initial_peaks, self.baseline, x, y, dy, None, self.error_method, self.pf) + mcluster = ModelCluster( + self.initial_peaks, + self.baseline, + x, + y, + dy, + None, + self.error_method, + self.pf, + ) plt.plot(*mcluster.plottable(kwds)) - def read(self, filename): """load PeakExtraction object from file @@ -254,12 +278,14 @@ def read(self, filename): returns self """ try: - self.readstr(open(filename,'rb').read()) - except SrMiseDataFormatError, err: + self.readstr(open(filename, "rb").read()) + except SrMiseDataFormatError as err: logger.exception("") basename = os.path.basename(filename) - emsg = ("Could not open '%s' due to unsupported file format " + - "or corrupted data. [%s]") % (basename, err) + emsg = ( + "Could not open '%s' due to unsupported file format " + + "or corrupted data. [%s]" + ) % (basename, err) raise SrMiseFileError(emsg) return self @@ -288,120 +314,120 @@ def readstr(self, datastring): safebf = [] # find where the results section starts - res = re.search(r'^#+ Results\s*(?:#.*\s+)*', datastring, re.M) + res = re.search(r"^#+ Results\s*(?:#.*\s+)*", datastring, re.M) if res: - results = datastring[res.end():].strip() - header = datastring[:res.start()] + results = datastring[res.end() :].strip() + header = datastring[: res.start()] # find data section, and what information it contains - res = re.search(r'^#+ start data\s*(?:#.*\s+)*', header, re.M) + res = re.search(r"^#+ start data\s*(?:#.*\s+)*", header, re.M) if res: - start_data = header[res.end():].strip() - start_data_info = header[res.start():res.end()] - header = header[:res.start()] - res = re.search(r'^(#+L.*)$', start_data_info, re.M) + start_data = header[res.end() :].strip() + start_data_info = header[res.start() : res.end()] + header = header[: res.start()] + res = re.search(r"^(#+L.*)$", start_data_info, re.M) if res: - start_data_info = start_data_info[res.start():res.end()].strip() + start_data_info = start_data_info[res.start() : res.end()].strip() hasx = False hasy = False hasdx = False hasdy = False hasedy = False - res = re.search(r'\bx\b', start_data_info) + res = re.search(r"\bx\b", start_data_info) if res: hasx = True - res = re.search(r'\by\b', start_data_info) + res = re.search(r"\by\b", start_data_info) if res: hasy = True - res = re.search(r'\bdx\b', start_data_info) + res = re.search(r"\bdx\b", start_data_info) if res: hasdx = True - res = re.search(r'\bdy\b', start_data_info) + res = re.search(r"\bdy\b", start_data_info) if res: hasdy = True - res = re.search(r'\edy\b', start_data_info) + res = re.search(r"\edy\b", start_data_info) if res: hasedy = True - res = re.search(r'^#+ Metadata\s*(?:#.*\s+)*', header, re.M) + res = re.search(r"^#+ Metadata\s*(?:#.*\s+)*", header, re.M) if res: - metadata = header[res.end():].strip() - header = header[:res.start()] + metadata = header[res.end() :].strip() + header = header[: res.start()] - res = re.search(r'^#+ SrMiseMetadata\s*(?:#.*\s+)*', header, re.M) + res = re.search(r"^#+ SrMiseMetadata\s*(?:#.*\s+)*", header, re.M) if res: - srmisemetadata = header[res.end():].strip() - header = header[:res.start()] + srmisemetadata = header[res.end() :].strip() + header = header[: res.start()] - res = re.search(r'^#+ InitialPeaks.*$', header, re.M) + res = re.search(r"^#+ InitialPeaks.*$", header, re.M) if res: - initial_peaks = header[res.end():].strip() - header = header[:res.start()] + initial_peaks = header[res.end() :].strip() + header = header[: res.start()] - res = re.search(r'^#+ BaselineObject\s*(?:#.*\s+)*', header, re.M) + res = re.search(r"^#+ BaselineObject\s*(?:#.*\s+)*", header, re.M) if res: - baselineobject = header[res.end():].strip() - header = header[:res.start()] + baselineobject = header[res.end() :].strip() + header = header[: res.start()] - res = re.search(r'^#+ PeakFunctions.*$', header, re.M) + res = re.search(r"^#+ PeakFunctions.*$", header, re.M) if res: - peakfunctions = header[res.end():].strip() - header = header[:res.start()] + peakfunctions = header[res.end() :].strip() + header = header[: res.start()] - res = re.search(r'^#+ BaselineFunctions.*$', header, re.M) + res = re.search(r"^#+ BaselineFunctions.*$", header, re.M) if res: - baselinefunctions = header[res.end():].strip() - header = header[:res.start()] + baselinefunctions = header[res.end() :].strip() + header = header[: res.start()] ### Instantiating baseline functions - res = re.split(r'(?m)^#+ BaselineFunction \d+\s*(?:#.*\s+)*', baselinefunctions) + res = re.split(r"(?m)^#+ BaselineFunction \d+\s*(?:#.*\s+)*", baselinefunctions) for s in res[1:]: safebf.append(BaseFunction.factory(s, safebf)) ### Instantiating peak functions - res = re.split(r'(?m)^#+ PeakFunction \d+\s*(?:#.*\s+)*', peakfunctions) + res = re.split(r"(?m)^#+ PeakFunction \d+\s*(?:#.*\s+)*", peakfunctions) for s in res[1:]: safepf.append(BaseFunction.factory(s, safepf)) ### Instantiating Baseline object - if re.match(r'^None$', baselineobject): + if re.match(r"^None$", baselineobject): self.baseline = None - elif re.match(r'^\d+$', baselineobject): + elif re.match(r"^\d+$", baselineobject): self.baseline = safebf[int(baselineobject)] else: self.baseline = Baseline.factory(baselineobject, safebf) ### Instantiating initial peaks - if re.match(r'^None$', initial_peaks): + if re.match(r"^None$", initial_peaks): self.initial_peaks = None else: self.initial_peaks = Peaks() - res = re.split(r'(?m)^#+ InitialPeak\s*(?:#.*\s+)*', initial_peaks) + res = re.split(r"(?m)^#+ InitialPeak\s*(?:#.*\s+)*", initial_peaks) for s in res[1:]: self.initial_peaks.append(Peak.factory(s, safepf)) ### Instantiating srmise metatdata # pf - res = re.search(r'^pf=(.*)$', srmisemetadata, re.M) + res = re.search(r"^pf=(.*)$", srmisemetadata, re.M) self.pf = eval(res.groups()[0].strip()) if self.pf is not None: self.pf = [safepf[i] for i in self.pf] # cres - rx = { 'f' : r'[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?' } + rx = {"f": r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?"} regexp = r"\bcres *= *(%(f)s)\b" % rx res = re.search(regexp, srmisemetadata, re.I) self.cres = float(res.groups()[0]) # error_method - res = re.search(r'^ModelEvaluator=(.*)$', srmisemetadata, re.M) + res = re.search(r"^ModelEvaluator=(.*)$", srmisemetadata, re.M) __import__("diffpy.srmise.modelevaluators") module = sys.modules["diffpy.srmise.modelevaluators"] self.error_method = getattr(module, res.groups()[0].strip()) # range - res = re.search(r'^Range=(.*)$', srmisemetadata, re.M) + res = re.search(r"^Range=(.*)$", srmisemetadata, re.M) self.rng = eval(res.groups()[0].strip()) ### Instantiating other metadata @@ -440,10 +466,13 @@ def readstr(self, datastring): for line in start_data.split("\n"): l = line.split() if len(arrays) != len(l): - emsg = ("Number of value fields does not match that given by '%s'" %start_data_info) + emsg = ( + "Number of value fields does not match that given by '%s'" + % start_data_info + ) for a, v in zip(arrays, line.split()): a.append(float(v)) - except (ValueError, IndexError), err: + except (ValueError, IndexError) as err: raise SrMiseDataFormatError(str(err)) if hasx: self.x = np.array(self.x) @@ -456,15 +485,14 @@ def readstr(self, datastring): if hasedy: self.effective_dy = np.array(self.effective_dy) - ### Instantiating results - res = re.search(r'^#+ ModelCluster\s*(?:#.*\s+)*', results, re.M) + res = re.search(r"^#+ ModelCluster\s*(?:#.*\s+)*", results, re.M) if res: - mc = results[res.end():].strip() - results = results[:res.start()] + mc = results[res.end() :].strip() + results = results[: res.start()] # extraction type - res = re.search(r'^extraction_type=(.*)$', results, re.M) + res = re.search(r"^extraction_type=(.*)$", results, re.M) if res: self.extraction_type = eval(res.groups()[0].strip()) else: @@ -472,10 +500,12 @@ def readstr(self, datastring): raise SrMiseDataFormatError(emsg) # extracted - if re.match(r'^None$', mc): + if re.match(r"^None$", mc): self.extracted = None else: - self.extracted = ModelCluster.factory(mc, pfbaselist=safepf, blfbaselist=safebf) + self.extracted = ModelCluster.factory( + mc, pfbaselist=safepf, blfbaselist=safebf + ) def write(self, filename): """Write string representation of PeakExtraction instance to file. @@ -483,12 +513,11 @@ def write(self, filename): Parameters filename: the name of the file to write""" bytes = self.writestr() - f = open(filename, 'w') + f = open(filename, "w") f.write(bytes) f.close() return - def writestr(self): """Return string representation of PeakExtraction object.""" import time @@ -500,11 +529,14 @@ def writestr(self): lines = [] # Header - lines.extend([ - 'History written: ' + time.ctime(), - 'produced by ' + getuser(), - 'diffpy.srmise version %s' %__version__, - '##### PDF Peak Extraction' ]) + lines.extend( + [ + "History written: " + time.ctime(), + "produced by " + getuser(), + "diffpy.srmise version %s" % __version__, + "##### PDF Peak Extraction", + ] + ) # Generate list of PeakFunctions and BaselineFunctions # so I can refer to them by index when necessary. @@ -517,7 +549,7 @@ def writestr(self): if self.baseline is not None: if isinstance(self.baseline, BaseFunction): allbf.append(self.baseline) - else: # should be a ModelPart + else: # should be a ModelPart allbf.append(self.baseline.owner()) if self.extracted is not None: allpf.extend(self.extracted.peak_funcs) @@ -532,13 +564,13 @@ def writestr(self): # Indexed baseline functions lines.append("## BaselineFunctions") for i, bf in enumerate(safebf): - lines.append('# BaselineFunction %s' %i) + lines.append("# BaselineFunction %s" % i) lines.append(bf.writestr(safebf)) # Indexed peak functions lines.append("## PeakFunctions") for i, pf in enumerate(safepf): - lines.append('# PeakFunction %s' %i) + lines.append("# PeakFunction %s" % i) lines.append(pf.writestr(safepf)) # Baseline @@ -546,7 +578,7 @@ def writestr(self): if self.baseline is None: lines.append("None") elif self.baseline in safebf: - lines.append('%s' %repr(safebf.index(self.baseline))) + lines.append("%s" % repr(safebf.index(self.baseline))) else: lines.append(self.baseline.writestr(safebf)) @@ -556,34 +588,34 @@ def writestr(self): lines.append("None") else: for ip in self.initial_peaks: - lines.append('# InitialPeak') + lines.append("# InitialPeak") lines.append(ip.writestr(safepf)) - lines.append('# SrMiseMetadata') + lines.append("# SrMiseMetadata") # Extractable peak types if self.pf is None: lines.append("pf=None") else: - lines.append("pf=%s" %repr([safepf.index(p) for p in self.pf])) + lines.append("pf=%s" % repr([safepf.index(p) for p in self.pf])) # Clustering resolution - lines.append('cres=%g' %self.cres) + lines.append("cres=%g" % self.cres) # Model evaluator if self.error_method is None: - lines.append('ModelEvaluator=None') + lines.append("ModelEvaluator=None") else: - lines.append('ModelEvaluator=%s' %self.error_method.__name__) + lines.append("ModelEvaluator=%s" % self.error_method.__name__) # Extraction range - lines.append("Range=%s" %repr(self.rng)) + lines.append("Range=%s" % repr(self.rng)) # Everything not defined by PeakExtraction - lines.append('# Metadata') + lines.append("# Metadata") lines.append(self.writemetadata()) # Raw data used in extraction. - lines.append('##### start data') - line = ['#L'] + lines.append("##### start data") + line = ["#L"] numlines = 0 if self.x is not None: line.append("x") @@ -604,29 +636,28 @@ def writestr(self): for i in range(numlines): line = [] if self.x is not None: - line.append("%g" %self.x[i]) + line.append("%g" % self.x[i]) if self.y is not None: - line.append("%g" %self.y[i]) + line.append("%g" % self.y[i]) if self.dx is not None: - line.append("%g" %self.dx[i]) + line.append("%g" % self.dx[i]) if self.dy is not None: - line.append("%g" %self.dy[i]) + line.append("%g" % self.dy[i]) if self.effective_dy is not None: - line.append("%g" %self.effective_dy[i]) + line.append("%g" % self.effective_dy[i]) lines.append(" ".join(line)) - ### Calculated members - lines.append('##### Results') - lines.append('extraction_type=%s' %repr(self.extraction_type)) + lines.append("##### Results") + lines.append("extraction_type=%s" % repr(self.extraction_type)) lines.append("### ModelCluster") if self.extracted is None: - lines.append('None') + lines.append("None") else: lines.append(self.extracted.writestr(pfbaselist=safepf, blfbaselist=safebf)) - datastring = "\n".join(lines)+"\n" + datastring = "\n".join(lines) + "\n" return datastring def writemetadata(self): @@ -647,7 +678,7 @@ def getrangeslice(self): while self.x[low_idx] < max(self.x[0], self.rng[0]): low_idx += 1 hi_idx = len(self.x) - while self.x[hi_idx-1] > min(self.x[-1], self.rng[1]): + while self.x[hi_idx - 1] > min(self.x[-1], self.rng[1]): hi_idx -= 1 return slice(low_idx, hi_idx) @@ -675,18 +706,22 @@ def estimate_peak(self, x, add=True): # Determine clusters using initial_peaks and pre-defined or estimated baseline rangeslice = self.getrangeslice() x1 = self.x[rangeslice] - y1 = self.y[rangeslice] - self.baseline.value(x1) - self.initial_peaks.value(x1) + y1 = ( + self.y[rangeslice] + - self.baseline.value(x1) + - self.initial_peaks.value(x1) + ) dy = self.effective_dy[rangeslice] if x < x1[0] or x > x1[-1]: - emsg = "Argument x=%s outside allowed range (%s, %s)." %(x, x1[0], x1[-1]) + emsg = "Argument x=%s outside allowed range (%s, %s)." % (x, x1[0], x1[-1]) raise ValueError(emsg) # Object performing clustering on data. Note that DataClusters # provides an iterator that clusters the next point and returns # itself. Thus, dclusters and step (below) refer to the same object. - dclusters = DataClusters(x1, y1, self.cres) # Cluster with baseline removed + dclusters = DataClusters(x1, y1, self.cres) # Cluster with baseline removed dclusters.makeclusters() cidx = dclusters.find_nearest_cluster2(x)[0] cslice = dclusters.cut(cidx) @@ -695,15 +730,17 @@ def estimate_peak(self, x, add=True): y1 = y1[cslice] dy = dy[cslice] - mcluster = ModelCluster(None, None, x1, y1, dy, None, self.error_method, self.pf) + mcluster = ModelCluster( + None, None, x1, y1, dy, None, self.error_method, self.pf + ) mcluster.fit() if len(mcluster.model) > 0: if add: - logger.info("Adding peak: %s" %mcluster.model[0]) + logger.info("Adding peak: %s" % mcluster.model[0]) self.add_peaks(mcluster.model) else: - logger.info("Found peak: %s" %mcluster.model[0]) + logger.info("Found peak: %s" % mcluster.model[0]) return mcluster.model[0] else: logger.info("No peaks found.") @@ -725,12 +762,12 @@ def add_peaks(self, peaks): def extract_single(self, recursion_depth=1): """Find ModelCluster with peaks extracted from data. Return ModelCovariance instance at top level. - Every extracted peak is one of the peak functions supplied. All - comparisons of different peak models are performed with the class - specified by error_method. + Every extracted peak is one of the peak functions supplied. All + comparisons of different peak models are performed with the class + specified by error_method. - Parameters - recursion_depth: (1) Tracks recursion with extract_single.""" + Parameters + recursion_depth: (1) Tracks recursion with extract_single.""" self.clearcalc() tracer = srmiselog.tracer tracer.pushc() @@ -755,13 +792,17 @@ def extract_single(self, recursion_depth=1): # provides an iterator that clusters the next point and returns # itself. Thus, dclusters and step (below) refer to the same object. - dclusters = DataClusters(x, y, self.cres) # Cluster with baseline removed + dclusters = DataClusters(x, y, self.cres) # Cluster with baseline removed # The data for model clusters includes the baseline y = self.y[rangeslice] - ip.value(x) # List of ModelClusters containing extracted peaks. - mclusters = [ModelCluster(None, bl, x, y, dy, dclusters.cut(0), self.error_method, self.pf)] + mclusters = [ + ModelCluster( + None, bl, x, y, dy, dclusters.cut(0), self.error_method, self.pf + ) + ] # The minimum number of points required to make a valid fit, as # determined by the peak functions and error method used. This is a @@ -777,28 +818,35 @@ def extract_single(self, recursion_depth=1): stepcounter += 1 msg = "\n\n------ Recursion: %s Step: %s Cluster: %s %s ------" - logger.debug(msg, - recursion_depth, - stepcounter, - step.lastcluster_idx, - step.clusters[step.lastcluster_idx] - ) + logger.debug( + msg, + recursion_depth, + stepcounter, + step.lastcluster_idx, + step.clusters[step.lastcluster_idx], + ) # Update mclusters if len(step.clusters) > len(mclusters): # Add a new cluster - mclusters.insert(step.lastcluster_idx, - ModelCluster(None, - bl, - x, - y, - dy, - step.cut(step.lastcluster_idx), - self.error_method, - self.pf)) + mclusters.insert( + step.lastcluster_idx, + ModelCluster( + None, + bl, + x, + y, + dy, + step.cut(step.lastcluster_idx), + self.error_method, + self.pf, + ), + ) else: # Update an existing cluster - mclusters[step.lastcluster_idx].change_slice(step.cut(step.lastcluster_idx)) + mclusters[step.lastcluster_idx].change_slice( + step.cut(step.lastcluster_idx) + ) # Find newly adjacent clusters adjacent = step.find_adjacent_clusters().ravel() @@ -813,7 +861,7 @@ def extract_single(self, recursion_depth=1): assert len(adjacent) <= 3 ### Update cluster fits ### - #1. Refit clusters adjacent to at least one other cluster. + # 1. Refit clusters adjacent to at least one other cluster. for a in adjacent: mclusters[a].fit(justify=True) @@ -842,7 +890,7 @@ def extract_single(self, recursion_depth=1): # enlarged cluster ("new_cluster") or an intermediate cluster # ("adj_cluster"). - if step.lastpoint_idx == 0 or step.lastpoint_idx == len(step.x)-1: + if step.lastpoint_idx == 0 or step.lastpoint_idx == len(step.x) - 1: logger.debug("Boundary full: %s", step.lastpoint_idx) full_cluster = ModelCluster(mclusters[step.lastcluster_idx]) full_cluster.fit(True) @@ -853,7 +901,7 @@ def extract_single(self, recursion_depth=1): # Determine neighborhood appropriate for fitting (no larger than combined clusters) if len(full_cluster.model) > 0: - peak_pos = np.array([p['position'] for p in full_cluster.model]) + peak_pos = np.array([p["position"] for p in full_cluster.model]) pivot = peak_pos.searchsorted(border_x) else: peak_pos = np.array([]) @@ -871,25 +919,27 @@ def extract_single(self, recursion_depth=1): elif pivot == 1: # One peak left left_data = full_cluster.slice.indices(len(x))[0] - near_peaks = np.append(near_peaks, pivot-1) + near_peaks = np.append(near_peaks, pivot - 1) else: # left_data -> one more peak to the left - left_data = max(0, x.searchsorted(peak_pos[pivot-2])-1) - near_peaks = np.append(near_peaks, pivot-1) + left_data = max(0, x.searchsorted(peak_pos[pivot - 2]) - 1) + near_peaks = np.append(near_peaks, pivot - 1) if pivot == len(peak_pos): # No peaks right of border_x! - right_data = full_cluster.slice.indices(len(x))[1]-1 - elif pivot == len(peak_pos)-1: + right_data = full_cluster.slice.indices(len(x))[1] - 1 + elif pivot == len(peak_pos) - 1: # One peak right - right_data = full_cluster.slice.indices(len(x))[1]-1 + right_data = full_cluster.slice.indices(len(x))[1] - 1 near_peaks = np.append(near_peaks, pivot) else: # right_data -> one more peak to the right - right_data = min(len(x), x.searchsorted(peak_pos[pivot+1])+1) + right_data = min(len(x), x.searchsorted(peak_pos[pivot + 1]) + 1) near_peaks = np.append(near_peaks, pivot) - other_peaks = np.concatenate([np.arange(0, pivot-1), np.arange(pivot+1, len(peak_pos))]) + other_peaks = np.concatenate( + [np.arange(0, pivot - 1), np.arange(pivot + 1, len(peak_pos))] + ) # Go from indices to lists of peaks. near_peaks = Peaks([full_cluster.model[i] for i in near_peaks]) @@ -900,31 +950,63 @@ def extract_single(self, recursion_depth=1): # The adjusted error is passed unchanged. This may introduce # a few more peaks than is justified, but they can be pruned # with the correct statistics at the top level of recursion. - adj_slice = slice(left_data, right_data+1) + adj_slice = slice(left_data, right_data + 1) adj_x = x[adj_slice] - adj_y = y[adj_slice]-other_peaks.value(adj_x) + adj_y = y[adj_slice] - other_peaks.value(adj_x) adj_error = dy[adj_slice] - adj_cluster = ModelCluster(near_peaks, bl, adj_x, adj_y, adj_error, slice(len(adj_x)), self.error_method, self.pf) + adj_cluster = ModelCluster( + near_peaks, + bl, + adj_x, + adj_y, + adj_error, + slice(len(adj_x)), + self.error_method, + self.pf, + ) # Recursively cluster/fit the residual rec_r = adj_x - rec_y = adj_y-near_peaks.value(rec_r) + rec_y = adj_y - near_peaks.value(rec_r) rec_error = adj_error # Quick check to see if there is anything to find min_npars = min([p.npars for p in self.pf]) - checkrec = ModelCluster(None, None, rec_r, rec_y, rec_error, None, self.error_method, self.pf) - recurse = len(near_peaks) > 0 and checkrec.quality().growth_justified(checkrec, min_npars) + checkrec = ModelCluster( + None, + None, + rec_r, + rec_y, + rec_error, + None, + self.error_method, + self.pf, + ) + recurse = len(near_peaks) > 0 and checkrec.quality().growth_justified( + checkrec, min_npars + ) if recurse and recursion_depth < 3: - logger.info("\n*********STARTING RECURSION level %s (full boundary)************" %(recursion_depth+1)) + logger.info( + "\n*********STARTING RECURSION level %s (full boundary)************" + % (recursion_depth + 1) + ) rec_search = PeakExtraction() rec_search.setdata(rec_r, rec_y, None, rec_error) - rec_search.setvars(quiet=True, baseline=bl, cres=self.cres, pf=self.pf, error_method=self.error_method) - rec_search.extract_single(recursion_depth+1) + rec_search.setvars( + quiet=True, + baseline=bl, + cres=self.cres, + pf=self.pf, + error_method=self.error_method, + ) + rec_search.extract_single(recursion_depth + 1) rec = rec_search.extracted - logger.info("*********ENDING RECURSION level %s (full boundary) ************\n" %(recursion_depth+1)) + logger.info( + "*********ENDING RECURSION level %s (full boundary) ************\n" + % (recursion_depth + 1) + ) # Incorporate best peaks from recursive search. adj_cluster.augment(rec) @@ -934,15 +1016,17 @@ def extract_single(self, recursion_depth=1): full_cluster.replacepeaks(adj_cluster.model) full_cluster.fit(True) - msg = ["---Result of full boundary---", - "Original cluster:", - "%s", - "Final cluster:", - "%s", - "---End of combining clusters---"] - logger.debug("\n".join(msg), - mclusters[step.lastcluster_idx], - full_cluster) + msg = [ + "---Result of full boundary---", + "Original cluster:", + "%s", + "Final cluster:", + "%s", + "---End of combining clusters---", + ] + logger.debug( + "\n".join(msg), mclusters[step.lastcluster_idx], full_cluster + ) mclusters[step.lastcluster_idx] = full_cluster ### End update cluster fits ### @@ -954,21 +1038,22 @@ def extract_single(self, recursion_depth=1): msg = ["Current model"] msg.extend(["%s" for m in mclusters]) - logger.debug("\n".join(msg), - *[m.model for m in mclusters]) + logger.debug("\n".join(msg), *[m.model for m in mclusters]) - cleft = step.clusters[idx-1] + cleft = step.clusters[idx - 1] cright = step.clusters[idx] - new_cluster = ModelCluster.join_adjacent(mclusters[idx-1], mclusters[idx]) + new_cluster = ModelCluster.join_adjacent( + mclusters[idx - 1], mclusters[idx] + ) # Estimate coordinate where clusters combine. - border_x = .5*(x[cleft[1]]+x[cright[0]]) - border_y = .5*(y[cleft[1]]+y[cright[0]]) + border_x = 0.5 * (x[cleft[1]] + x[cright[0]]) + border_y = 0.5 * (y[cleft[1]] + y[cright[0]]) # Determine neighborhood appropriate for fitting (no larger than combined clusters) if len(new_cluster.model) > 0: - peak_pos = np.array([p['position'] for p in new_cluster.model]) + peak_pos = np.array([p["position"] for p in new_cluster.model]) pivot = peak_pos.searchsorted(border_x) else: peak_pos = np.array([]) @@ -982,29 +1067,31 @@ def extract_single(self, recursion_depth=1): # interpeak range goes from peak to peak of next nearest peaks, although their contributions to the data are still removed. if pivot == 0: # No peaks left of border_x! - left_data=new_cluster.slice.indices(len(x))[0] + left_data = new_cluster.slice.indices(len(x))[0] elif pivot == 1: # One peak left left_data = new_cluster.slice.indices(len(x))[0] - near_peaks = np.append(near_peaks, pivot-1) + near_peaks = np.append(near_peaks, pivot - 1) else: # left_data -> one more peak to the left - left_data = max(0,x.searchsorted(peak_pos[pivot-2])-1) - near_peaks = np.append(near_peaks, pivot-1) + left_data = max(0, x.searchsorted(peak_pos[pivot - 2]) - 1) + near_peaks = np.append(near_peaks, pivot - 1) if pivot == len(peak_pos): # No peaks right of border_x! - right_data = new_cluster.slice.indices(len(x))[1]-1 - elif pivot == len(peak_pos)-1: + right_data = new_cluster.slice.indices(len(x))[1] - 1 + elif pivot == len(peak_pos) - 1: # One peak right - right_data = new_cluster.slice.indices(len(x))[1]-1 + right_data = new_cluster.slice.indices(len(x))[1] - 1 near_peaks = np.append(near_peaks, pivot) else: # right_data -> one more peak to the right - right_data = min(len(x), x.searchsorted(peak_pos[pivot+1])+1) + right_data = min(len(x), x.searchsorted(peak_pos[pivot + 1]) + 1) near_peaks = np.append(near_peaks, pivot) - other_peaks = np.concatenate([np.arange(0, pivot-1), np.arange(pivot+1, len(peak_pos))]) + other_peaks = np.concatenate( + [np.arange(0, pivot - 1), np.arange(pivot + 1, len(peak_pos))] + ) # Go from indices to lists of peaks. near_peaks = Peaks([new_cluster.model[i] for i in near_peaks]) @@ -1015,17 +1102,35 @@ def extract_single(self, recursion_depth=1): # The adjusted error is passed unchanged. This may introduce # a few more peaks than is justified, but they can be pruned # with the correct statistics at the top level of recursion. - adj_slice = slice(left_data, right_data+1) + adj_slice = slice(left_data, right_data + 1) adj_x = x[adj_slice] - adj_y = y[adj_slice]-other_peaks.value(adj_x) + adj_y = y[adj_slice] - other_peaks.value(adj_x) adj_error = dy[adj_slice] #### Perform recursion on a version that is scaled at the # border, as well as on that is simply fit beforehand. In # many cases these lead to nearly identical results, but # occasionally one works much better than the other. - adj_cluster1 = ModelCluster(near_peaks.copy(), bl, adj_x, adj_y, adj_error, slice(len(adj_x)), self.error_method, self.pf) - adj_cluster2 = ModelCluster(near_peaks.copy(), bl, adj_x, adj_y, adj_error, slice(len(adj_x)), self.error_method, self.pf) + adj_cluster1 = ModelCluster( + near_peaks.copy(), + bl, + adj_x, + adj_y, + adj_error, + slice(len(adj_x)), + self.error_method, + self.pf, + ) + adj_cluster2 = ModelCluster( + near_peaks.copy(), + bl, + adj_x, + adj_y, + adj_error, + slice(len(adj_x)), + self.error_method, + self.pf, + ) # Adjust cluster at border if there is at least one peak on # either side. @@ -1034,23 +1139,44 @@ def extract_single(self, recursion_depth=1): # Recursively cluster/fit the residual rec_r1 = adj_x - #rec_y1 = adj_y - near_peaks.value(rec_r1) + # rec_y1 = adj_y - near_peaks.value(rec_r1) rec_y1 = adj_y - adj_cluster1.model.value(rec_r1) rec_error1 = adj_error # Quick check to see if there is anything to find min_npars = min([p.npars for p in self.pf]) - checkrec = ModelCluster(None, None, rec_r1, rec_y1, rec_error1, None, self.error_method, self.pf) + checkrec = ModelCluster( + None, + None, + rec_r1, + rec_y1, + rec_error1, + None, + self.error_method, + self.pf, + ) recurse1 = checkrec.quality().growth_justified(checkrec, min_npars) if recurse1 and recursion_depth < 3: - logger.info("\n*********STARTING RECURSION level %s (reduce at border)************" %(recursion_depth+1)) + logger.info( + "\n*********STARTING RECURSION level %s (reduce at border)************" + % (recursion_depth + 1) + ) rec_search1 = PeakExtraction() rec_search1.setdata(rec_r1, rec_y1, None, rec_error1) - rec_search1.setvars(quiet=True, baseline=bl, cres=self.cres, pf=self.pf, error_method=self.error_method) - rec_search1.extract_single(recursion_depth+1) + rec_search1.setvars( + quiet=True, + baseline=bl, + cres=self.cres, + pf=self.pf, + error_method=self.error_method, + ) + rec_search1.extract_single(recursion_depth + 1) rec1 = rec_search1.extracted - logger.info("*********ENDING RECURSION level %s (reduce at border) ************\n" %(recursion_depth+1)) + logger.info( + "*********ENDING RECURSION level %s (reduce at border) ************\n" + % (recursion_depth + 1) + ) # Incorporate best peaks from recursive search. adj_cluster1.augment(rec1) @@ -1060,23 +1186,46 @@ def extract_single(self, recursion_depth=1): # Recursively cluster/fit the residual rec_r2 = adj_x - #rec_y2 = adj_y - near_peaks.value(rec_r2) + # rec_y2 = adj_y - near_peaks.value(rec_r2) rec_y2 = adj_y - adj_cluster2.model.value(rec_r2) rec_error2 = adj_error # Quick check to see if there is anything to find min_npars = min([p.npars for p in self.pf]) - checkrec = ModelCluster(None, None, rec_r2, rec_y2, rec_error2, None, self.error_method, self.pf) - recurse2 = len(near_peaks) > 0 and checkrec.quality().growth_justified(checkrec, min_npars) + checkrec = ModelCluster( + None, + None, + rec_r2, + rec_y2, + rec_error2, + None, + self.error_method, + self.pf, + ) + recurse2 = len(near_peaks) > 0 and checkrec.quality().growth_justified( + checkrec, min_npars + ) if recurse2 and recursion_depth < 3: - logger.info("\n*********STARTING RECURSION level %s (prefit)************" %(recursion_depth+1)) + logger.info( + "\n*********STARTING RECURSION level %s (prefit)************" + % (recursion_depth + 1) + ) rec_search2 = PeakExtraction() rec_search2.setdata(rec_r2, rec_y2, None, rec_error2) - rec_search2.setvars(quiet=True, baseline=bl, cres=self.cres, pf=self.pf, error_method=self.error_method) - rec_search2.extract_single(recursion_depth+1) + rec_search2.setvars( + quiet=True, + baseline=bl, + cres=self.cres, + pf=self.pf, + error_method=self.error_method, + ) + rec_search2.extract_single(recursion_depth + 1) rec2 = rec_search2.extracted - logger.info("*********ENDING RECURSION level %s (prefit) ************\n" %(recursion_depth+1)) + logger.info( + "*********ENDING RECURSION level %s (prefit) ************\n" + % (recursion_depth + 1) + ) # Incorporate best peaks from recursive search. adj_cluster2.augment(rec2) @@ -1095,22 +1244,22 @@ def extract_single(self, recursion_depth=1): new_cluster.fit(True) - - msg = ["---Result of combining clusters---", - "First cluster:", - "%s", - "Second cluster:", - "%s", - "Resulting cluster:", - "%s", - "---End of combining clusters---"] - - logger.debug("\n".join(msg), - mclusters[idx-1], - mclusters[idx], - new_cluster) - - mclusters[idx-1] = new_cluster + msg = [ + "---Result of combining clusters---", + "First cluster:", + "%s", + "Second cluster:", + "%s", + "Resulting cluster:", + "%s", + "---End of combining clusters---", + ] + + logger.debug( + "\n".join(msg), mclusters[idx - 1], mclusters[idx], new_cluster + ) + + mclusters[idx - 1] = new_cluster del mclusters[idx] ### End combine adjacent clusters loop ### @@ -1121,7 +1270,6 @@ def extract_single(self, recursion_depth=1): tracer.emit(*mclusters) - ### End main extraction loop ### ################################ @@ -1166,13 +1314,20 @@ def fit_single(self): dy = self.effective_dy[rngslice] # Set up ModelCluster - ext = ModelCluster(self.initial_peaks, self.baseline, x, y, dy, None, - self.error_method, self.pf) + ext = ModelCluster( + self.initial_peaks, + self.baseline, + x, + y, + dy, + None, + self.error_method, + self.pf, + ) # Fit model with baseline and calculate covariance matrix cov = ModelCovariance() - ext.fit(fitbaseline=True, estimate=False, cov=cov, - cov_format="default_output") + ext.fit(fitbaseline=True, estimate=False, cov=cov, cov_format="default_output") # Update calculated instance variables self.extraction_type = "fit_single" @@ -1180,11 +1335,12 @@ def fit_single(self): return cov -#end PeakExtraction class + +# end PeakExtraction class # simple test code -if __name__ == '__main__': +if __name__ == "__main__": from numpy.random import randn @@ -1195,13 +1351,13 @@ def fit_single(self): srmiselog.setlevel("info") srmiselog.liveplotting(False) - pf = GaussianOverR(.7) - res = .01 + pf = GaussianOverR(0.7) + res = 0.01 - pars = [[3, .2, 10], [3.5, .2, 10]] + pars = [[3, 0.2, 10], [3.5, 0.2, 10]] ideal_peaks = Peaks([pf.actualize(p, "pwa") for p in pars]) - r = np.arange(2,4,res) + r = np.arange(2, 4, res) y = ideal_peaks.value(r) + randn(len(r)) err = np.ones(len(r)) @@ -1209,14 +1365,14 @@ def fit_single(self): te = PeakExtraction() te.setdata(r, y, None, err) - te.setvars(rng=[1.51,10.], pf=[pf], cres=.1, effective_dy = 1.5*err) + te.setvars(rng=[1.51, 10.0], pf=[pf], cres=0.1, effective_dy=1.5 * err) te.extract_single() - print "--- Actual Peak parameters ---" - print ideal_peaks + print("--- Actual Peak parameters ---") + print(ideal_peaks) - print "\n--- After extraction ---" - print te + print("\n--- After extraction ---") + print(te) te.plot() - raw_input() + input() diff --git a/diffpy/srmise/peakstability.py b/diffpy/srmise/peakstability.py index 3897a2d..fcf5e31 100644 --- a/diffpy/srmise/peakstability.py +++ b/diffpy/srmise/peakstability.py @@ -27,7 +27,7 @@ class PeakStability: """Utility to test robustness of peaks. results: [error scalar, model, bl, dr] - ppe: a PDFPeakExtraction instance """ + ppe: a PDFPeakExtraction instance""" def __init__(self): self.results = [] @@ -39,11 +39,11 @@ def setppe(self, ppe): def load(self, filename): try: - import cPickle as pickle + import cPickle as pickle except: - import pickle + import pickle - in_s = open(filename, 'rb') + in_s = open(filename, "rb") try: (self.results, ppestr) = pickle.load(in_s) self.ppe = PDFPeakExtraction() @@ -67,10 +67,10 @@ def load(self, filename): def save(self, filename): try: - import cPickle as pickle + import cPickle as pickle except: - import pickle - out_s = open(filename, 'wb') + import pickle + out_s = open(filename, "wb") try: # Write to the stream outstr = self.ppe.writestr() @@ -82,18 +82,23 @@ def save(self, filename): if r[2] is None: bldict = None else: - bldict = {"pars":r[2].pars, "free":r[2].free, "removable":r[2].removable, "static_owner":r[2].static_owner} + bldict = { + "pars": r[2].pars, + "free": r[2].free, + "removable": r[2].removable, + "static_owner": r[2].static_owner, + } results2.append([r[0], r[1], bldict, r[3]]) pickle.dump([results2, outstr], out_s) finally: out_s.close() - def plotseries(self, style='o', **kwds): + def plotseries(self, style="o", **kwds): plt.figure() plt.ioff() for e, r, bl, dr in self.results: peakpos = [p["position"] for p in r] - es = [e]*len(peakpos) + es = [e] * len(peakpos) plt.plot(peakpos, es, style, **kwds) plt.ion() plt.draw() @@ -103,19 +108,32 @@ def plot(self, **kwds): plt.clf() plt.plot(*self.ppe.extracted.plottable(), **kwds) q = self.ppe.extracted.quality() - plt.suptitle("[%i/%i]\n" - "Uncertainty: %6.3f. Peaks: %i.\n" - "Quality: %6.3f. Chi-square: %6.3f" - %(self.current+1, len(self.results), self.ppe.effective_dy[0], len(self.ppe.extracted.model), q.stat, q.chisq)) + plt.suptitle( + "[%i/%i]\n" + "Uncertainty: %6.3f. Peaks: %i.\n" + "Quality: %6.3f. Chi-square: %6.3f" + % ( + self.current + 1, + len(self.results), + self.ppe.effective_dy[0], + len(self.ppe.extracted.model), + q.stat, + q.chisq, + ) + ) def setcurrent(self, idx): """Make the idxth model the active one.""" self.current = idx if idx is not None: result = self.results[idx] - self.ppe.setvars(quiet=True, effective_dy=result[0]*np.ones(len(self.ppe.x))) + self.ppe.setvars( + quiet=True, effective_dy=result[0] * np.ones(len(self.ppe.x)) + ) (r, y, dr, dy) = self.ppe.resampledata(result[3]) - self.ppe.extracted = ModelCluster(result[1], result[2], r, y, dy, None, self.ppe.error_method, self.ppe.pf) + self.ppe.extracted = ModelCluster( + result[1], result[2], r, y, dy, None, self.ppe.error_method, self.ppe.pf + ) else: self.ppe.clearcalc() @@ -142,7 +160,7 @@ def animate(self, results=None, step=False, **kwds): plt.ion() plt.draw() if step: - raw_input() + input() self.setcurrent(oldcurrent) @@ -153,18 +171,22 @@ def run(self, err, savecovs=False): self.results = [] covs = [] for i, e in enumerate(err): - print "---- Running for uncertainty %s (%i/%i) ----" %(e, i, len(err)) + print("---- Running for uncertainty %s (%i/%i) ----" % (e, i, len(err))) self.ppe.clearcalc() self.ppe.setvars(effective_dy=e) if savecovs: covs.append(self.ppe.extract()) else: self.ppe.extract() - dr = (self.ppe.extracted.r_cluster[-1]-self.ppe.extracted.r_cluster[0])/(len(self.ppe.extracted.r_cluster)-1) - self.results.append([e, self.ppe.extracted.model, self.ppe.extracted.baseline, dr]) + dr = ( + self.ppe.extracted.r_cluster[-1] - self.ppe.extracted.r_cluster[0] + ) / (len(self.ppe.extracted.r_cluster) - 1) + self.results.append( + [e, self.ppe.extracted.model, self.ppe.extracted.baseline, dr] + ) for e, r, bl, dr in self.results: - print "---- Results for uncertainty %s ----" %e - print r + print("---- Results for uncertainty %s ----" % e) + print(r) return covs diff --git a/diffpy/srmise/srmiselog.py b/diffpy/srmise/srmiselog.py index 3ea2c50..d971b73 100644 --- a/diffpy/srmise/srmiselog.py +++ b/diffpy/srmise/srmiselog.py @@ -48,11 +48,13 @@ defaultformat = "%(message)s" defaultlevel = logging.INFO -LEVELS = {'debug': logging.DEBUG, - 'info': logging.INFO, - 'warning': logging.WARNING, - 'error': logging.ERROR, - 'critical': logging.CRITICAL} +LEVELS = { + "debug": logging.DEBUG, + "info": logging.INFO, + "warning": logging.WARNING, + "error": logging.ERROR, + "critical": logging.CRITICAL, +} ### Set up logging to stdout ### logger = logging.getLogger("diffpy.srmise") @@ -72,13 +74,15 @@ liveplots = False wait = False + def addfilelog(filename, level=defaultlevel, format=defaultformat): """Log output from diffpy.srmise in specified file. Parameters filename: Name of file to receiving output level: The logging level - format: A string defining format of output messages conforming to logging package.""" + format: A string defining format of output messages conforming to logging package. + """ global fh fh = logging.FileHandler(filename) fh.setLevel(level) @@ -86,6 +90,7 @@ def addfilelog(filename, level=defaultlevel, format=defaultformat): fh.setFormatter(formatter) logger.addHandler(fh) + def setfilelevel(level): """Set level of file logger. @@ -101,6 +106,7 @@ def setfilelevel(level): emsg = "File handler does not exist, cannot set its level." raise SrMiseLogError(emsg) + def setlevel(level): """Set level of default (stdout) logger. @@ -112,6 +118,7 @@ def setlevel(level): if level < logger.getEffectiveLevel(): logger.setLevel(level) + def liveplotting(lp, w=False): """Set whether or not to use live plotting. @@ -138,6 +145,7 @@ def liveplotting(lp, w=False): ### TracePeaks. Primary purpose is to enable creating movies. ### + class TracePeaks(object): """Output trace information during peak extraction.""" @@ -191,15 +199,20 @@ def maketrace(self, *args, **kwds): clusters.append(m.slice) for m in args[1:]: mc.replacepeaks(m.model) - return {"mc":mc, "clusters":clusters, "recursion":self.recursion, "counter":self.counter} + return { + "mc": mc, + "clusters": clusters, + "recursion": self.recursion, + "counter": self.counter, + } def writestr(self, trace): """Return string representation of current trace.""" lines = [] lines.append("### Trace") - lines.append("counter=%i" %trace["counter"]) - lines.append("recursion=%i" %trace["recursion"]) - lines.append("clusters=%s" %trace["clusters"]) + lines.append("counter=%i" % trace["counter"]) + lines.append("recursion=%i" % trace["recursion"]) + lines.append("clusters=%s" % trace["clusters"]) lines.append("### ModelCluster") lines.append(trace["mc"].writestr()) @@ -207,8 +220,8 @@ def writestr(self, trace): def write(self, trace): """Write current trace to file.""" - filename = "%s_%i" %(self.filebase, trace["counter"]) - f = open(filename, 'w') + filename = "%s_%i" % (self.filebase, trace["counter"]) + f = open(filename, "w") bytes = self.writestr(trace) f.write(bytes) f.close() @@ -225,12 +238,14 @@ def read(self, filename): "mc" - A ModelCluster instance "recursion" - The recursion level of mc""" try: - return self.readstr(open(filename,'rb').read()) - except SrMiseDataFormatError, err: + return self.readstr(open(filename, "rb").read()) + except SrMiseDataFormatError as err: logger.exception("") basename = os.path.basename(filename) - emsg = ("Could not open '%s' due to unsupported file format " + - "or corrupted data. [%s]") % (basename, err) + emsg = ( + "Could not open '%s' due to unsupported file format " + + "or corrupted data. [%s]" + ) % (basename, err) raise SrMiseFileError(emsg) return None @@ -247,43 +262,49 @@ def readstr(self, datastring): "recursion" - The recursion level of mc""" # find where the ModelCluster section starts - res = re.search(r'^#+ ModelCluster\s*(?:#.*\s+)*', datastring, re.M) + res = re.search(r"^#+ ModelCluster\s*(?:#.*\s+)*", datastring, re.M) if res: - header = datastring[:res.start()] - mc = datastring[res.end():].strip() + header = datastring[: res.start()] + mc = datastring[res.end() :].strip() else: emsg = "Required section 'ModelCluster' not found." raise SrMiseDataFormatError(emsg) # instantiate ModelCluster - if re.match(r'^None$', mc): + if re.match(r"^None$", mc): mc = None else: from diffpy.srmise.modelcluster import ModelCluster + mc = ModelCluster.factory(mc) - res = re.search(r'^clusters=(.*)$', header, re.M) + res = re.search(r"^clusters=(.*)$", header, re.M) if res: clusters = eval(res.groups()[0].strip()) else: emsg = "Required field 'clusters' not found." raise SrMiseDataFormatError(emsg) - res = re.search(r'^recursion=(.*)$', header, re.M) + res = re.search(r"^recursion=(.*)$", header, re.M) if res: recursion = eval(res.groups()[0].strip()) else: emsg = "Required field 'recursion' not found." raise SrMiseDataFormatError(emsg) - res = re.search(r'^counter=(.*)$', header, re.M) + res = re.search(r"^counter=(.*)$", header, re.M) if res: counter = eval(res.groups()[0].strip()) else: emsg = "Required field 'counter' not found." raise SrMiseDataFormatError(emsg) - return {"mc":mc, "clusters":clusters, "recursion":self.recursion, "counter":self.counter} + return { + "mc": mc, + "clusters": clusters, + "recursion": self.recursion, + "counter": self.counter, + } def pushr(self): """Enter a layer of recursion, and return new level.""" @@ -315,16 +336,24 @@ def reset_trace(self): # filter property def setfilter(self, filter): - self.__filter = compile(" and ".join(["(%s)" %f for f in filter]), '', 'eval') - def getfilter(self): return self.__filter + self.__filter = compile( + " and ".join(["(%s)" % f for f in filter]), "", "eval" + ) + + def getfilter(self): + return self.__filter + filter = property(getfilter, setfilter) + ### End of class TracePeaks + def settracer(**kwds): global tracer tracer = TracePeaks(**kwds) return tracer + # Default tracer never emits tracer = settracer() From ff0fc587b88c8c9a8f28c7b317d61c02f12a6d16 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 29 Jul 2024 23:19:29 +0200 Subject: [PATCH 05/65] finish parenthesizing print statements (#24) * finish parenthesizing print statements * [pre-commit.ci] auto fixes from pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- devutils/prep.py | 44 +-- diffpy/srmise/applications/extract.py | 45 +--- diffpy/srmise/applications/plot.py | 55 +--- diffpy/srmise/basefunction.py | 49 +--- diffpy/srmise/baselines/arbitrary.py | 12 +- diffpy/srmise/baselines/base.py | 4 +- diffpy/srmise/baselines/fromsequence.py | 16 +- diffpy/srmise/baselines/nanospherical.py | 12 +- diffpy/srmise/baselines/polynomial.py | 12 +- diffpy/srmise/dataclusters.py | 13 +- diffpy/srmise/modelcluster.py | 105 ++------ diffpy/srmise/modelevaluators/aic.py | 4 +- diffpy/srmise/modelevaluators/aicc.py | 4 +- diffpy/srmise/modelevaluators/base.py | 24 +- diffpy/srmise/modelparts.py | 38 +-- diffpy/srmise/multimodelselection.py | 56 +--- diffpy/srmise/pdfdataset.py | 13 +- diffpy/srmise/pdfpeakextraction.py | 32 +-- diffpy/srmise/peakextraction.py | 82 ++---- diffpy/srmise/peaks/base.py | 75 ++++-- diffpy/srmise/peaks/gaussian.py | 245 +++++++++-------- diffpy/srmise/peaks/gaussianoverr.py | 309 ++++++++++++---------- diffpy/srmise/peaks/terminationripples.py | 120 +++++---- diffpy/srmise/peakstability.py | 12 +- diffpy/srmise/srmiselog.py | 12 +- doc/examples/multimodel_known_dG2.py | 76 +++--- doc/examples/multimodel_unknown_dG2.py | 91 ++++--- doc/examples/query_results.py | 102 ++++--- pyproject.toml | 20 ++ setup.py | 5 +- 30 files changed, 757 insertions(+), 930 deletions(-) create mode 100644 pyproject.toml diff --git a/devutils/prep.py b/devutils/prep.py index 8ecea6b..8571e2b 100644 --- a/devutils/prep.py +++ b/devutils/prep.py @@ -18,20 +18,21 @@ def __init__(self): def test(self, call, *args, **kwds): m = sys.modules[call.__module__] - testname = m.__name__+'.'+call.__name__ + testname = m.__name__ + "." + call.__name__ path = os.path.dirname(m.__file__) os.chdir(path) try: call(*args, **kwds) - self.messages.append("%s: success" %testname) - except Exception, e: - self.messages.append("%s: error, details below.\n%s" %(testname, e)) + self.messages.append("%s: success" % testname) + except Exception as e: + self.messages.append("%s: error, details below.\n%s" % (testname, e)) finally: os.chdir(__basedir__) def report(self): - print '==== Results of Tests ====' - print '\n'.join(self.messages) + print("==== Results of Tests ====") + print("\n".join(self.messages)) + def scrubeol(directory, filerestr): """Use unix-style endlines for files in directory matched by regex string. @@ -50,11 +51,11 @@ def scrubeol(directory, filerestr): text = unicode(original.read()) original.close() - updated = io.open(f, 'w', newline='\n') + updated = io.open(f, "w", newline="\n") updated.write(text) updated.close() - print "Updated %s to unix-style endlines." %f + print("Updated %s to unix-style endlines." % f) def rm(directory, filerestr): @@ -72,14 +73,13 @@ def rm(directory, filerestr): for f in files: os.remove(f) - print "Deleted %s." %f - + print("Deleted %s." % f) if __name__ == "__main__": # Temporarily add examples to path - lib_path = os.path.abspath(os.path.join('..','doc','examples')) + lib_path = os.path.abspath(os.path.join("..", "doc", "examples")) sys.path.append(lib_path) # Delete existing files that don't necessarily have a fixed name. @@ -88,14 +88,16 @@ def rm(directory, filerestr): ### Testing examples examples = Test() - test_names = ["extract_single_peak", - "parameter_summary", - "fit_initial", - "query_results", - "multimodel_known_dG1", - "multimodel_known_dG2", - "multimodel_unknown_dG1", - "multimodel_unknown_dG2"] + test_names = [ + "extract_single_peak", + "parameter_summary", + "fit_initial", + "query_results", + "multimodel_known_dG1", + "multimodel_known_dG2", + "multimodel_unknown_dG1", + "multimodel_unknown_dG2", + ] test_modules = [] for test in test_names: @@ -107,7 +109,7 @@ def rm(directory, filerestr): examples.report() ### Convert output of example files to Unix-style endlines for sdist. - if os.linesep != '\n': - print"==== Scrubbing Endlines ====" + if os.linesep != "\n": + print("==== Scrubbing Endlines ====") # All *.srmise and *.pwa files in examples directory. scrubeol("../doc/examples/output", r".*(\.srmise|\.pwa)") diff --git a/diffpy/srmise/applications/extract.py b/diffpy/srmise/applications/extract.py index 59ca6b2..6b89be9 100755 --- a/diffpy/srmise/applications/extract.py +++ b/diffpy/srmise/applications/extract.py @@ -149,9 +149,7 @@ def main(): "--pf", dest="peakfunction", metavar="PF", - help="Fit peak function PF defined in " - "diffpy.srmise.peaks, e.g. " - "'GaussianOverR(maxwidth=0.7)'", + help="Fit peak function PF defined in " "diffpy.srmise.peaks, e.g. " "'GaussianOverR(maxwidth=0.7)'", ) parser.add_option( "--cres", @@ -230,8 +228,7 @@ def main(): dest="bpoly0", type="string", metavar="a0[c]", - help="Use constant baseline given by y=a0. " - "Append 'c' to make parameter constant.", + help="Use constant baseline given by y=a0. " "Append 'c' to make parameter constant.", ) group.add_option( "--bpoly1", @@ -239,8 +236,7 @@ def main(): type="string", nargs=2, metavar="a1[c] a0[c]", - help="Use baseline given by y=a1*x + a0. Append 'c' to " - "make parameter constant.", + help="Use baseline given by y=a1*x + a0. Append 'c' to " "make parameter constant.", ) group.add_option( "--bpoly2", @@ -248,16 +244,14 @@ def main(): type="string", nargs=3, metavar="a2[c] a1[c] a0[c]", - help="Use baseline given by y=a2*x^2+a1*x + a0. Append " - "'c' to make parameter constant.", + help="Use baseline given by y=a2*x^2+a1*x + a0. Append " "'c' to make parameter constant.", ) group.add_option( "--bseq", dest="bseq", type="string", metavar="FILE", - help="Use baseline interpolated from x,y values in FILE. " - "This baseline has no free parameters.", + help="Use baseline interpolated from x,y values in FILE. " "This baseline has no free parameters.", ) group.add_option( "--bspherical", @@ -343,9 +337,7 @@ def main(): metavar="FILE", help="Save result of extraction to FILE (.srmise " "format).", ) - group.add_option( - "--plot", "-p", action="store_true", dest="plot", help="Plot extracted peaks." - ) + group.add_option("--plot", "-p", action="store_true", dest="plot", help="Plot extracted peaks.") group.add_option( "--liveplot", "-l", @@ -362,9 +354,7 @@ def main(): ) parser.add_option_group(group) - group = OptionGroup( - parser, "Verbosity Options", "Control detail printed to console." - ) + group = OptionGroup(parser, "Verbosity Options", "Control detail printed to console.") group.add_option( "--informative", "-i", @@ -435,9 +425,7 @@ def main(): options.peakfunction = eval("peaks." + options.peakfunction) except Exception as err: print(err) - print( - "Could not create peak function '%s'. Exiting." % options.peakfunction - ) + print("Could not create peak function '%s'. Exiting." % options.peakfunction) return if options.modelevaluator is not None: @@ -447,9 +435,7 @@ def main(): options.modelevaluator = eval("modelevaluators." + options.modelevaluator) except Exception as err: print(err) - print( - "Could not find ModelEvaluator '%s'. Exiting." % options.modelevaluator - ) + print("Could not find ModelEvaluator '%s'. Exiting." % options.modelevaluator) return if options.bcrystal is not None: @@ -534,9 +520,7 @@ def main(): if options.rng is not None: pdict["rng"] = list(options.rng) if options.qmax is not None: - pdict["qmax"] = ( - options.qmax if options.qmax == "automatic" else float(options.qmax) - ) + pdict["qmax"] = options.qmax if options.qmax == "automatic" else float(options.qmax) if options.nyquist is not None: pdict["nyquist"] = options.nyquist if options.supersample is not None: @@ -624,10 +608,7 @@ def _format_text(self, text): # the above is still the same bits = text.split("\n") formatted_bits = [ - textwrap.fill( - bit, text_width, initial_indent=indent, subsequent_indent=indent - ) - for bit in bits + textwrap.fill(bit, text_width, initial_indent=indent, subsequent_indent=indent) for bit in bits ] result = "\n".join(formatted_bits) + "\n" return result @@ -665,9 +646,7 @@ def format_option(self, option): help_lines.extend(textwrap.wrap(para, self.help_width)) # Everything is the same after here result.append("%*s%s\n" % (indent_first, "", help_lines[0])) - result.extend( - ["%*s%s\n" % (self.help_position, "", line) for line in help_lines[1:]] - ) + result.extend(["%*s%s\n" % (self.help_position, "", line) for line in help_lines[1:]]) elif opts[-1] != "\n": result.append("\n") return "".join(result) diff --git a/diffpy/srmise/applications/plot.py b/diffpy/srmise/applications/plot.py index 14f167e..0c82cb6 100755 --- a/diffpy/srmise/applications/plot.py +++ b/diffpy/srmise/applications/plot.py @@ -194,9 +194,7 @@ def makeplot(ppe_or_stability, ip=None, **kwds): x = ppe.x[rangeslice] y = ppe.y[rangeslice] dy = ppe.effective_dy[rangeslice] - mcluster = ModelCluster( - ppe.initial_peaks, ppe.baseline, x, y, dy, None, ppe.error_method, ppe.pf - ) + mcluster = ModelCluster(ppe.initial_peaks, ppe.baseline, x, y, dy, None, ppe.error_method, ppe.pf) ext = mcluster else: ext = ppe.extracted @@ -255,9 +253,7 @@ def makeplot(ppe_or_stability, ip=None, **kwds): # Define the various data which will be plotted r = ext.r_cluster dr = (r[-1] - r[0]) / len(r) - rexpand = np.concatenate( - (np.arange(r[0] - dr, xlo, -dr)[::-1], r, np.arange(r[-1] + dr, xhi + dr, dr)) - ) + rexpand = np.concatenate((np.arange(r[0] - dr, xlo, -dr)[::-1], r, np.arange(r[-1] + dr, xhi + dr, dr))) rfine = np.arange(r[0], r[-1], 0.1 * dr) gr_obs = np.array(resample(ppe.x, ppe.y, rexpand)) * scale # gr_fit = resample(r, ext.value(), rfine) @@ -295,21 +291,10 @@ def makeplot(ppe_or_stability, ip=None, **kwds): max_res = 0.0 # Derive various y limits based on all the offsets - rel_height = ( - 100.0 - - top_offset - - dg_height - - cmp_height - - datatop_offset - - databottom_offset - - bottom_offset - ) + rel_height = 100.0 - top_offset - dg_height - cmp_height - datatop_offset - databottom_offset - bottom_offset abs_height = 100 * ((max_gr - min_gr) + (max_res - min_res)) / rel_height - yhi = ( - max_gr - + (top_offset + dg_height + cmp_height + datatop_offset) * abs_height / 100 - ) + yhi = max_gr + (top_offset + dg_height + cmp_height + datatop_offset) * abs_height / 100 ylo = yhi - abs_height yhi = kwds.get("yhi", yhi) @@ -353,13 +338,7 @@ def makeplot(ppe_or_stability, ip=None, **kwds): # Remove labels above where insets begin # ax_data.yaxis.set_ticklabels([str(int(loc)) for loc in ax_data.yaxis.get_majorticklocs() if loc < datatop]) - ax_data.yaxis.set_ticks( - [ - loc - for loc in ax_data.yaxis.get_majorticklocs() - if (loc < datatop and loc >= ylo) - ] - ) + ax_data.yaxis.set_ticks([loc for loc in ax_data.yaxis.get_majorticklocs() if (loc < datatop and loc >= ylo)]) # Dataset label if datalabel is not None: @@ -378,9 +357,7 @@ def makeplot(ppe_or_stability, ip=None, **kwds): # Create new x axis at bottom edge of compare inset ax_data.axis["top"].set_visible(False) - ax_data.axis["newtop"] = ax_data.new_floating_axis( - 0, datatop, axis_direction="bottom" - ) # "top" bugged? + ax_data.axis["newtop"] = ax_data.new_floating_axis(0, datatop, axis_direction="bottom") # "top" bugged? ax_data.axis["newtop"].toggle(all=False, ticks=True) ax_data.axis["newtop"].major_ticks.set_tick_out(True) ax_data.axis["newtop"].minor_ticks.set_tick_out(True) @@ -399,9 +376,7 @@ def makeplot(ppe_or_stability, ip=None, **kwds): transform=ax_data.transAxes, ) labeldict[fig] = newylabel # so we can find the correct text object - fig.canvas.mpl_connect( - "draw_event", on_draw - ) # original label invisibility and updating + fig.canvas.mpl_connect("draw_event", on_draw) # original label invisibility and updating # Compare extracted (and ideal, if provided) peak positions clearly. if cmp_height > 0: @@ -610,15 +585,9 @@ def main(): type="int", help="Plot given model from set. Ignored if srmise_file is not a PeakStability file.", ) - parser.add_option( - "--show", action="store_true", help="execute pylab.show() blocking call" - ) - parser.add_option( - "-o", "--output", type="string", help="save plot to the specified file" - ) - parser.add_option( - "--format", type="string", default="eps", help="output format for plot saving" - ) + parser.add_option("--show", action="store_true", help="execute pylab.show() blocking call") + parser.add_option("-o", "--output", type="string", help="save plot to the specified file") + parser.add_option("--format", type="string", default="eps", help="output format for plot saving") parser.allow_interspersed_args = True opts, args = parser.parse_args(sys.argv[1:]) @@ -636,9 +605,7 @@ def main(): try: toplot.load(filename) except Exception: - print( - "File '%s' is not a .srmise or PeakStability data file." % filename - ) + print("File '%s' is not a .srmise or PeakStability data file." % filename) return if opts.model is not None: diff --git a/diffpy/srmise/basefunction.py b/diffpy/srmise/basefunction.py index e9af1ec..f56c5c2 100644 --- a/diffpy/srmise/basefunction.py +++ b/diffpy/srmise/basefunction.py @@ -116,21 +116,12 @@ def __init__( # Check validity of default_formats self.default_formats = default_formats - if not ( - "default_input" in self.default_formats - and "default_output" in self.default_formats - ): - emsg = ( - "Argument default_formats must specify 'default_input' " - + "and 'default_output' as keys." - ) + if not ("default_input" in self.default_formats and "default_output" in self.default_formats): + emsg = "Argument default_formats must specify 'default_input' " + "and 'default_output' as keys." raise ValueError(emsg) for f in self.default_formats.values(): if not f in self.parformats: - emsg = ( - "Keys of argument default_formats must map to a " - + "value within argument parformats." - ) + emsg = "Keys of argument default_formats must map to a " + "value within argument parformats." raise ValueError() # Set metadictionary @@ -195,10 +186,7 @@ def jacobian(self, p, r, rng=None): previously calculated values instead. """ if self is not p._owner: - emsg = ( - "Argument 'p' must be evaluated by the BaseFunction " - + "subclass which owns it." - ) + emsg = "Argument 'p' must be evaluated by the BaseFunction " + "subclass which owns it." raise ValueError(emsg) # normally r will be a sequence, but also allow single numeric values @@ -241,18 +229,12 @@ def transform_derivatives(self, pars, in_format=None, out_format=None): out_format = self.default_formats["default_input"] if not in_format in self.parformats: - raise ValueError( - "Argument 'in_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) if not out_format in self.parformats: - raise ValueError( - "Argument 'out_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) if in_format == out_format: return np.identity(self.npars) - return self._transform_derivativesraw( - pars, in_format=in_format, out_format=out_format - ) + return self._transform_derivativesraw(pars, in_format=in_format, out_format=out_format) def transform_parameters(self, pars, in_format=None, out_format=None): """Return new sequence with pars converted from in_format to out_format. @@ -282,18 +264,12 @@ def transform_parameters(self, pars, in_format=None, out_format=None): out_format = self.default_formats["default_input"] if not in_format in self.parformats: - raise ValueError( - "Argument 'in_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) if not out_format in self.parformats: - raise ValueError( - "Argument 'out_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) # if in_format == out_format: # return pars - return self._transform_parametersraw( - pars, in_format=in_format, out_format=out_format - ) + return self._transform_parametersraw(pars, in_format=in_format, out_format=out_format) def value(self, p, r, rng=None): """Calculate value of ModelPart over r, possibly restricted by range. @@ -307,10 +283,7 @@ def value(self, p, r, rng=None): previously calculated values instead. """ if self is not p._owner: - emsg = ( - "Argument 'p' must be evaluated by the BaseFunction " - + "subclass which owns it." - ) + emsg = "Argument 'p' must be evaluated by the BaseFunction " + "subclass which owns it." raise ValueError(emsg) # normally r will be a sequence, but also allow single numeric values diff --git a/diffpy/srmise/baselines/arbitrary.py b/diffpy/srmise/baselines/arbitrary.py index b661944..d2bc487 100644 --- a/diffpy/srmise/baselines/arbitrary.py +++ b/diffpy/srmise/baselines/arbitrary.py @@ -95,9 +95,7 @@ def __init__(self, npars, valuef, jacobianf=None, estimatef=None, Cache=None): metadict["valuef"] = (valuef, repr) metadict["jacobianf"] = (jacobianf, repr) metadict["estimatef"] = (estimatef, repr) - BaselineFunction.__init__( - self, parameterdict, formats, default_formats, metadict, None, Cache - ) + BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) #### Methods required by BaselineFunction #### @@ -173,17 +171,13 @@ def _transform_parametersraw(self, pars, in_format, out_format): if in_format == "internal": pass else: - raise ValueError( - "Argument 'in_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) # Convert to specified output format from "internal" format. if out_format == "internal": pass else: - raise ValueError( - "Argument 'out_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) return temp def _valueraw(self, pars, r): diff --git a/diffpy/srmise/baselines/base.py b/diffpy/srmise/baselines/base.py index 985cf9f..3f3844a 100644 --- a/diffpy/srmise/baselines/base.py +++ b/diffpy/srmise/baselines/base.py @@ -83,9 +83,7 @@ def __init__( additional functionality. Cache: A class (not instance) which implements caching of BaseFunction evaluations.""" - BaseFunction.__init__( - self, parameterdict, parformats, default_formats, metadict, base, Cache - ) + BaseFunction.__init__(self, parameterdict, parformats, default_formats, metadict, base, Cache) #### "Virtual" class methods #### diff --git a/diffpy/srmise/baselines/fromsequence.py b/diffpy/srmise/baselines/fromsequence.py index c75a8aa..718051a 100644 --- a/diffpy/srmise/baselines/fromsequence.py +++ b/diffpy/srmise/baselines/fromsequence.py @@ -50,9 +50,7 @@ def __init__(self, *args, **kwds): if len(args) == 1 and len(kwds) == 0: # load from file x, y = self.readxy(args[0]) - elif len(args) == 0 and ( - "file" in kwds and "x" not in kwds and "y" not in kwds - ): + elif len(args) == 0 and ("file" in kwds and "x" not in kwds and "y" not in kwds): # load file x, y = self.readxy(kwds["file"]) elif len(args) == 2 and len(kwds) == 0: @@ -80,9 +78,7 @@ def __init__(self, *args, **kwds): metadict = {} metadict["x"] = (x, self.xyrepr) metadict["y"] = (y, self.xyrepr) - BaselineFunction.__init__( - self, parameterdict, formats, default_formats, metadict, None, Cache=None - ) + BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache=None) #### Methods required by BaselineFunction #### @@ -130,17 +126,13 @@ def _transform_parametersraw(self, pars, in_format, out_format): if in_format == "internal": pass else: - raise ValueError( - "Argument 'in_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) # Convert to specified output format from "internal" format. if out_format == "internal": pass else: - raise ValueError( - "Argument 'out_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) return temp def _valueraw(self, pars, r): diff --git a/diffpy/srmise/baselines/nanospherical.py b/diffpy/srmise/baselines/nanospherical.py index 57f7444..88de784 100644 --- a/diffpy/srmise/baselines/nanospherical.py +++ b/diffpy/srmise/baselines/nanospherical.py @@ -52,9 +52,7 @@ def __init__(self, Cache=None): formats = ["internal"] default_formats = {"default_input": "internal", "default_output": "internal"} metadict = {} - BaselineFunction.__init__( - self, parameterdict, formats, default_formats, metadict, None, Cache - ) + BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) #### Methods required by BaselineFunction #### @@ -169,17 +167,13 @@ def _transform_parametersraw(self, pars, in_format, out_format): temp[0] = np.abs(temp[0]) temp[1] = np.abs(temp[1]) else: - raise ValueError( - "Argument 'in_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) # Convert to specified output format from "internal" format. if out_format == "internal": pass else: - raise ValueError( - "Argument 'out_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) return temp def _valueraw(self, pars, r): diff --git a/diffpy/srmise/baselines/polynomial.py b/diffpy/srmise/baselines/polynomial.py index a2cd4d5..646e9bf 100644 --- a/diffpy/srmise/baselines/polynomial.py +++ b/diffpy/srmise/baselines/polynomial.py @@ -52,9 +52,7 @@ def __init__(self, degree, Cache=None): default_formats = {"default_input": "internal", "default_output": "internal"} metadict = {} metadict["degree"] = (degree, repr) - BaselineFunction.__init__( - self, parameterdict, formats, default_formats, metadict, None, Cache - ) + BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) #### Methods required by BaselineFunction #### @@ -152,17 +150,13 @@ def _transform_parametersraw(self, pars, in_format, out_format): if in_format == "internal": pass else: - raise ValueError( - "Argument 'in_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) # Convert to specified output format from "internal" format. if out_format == "internal": pass else: - raise ValueError( - "Argument 'out_format' must be one of %s." % self.parformats - ) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) return temp def _valueraw(self, pars, r): diff --git a/diffpy/srmise/dataclusters.py b/diffpy/srmise/dataclusters.py index 0bf968b..ccfa27c 100644 --- a/diffpy/srmise/dataclusters.py +++ b/diffpy/srmise/dataclusters.py @@ -169,9 +169,7 @@ def next(self): else: # insert right of nearest cluster self.lastcluster_idx = nearest_cluster[0] + 1 - self.clusters = np.insert( - self.clusters, self.lastcluster_idx, [test_idx, test_idx], 0 - ) + self.clusters = np.insert(self.clusters, self.lastcluster_idx, [test_idx, test_idx], 0) return self def makeclusters(self): @@ -266,10 +264,7 @@ def cluster_is_full(self, cluster_idx): high = self.clusters[cluster_idx + 1, 0] - 1 else: high = len(self.data_order) - 1 - return ( - self.clusters[cluster_idx, 0] == low - and self.clusters[cluster_idx, 1] == high - ) + return self.clusters[cluster_idx, 0] == low and self.clusters[cluster_idx, 1] == high def combine_clusters(self, combine): """Combine clusters specified by each subarray of cluster indices. @@ -441,9 +436,7 @@ def animate(self): # simple test code if __name__ == "__main__": - x = np.array( - [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] - ) + x = np.array([-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0]) y = np.array( [ 0.0183156, diff --git a/diffpy/srmise/modelcluster.py b/diffpy/srmise/modelcluster.py index 1b4bfe0..2367297 100644 --- a/diffpy/srmise/modelcluster.py +++ b/diffpy/srmise/modelcluster.py @@ -58,9 +58,7 @@ class ModelCovariance(object): def __init__(self, *args, **kwds): """Intialize object.""" self.cov = None # The raw covariance matrix - self.model = ( - None # ModelParts instance, so both peaks and baseline (if present) - ) + self.model = None # ModelParts instance, so both peaks and baseline (if present) # Map i->[n1,n2,...] of the jth ModelPart to the n_i parameters in cov. self.mmap = {} @@ -98,9 +96,7 @@ def setcovariance(self, model, cov): emsg = "Parameter 'cov' must be a square matrix." raise ValueError(emsg) - if tempcov.shape[0] != model.npars(True) and tempcov.shape[0] != model.npars( - False - ): + if tempcov.shape[0] != model.npars(True) and tempcov.shape[0] != model.npars(False): emsg = [ "Parameter 'cov' must be an nxn matrix, where n is equal to the number of free ", "parameters in the model, or the total number of parameters (fixed and free) of ", @@ -248,9 +244,7 @@ def getcorrelation(self, i, j): if self.cov[i1, i1] == 0.0 or self.cov[j1, j1] == 0.0: return 0.0 # Avoiding undefined quantities is sensible in this context. else: - return self.cov[i1, j1] / ( - np.sqrt(self.cov[i1, i1]) * np.sqrt(self.cov[j1, j1]) - ) + return self.cov[i1, j1] / (np.sqrt(self.cov[i1, i1]) * np.sqrt(self.cov[j1, j1])) def getvalue(self, i): """Return value of parameter i. @@ -483,9 +477,7 @@ def writestr(self, **kwds): if self.peak_funcs is None: lines.append("peak_funcs=None") else: - lines.append( - "peak_funcs=%s" % repr([pfbaselist.index(p) for p in self.peak_funcs]) - ) + lines.append("peak_funcs=%s" % repr([pfbaselist.index(p) for p in self.peak_funcs])) if self.error_method is None: lines.append("ModelEvaluator=None") else: @@ -611,9 +603,7 @@ def factory(mcstr, **kwds): ### Instantiating baseline functions if readblf: blfbaselist = [] - res = re.split( - r"(?m)^#+ BaselineFunction \d+\s*(?:#.*\s+)*", baselinefunctions - ) + res = re.split(r"(?m)^#+ BaselineFunction \d+\s*(?:#.*\s+)*", baselinefunctions) for s in res[1:]: blfbaselist.append(BaseFunction.factory(s, blfbaselist)) @@ -676,10 +666,7 @@ def factory(mcstr, **kwds): for line in start_data.split("\n"): l = line.split() if len(arrays) != len(l): - emsg = ( - "Number of value fields does not match that given by '%s'" - % start_data_info - ) + emsg = "Number of value fields does not match that given by '%s'" % start_data_info for a, v in zip(arrays, line.split()): a.append(float(v)) except (ValueError, IndexError) as err: @@ -765,19 +752,11 @@ def join_adjacent(m1, m2): # border_x are removed. The highly unlikely case of two peaks # exactly at the border is also handled. for i in reversed(range(len(new_model))): - if ( - new_model[i]["position"] == border_x - and i > 0 - and new_model[i - 1]["position"] == border_x - ): + if new_model[i]["position"] == border_x and i > 0 and new_model[i - 1]["position"] == border_x: del new_model[i] elif new_ids[i] != i: - if ( - new_model[i]["position"] > border_x - and new_ids[i] < len(left.model) - ) and ( - new_model[i]["position"] < border_x - and new_ids[i] >= len(left.model) + if (new_model[i]["position"] > border_x and new_ids[i] < len(left.model)) and ( + new_model[i]["position"] < border_x and new_ids[i] >= len(left.model) ): del new_model[i] @@ -826,15 +805,11 @@ def change_slice(self, new_slice): # check if slice has expanded on the left if self.never_fit and self.slice.start < old_slice.start: left_slice = slice(self.slice.start, old_slice.start) - self.never_fit = ( - max(y_data_nobl[left_slice] - self.y_error[left_slice]) < 0 - ) + self.never_fit = max(y_data_nobl[left_slice] - self.y_error[left_slice]) < 0 # check if slice has expanded on the right if self.never_fit and self.slice.stop > old_slice.stop: right_slice = slice(old_slice.stop, self.slice.stop) - self.never_fit = ( - max(y_data_nobl[right_slice] - self.y_error[right_slice]) < 0 - ) + self.never_fit = max(y_data_nobl[right_slice] - self.y_error[right_slice]) < 0 return @@ -884,9 +859,7 @@ def estimatepeak(self): # throw some exception pass selected = self.peak_funcs[0] - estimate = selected.estimate_parameters( - self.r_cluster, self.y_cluster - self.valuebl() - ) + estimate = selected.estimate_parameters(self.r_cluster, self.y_cluster - self.valuebl()) if estimate is not None: newpeak = selected.actualize(estimate, "internal") @@ -943,11 +916,7 @@ def fit( orig_baseline = self.baseline.copy() self.last_fit_size = self.size - if ( - fitbaseline - and self.baseline is not None - and self.baseline.npars(count_fixed=False) > 0 - ): + if fitbaseline and self.baseline is not None and self.baseline.npars(count_fixed=False) > 0: y_datafit = self.y_data fmodel = ModelParts(self.model) fmodel.append(self.baseline) @@ -966,18 +935,12 @@ def fit( cov_format, ) except SrMiseFitError as e: - logger.debug( - "Error while fitting cluster: %s\nReverting to original model.", e - ) + logger.debug("Error while fitting cluster: %s\nReverting to original model.", e) self.model = orig_model self.baseline = orig_baseline return None - if ( - fitbaseline - and self.baseline is not None - and self.baseline.npars(count_fixed=False) > 0 - ): + if fitbaseline and self.baseline is not None and self.baseline.npars(count_fixed=False) > 0: self.model = Peaks(fmodel[:-1]) self.baseline = fmodel[-1] else: @@ -1039,10 +1002,9 @@ def contingent_fit(self, minpoints, growth_threshold): """ if self.never_fit: return None - if ( - self.last_fit_size > 0 - and float(self.size) / self.last_fit_size >= growth_threshold - ) or (self.last_fit_size == 0 and self.size >= minpoints): + if (self.last_fit_size > 0 and float(self.size) / self.last_fit_size >= growth_threshold) or ( + self.last_fit_size == 0 and self.size >= minpoints + ): return self.fit(justify=True) return None @@ -1063,10 +1025,7 @@ def cleanfit(self): outside_idx = [ i for i in outside_idx - if ( - self.model[i].removable - and max(self.model[i].value(self.r_cluster) - self.error_cluster) < 0 - ) + if (self.model[i].removable and max(self.model[i].value(self.r_cluster) - self.error_cluster) < 0) ] # TODO: Check for peaks that have blown up. @@ -1075,14 +1034,10 @@ def cleanfit(self): # NaN is too serious not to remove, even if removable is False, but I should look # into better handling anyway. - nan_idx = [ - i for i in range(len(self.model)) if np.isnan(self.model[i].pars).any() - ] + nan_idx = [i for i in range(len(self.model)) if np.isnan(self.model[i].pars).any()] if len(outside_idx) > 0: - msg = [ - "Following peaks outside cluster made no contribution within it and were removed:" - ] + msg = ["Following peaks outside cluster made no contribution within it and were removed:"] msg.extend([str(self.model[i]) for i in outside_idx]) logger.debug("\n".join(msg)) @@ -1147,9 +1102,7 @@ def value(self, r=None): return self.valuebl(r) else: if r is None: - return self.valuebl(r) + ( - self.model.value(self.r_data, self.slice)[self.slice] - ) + return self.valuebl(r) + (self.model.value(self.r_data, self.slice)[self.slice]) else: return self.valuebl(r) + (self.model.value(r)) @@ -1360,18 +1313,12 @@ def prune(self): msg = ["len(check_models): %s", "len(best_model): %s", "i: %s"] logger.debug("\n".join(msg), len(check_models), len(best_model), i) - addpars = ( - best_model.npars() - - check_models[i].npars() - - best_model[i].npars(count_fixed=False) - ) + addpars = best_model.npars() - check_models[i].npars() - best_model[i].npars(count_fixed=False) # Remove contribution of (effectively) fixed peaks y = np.array(y_nobl) if lo > 0: - logger.debug( - "len(sum): %s", len(np.sum(best_modely[:lo], axis=0)) - ) + logger.debug("len(sum): %s", len(np.sum(best_modely[:lo], axis=0))) y -= np.sum(best_modely[:lo], axis=0) if hi < len(best_modely): y -= np.sum(best_modely[hi:], axis=0) @@ -1408,9 +1355,7 @@ def prune(self): "check_qual: %s", "sorted check_qual: %s", ] - logger.debug( - "\n".join(msg), best_qual.stat, [c.stat for c in check_qual], arg - ) + logger.debug("\n".join(msg), best_qual.stat, [c.stat for c in check_qual], arg) arg = arg[-1] newbest_qual = check_qual[arg] diff --git a/diffpy/srmise/modelevaluators/aic.py b/diffpy/srmise/modelevaluators/aic.py index 1bb8f9a..729e556 100644 --- a/diffpy/srmise/modelevaluators/aic.py +++ b/diffpy/srmise/modelevaluators/aic.py @@ -113,9 +113,7 @@ def growth_justified(self, fit, k_prime): # assert n >= self.minPoints(kActual) #check that AIC is defined for the actual fit if n < self.minpoints(k_actual): - logger.warn( - "AIC.growth_justified(): too few data to evaluate quality reliably." - ) + logger.warn("AIC.growth_justified(): too few data to evaluate quality reliably.") n = self.minpoints(k_actual) penalty = self.parpenalty(k_test, n) - self.parpenalty(k_actual, n) diff --git a/diffpy/srmise/modelevaluators/aicc.py b/diffpy/srmise/modelevaluators/aicc.py index 2e17e76..1e31b90 100644 --- a/diffpy/srmise/modelevaluators/aicc.py +++ b/diffpy/srmise/modelevaluators/aicc.py @@ -115,9 +115,7 @@ def growth_justified(self, fit, k_prime): # assert n >= self.minPoints(kActual) #check that AICc is defined for the actual fit if n < self.minpoints(k_actual): - logger.warn( - "AICc.growth_justified(): too few data to evaluate quality reliably." - ) + logger.warn("AICc.growth_justified(): too few data to evaluate quality reliably.") n = self.minpoints(k_actual) penalty = self.parpenalty(k_test, n) - self.parpenalty(k_actual, n) diff --git a/diffpy/srmise/modelevaluators/base.py b/diffpy/srmise/modelevaluators/base.py index eb28272..54c304a 100644 --- a/diffpy/srmise/modelevaluators/base.py +++ b/diffpy/srmise/modelevaluators/base.py @@ -68,9 +68,7 @@ def __lt__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert ( - self.stat != None and other.stat != None - ) # The statistic must already be calculated + assert self.stat != None and other.stat != None # The statistic must already be calculated if self.higher_is_better: return self.stat < other.stat @@ -81,9 +79,7 @@ def __le__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert ( - self.stat != None and other.stat != None - ) # The statistic must already be calculated + assert self.stat != None and other.stat != None # The statistic must already be calculated if self.higher_is_better: return self.stat <= other.stat @@ -94,9 +90,7 @@ def __eq__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert ( - self.stat != None and other.stat != None - ) # The statistic must already be calculated + assert self.stat != None and other.stat != None # The statistic must already be calculated return self.stat == other.stat @@ -104,9 +98,7 @@ def __ne__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert ( - self.stat != None and other.stat != None - ) # The statistic must already be calculated + assert self.stat != None and other.stat != None # The statistic must already be calculated return self.stat != other.stat @@ -114,9 +106,7 @@ def __gt__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert ( - self.stat != None and other.stat != None - ) # The statistic must already be calculated + assert self.stat != None and other.stat != None # The statistic must already be calculated if self.higher_is_better: return self.stat > other.stat @@ -127,9 +117,7 @@ def __ge__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert ( - self.stat != None and other.stat != None - ) # The statistic must already be calculated + assert self.stat != None and other.stat != None # The statistic must already be calculated if self.higher_is_better: return self.stat >= other.stat diff --git a/diffpy/srmise/modelparts.py b/diffpy/srmise/modelparts.py index 4943e0a..046b2f1 100644 --- a/diffpy/srmise/modelparts.py +++ b/diffpy/srmise/modelparts.py @@ -173,11 +173,7 @@ def fit( y, r, (y - self.value(r, range=range)) - 1.1 * (max(y) - min(y)), - *[ - i - for sublist in [[r, p.value(r, range=range)] for p in self] - for i in sublist - ] + *[i for sublist in [[r, p.value(r, range=range)] for p in self] for i in sublist] ) plt.draw() @@ -193,9 +189,7 @@ def fit( # clean up parameters for p in self: - p.pars = p.owner().transform_parameters( - p.pars, in_format="internal", out_format="internal" - ) + p.pars = p.owner().transform_parameters(p.pars, in_format="internal", out_format="internal") # Supply estimated covariance matrix if requested. # The precise relationship between f[1] and estimated covariance matrix is a little unclear from @@ -282,8 +276,7 @@ def residual_jacobian(self, freepars, r, y_expected, y_error, range=None): """ if len(freepars) == 0: raise ValueError( - "Argument freepars has length 0. The Jacobian " - "is only defined with >=1 free parameters." + "Argument freepars has length 0. The Jacobian " "is only defined with >=1 free parameters." ) self.pack_freepars(freepars) @@ -340,10 +333,7 @@ def covariance(self, format="internal", **kwds): try: idx = int(k[1:]) except ValueError: - emsg = ( - "Invalid format keyword '%s'. They must be specified as 'f0', 'f1', etc." - % k - ) + emsg = "Invalid format keyword '%s'. They must be specified as 'f0', 'f1', etc." % k raise ValueError(emsg) formats[int(k[1:])] = v @@ -431,10 +421,7 @@ def __init__(self, owner, pars, free=None, removable=True, static_owner=False): self._owner = owner if len(pars) != owner.npars: - emsg = ( - "The length of pars must equal the number of parameters " - + "specified by the model part owner." - ) + emsg = "The length of pars must equal the number of parameters " + "specified by the model part owner." raise ValueError(emsg) self.pars = np.array(pars[:]) # pars[:] in case pars is a ModelPart @@ -466,10 +453,7 @@ def changeowner(self, owner): emsg = "Cannot change owner if static_owner is True." raise SrMiseStaticOwnerError(emsg) if self._owner.npars != owner.npars: - emsg = ( - "New owner specifies different number of parameters than " - + "original owner." - ) + emsg = "New owner specifies different number of parameters than " + "original owner." raise SrMiseStaticOwnerError(emsg) self._owner = owner @@ -524,9 +508,7 @@ def copy(self): The original and the copy are completely independent, except they both reference the same owner.""" - return type(self).__call__( - self._owner, self.pars, self.free, self.removable, self.static_owner - ) + return type(self).__call__(self._owner, self.pars, self.free, self.removable, self.static_owner) def __getitem__(self, key_or_idx): """Return parameter of peak corresponding with key_or_idx. @@ -580,11 +562,7 @@ def npars(self, count_fixed=True): def __str__(self): """Return string representation of ModelPart parameters.""" - return str( - self._owner.transform_parameters( - self.pars, in_format="internal", out_format="default_output" - ) - ) + return str(self._owner.transform_parameters(self.pars, in_format="internal", out_format="default_output")) def __eq__(self, other): """ """ diff --git a/diffpy/srmise/multimodelselection.py b/diffpy/srmise/multimodelselection.py index 01995e2..53773f1 100644 --- a/diffpy/srmise/multimodelselection.py +++ b/diffpy/srmise/multimodelselection.py @@ -54,9 +54,7 @@ def __init__(self): self.classweights = {} self.classprobs = {} self.sortedclassprobs = {} - self.sortedclasses = ( - {} - ) # dg->as self.classes, but with model indices sorted by best AIC + self.sortedclasses = {} # dg->as self.classes, but with model indices sorted by best AIC PeakStability.__init__(self) return @@ -71,9 +69,7 @@ def makeaics(self, dgs, dr, filename=None): nominal value. filename - Optional file to save pickled results """ - aics_out = ( - {} - ) # Version of self.aics that holds only the statistic, not the AIC object. + aics_out = {} # Version of self.aics that holds only the statistic, not the AIC object. self.dgs = np.array(dgs) for i, dg in enumerate(self.dgs): self.dgs_idx[dg] = i @@ -100,17 +96,13 @@ def makeaics(self, dgs, dr, filename=None): # modelevaluators subpackage are in need of a rewrite, and so it would be # best to do them all at once. dg0 = self.dgs[0] - mc = ModelCluster( - result[1], result[2], r, y, dg0 * np.ones(len(r)), None, em, self.ppe.pf - ) + mc = ModelCluster(result[1], result[2], r, y, dg0 * np.ones(len(r)), None, em, self.ppe.pf) em0 = mc.quality() for dg in self.dgs: em_instance = em() em_instance.chisq = em0.chisq * (dg0 / dg) ** 2 # rescale chi-square - em_instance.evaluate( - mc - ) # evaluate AIC without recalculating chi-square + em_instance.evaluate(mc) # evaluate AIC without recalculating chi-square self.aics[dg].append(em_instance) aics_out[dg].append(em_instance.stat) @@ -198,9 +190,7 @@ def animate_probs(self, step=False, duration=0.0, **kwds): best_idx = self.sortedprobs[self.dgs[0]][-1] (line,) = plt.plot(self.dgs, self.aicprobs[self.dgs[0]]) vline = plt.axvline(self.dgs[0]) - (dot,) = plt.plot( - self.dgs[best_idx], self.aicprobs[self.dgs[0]][best_idx], "ro" - ) + (dot,) = plt.plot(self.dgs[best_idx], self.aicprobs[self.dgs[0]][best_idx], "ro") plt.subplot(212) self.setcurrent(best_idx) @@ -244,9 +234,7 @@ def animate_classprobs(self, step=False, duration=0.0, **kwds): arrow_left = len(self.classes) - 1 arrow_right = arrow_left + 0.05 * arrow_left (line,) = plt.plot(range(len(self.classes)), self.classprobs[self.dgs[0]]) - (dot,) = plt.plot( - self.dgs[best_idx], self.classprobs[self.dgs[0]][bestclass_idx], "ro" - ) + (dot,) = plt.plot(self.dgs[best_idx], self.classprobs[self.dgs[0]][bestclass_idx], "ro") plt.axvline(arrow_left, color="k") ax2 = ax1.twinx() ax2.set_ylim(self.dgs[0], self.dgs[-1]) @@ -315,9 +303,7 @@ def classify(self, r, tolerance=0.05): self.classes_idx = {} self.class_tolerance = None - classes = ( - [] - ) # each element is a list of the models (result indices) in the class + classes = [] # each element is a list of the models (result indices) in the class classes_idx = {} # given an integer corresponding to a model, return its class epsqval = {} # holds the squared value of each class' exemplar peaks ebsqval = {} # holds the squared value of each class exemplar baselines @@ -358,9 +344,7 @@ def classify(self, r, tolerance=0.05): for p, ep in zip(psqval, epsqval[c]): basediff = np.abs(np.sum(p - ep)) # if basediff > tolerance*np.sum(ep): - if basediff > tolerance * np.sum( - ep - ) or basediff > tolerance * np.sum(p): + if basediff > tolerance * np.sum(ep) or basediff > tolerance * np.sum(p): badpeak = True break if badpeak: @@ -369,9 +353,7 @@ def classify(self, r, tolerance=0.05): # check baseline values basediff = np.abs(np.sum(bsqval - ebsqval[c])) # if basediff > tolerance*np.sum(ebsqval[c]): - if basediff > tolerance * np.sum( - ebsqval[c] - ) or basediff > tolerance * np.sum(bsqval): + if basediff > tolerance * np.sum(ebsqval[c]) or basediff > tolerance * np.sum(bsqval): continue # that's all the checks, add to current class @@ -419,9 +401,7 @@ def makeclassweights(self): for dg in self.dgs: bestinclass = [cls[-1] for cls in self.sortedclasses[dg]] - self.classweights[dg] = em.akaikeweights( - [self.aics[dg][b] for b in bestinclass] - ) + self.classweights[dg] = em.akaikeweights([self.aics[dg][b] for b in bestinclass]) def makeclassprobs(self): self.classprobs = {} @@ -429,9 +409,7 @@ def makeclassprobs(self): for dg in self.dgs: bestinclass = [cls[-1] for cls in self.sortedclasses[dg]] - self.classprobs[dg] = em.akaikeprobs( - [self.aics[dg][b] for b in bestinclass] - ) + self.classprobs[dg] = em.akaikeprobs([self.aics[dg][b] for b in bestinclass]) def makesortedclassprobs(self): self.sortedclassprobs = {} @@ -577,9 +555,7 @@ def plot3dclassprobs(self, **kwds): elif norm is "full": mcolor = len(self.results) if class_size is "number": - norm = colors.BoundaryNorm( - np.linspace(0, mcolor + 1, mcolor + 2), mcolor + 1 - ) + norm = colors.BoundaryNorm(np.linspace(0, mcolor + 1, mcolor + 2), mcolor + 1) if class_size is "fraction": norm = colors.Normalize(0.0, 1.0) @@ -637,18 +613,14 @@ def plot3dclassprobs(self, **kwds): cbaxis = fig.add_axes(rect) # Remove all colorbar.make_axes keywords except orientation - kwds = eatkwds( - "fraction", "pad", "shrink", "aspect", "anchor", "panchor", **kwds - ) + kwds = eatkwds("fraction", "pad", "shrink", "aspect", "anchor", "panchor", **kwds) else: kwds.setdefault("shrink", 0.75) # In matplotlib 1.1.0 make_axes_gridspec ignores anchor and panchor keywords. # Eat these keywords for now. kwds = eatkwds("anchor", "panchor", **kwds) - cbaxis, kwds = colorbar.make_axes_gridspec( - ax, **kwds - ) # gridspec allows tight_layout + cbaxis, kwds = colorbar.make_axes_gridspec(ax, **kwds) # gridspec allows tight_layout plt.tight_layout() # do it after cbaxis, so colorbar isn't ignored cb = colorbar.ColorbarBase(cbaxis, cmap=cmap, norm=norm, **kwds) diff --git a/diffpy/srmise/pdfdataset.py b/diffpy/srmise/pdfdataset.py index feb64fa..034a93d 100644 --- a/diffpy/srmise/pdfdataset.py +++ b/diffpy/srmise/pdfdataset.py @@ -166,10 +166,10 @@ def read(self, filename): self.readStr(open(filename, "rb").read()) except PDFDataFormatError as err: basename = os.path.basename(filename) - emsg = ( - "Could not open '%s' due to unsupported file format " - + "or corrupted data. [%s]" - ) % (basename, err) + emsg = ("Could not open '%s' due to unsupported file format " + "or corrupted data. [%s]") % ( + basename, + err, + ) raise SrMiseFileError(emsg) self.filename = os.path.abspath(filename) return self @@ -350,10 +350,7 @@ def writeStr(self): lines.append("##### start data") lines.append("#L r(A) G(r) d_r d_Gr") for i in range(len(self.robs)): - lines.append( - "%g %g %g %g" - % (self.robs[i], self.Gobs[i], self.drobs[i], self.dGobs[i]) - ) + lines.append("%g %g %g %g" % (self.robs[i], self.Gobs[i], self.drobs[i], self.dGobs[i])) # that should be it datastring = "\n".join(lines) + "\n" return datastring diff --git a/diffpy/srmise/pdfpeakextraction.py b/diffpy/srmise/pdfpeakextraction.py index 06bd2be..1971bce 100644 --- a/diffpy/srmise/pdfpeakextraction.py +++ b/diffpy/srmise/pdfpeakextraction.py @@ -234,8 +234,7 @@ def resampledata(self, dr, **kwds): # Not a robust epsilon test, but all physical Nyquist rates in same oom. if dr - dr_nyquist > eps: logger.warning( - "Resampling at %s, below Nyquist rate of %s. Information will be lost!" - % (dr, dr_nyquist) + "Resampling at %s, below Nyquist rate of %s. Information will be lost!" % (dr, dr_nyquist) ) r = np.arange(max(self.x[0], self.rng[0]), min(self.x[-1], self.rng[1]), dr) @@ -440,9 +439,7 @@ def extract(self, **kwds): break else: - ext = ModelCluster( - ext.model, bl, r1, y1, y_error1, None, self.error_method, self.pf - ) + ext = ModelCluster(ext.model, bl, r1, y1, y_error1, None, self.error_method, self.pf) ext.prune() logger.info("Model after resampling and termination ripples:\n%s", ext) @@ -457,9 +454,7 @@ def extract(self, **kwds): logger.info(str(cov)) # logger.info("Correlations > .8:\n%s", "\n".join(str(c) for c in cov.correlationwarning(.8))) except SrMiseUndefinedCovarianceError as e: - logger.warn( - "Covariance not defined for final model. Fit may not have converged." - ) + logger.warn("Covariance not defined for final model. Fit may not have converged.") logger.info(str(ext)) # Update calculated instance variables @@ -532,9 +527,7 @@ def fit(self, **kwds): try: logger.info(str(cov)) except SrMiseUndefinedCovarianceError as e: - logger.warn( - "Covariance not defined for final model. Fit may not have converged." - ) + logger.warn("Covariance not defined for final model. Fit may not have converged.") # Update calculated instance variables self.extraction_type = "fit" @@ -703,13 +696,8 @@ def writepwastr(self, comments): lines.append("## Model Quality") # Quality of fit - lines.append( - "# Quality reported by ModelEvaluator: %s" % self.extracted.quality().stat - ) - lines.append( - "# Free parameters in extracted peaks: %s" - % self.extracted.model.npars(count_fixed=False) - ) + lines.append("# Quality reported by ModelEvaluator: %s" % self.extracted.quality().stat) + lines.append("# Free parameters in extracted peaks: %s" % self.extracted.model.npars(count_fixed=False)) if self.baseline is not None: fblpars = self.baseline.npars(count_fixed=False) else: @@ -830,15 +818,11 @@ def resample(orig_r, orig_y, new_r): dr = (orig_r[-1] - orig_r[0]) / (n - 1) if new_r[0] < orig_r[0]: - logger.warning( - "Resampling outside original grid: %s (requested) < %s (original)" - % (new_r[0], orig_r[0]) - ) + logger.warning("Resampling outside original grid: %s (requested) < %s (original)" % (new_r[0], orig_r[0])) if new_r[-1] > orig_r[-1]: logger.warning( - "Resampling outside original grid: %s (requested) > %s (original)" - % (new_r[-1], orig_r[-1]) + "Resampling outside original grid: %s (requested) > %s (original)" % (new_r[-1], orig_r[-1]) ) new_y = new_r * 0.0 diff --git a/diffpy/srmise/peakextraction.py b/diffpy/srmise/peakextraction.py index 8f1d404..30235e6 100644 --- a/diffpy/srmise/peakextraction.py +++ b/diffpy/srmise/peakextraction.py @@ -179,9 +179,7 @@ def defaultvars(self, *args): self.effective_dy = self.dy else: # A terribly crude guess - self.effective_dy = ( - 0.05 * (np.max(self.y) - np.min(self.y)) * np.ones(len(self.x)) - ) + self.effective_dy = 0.05 * (np.max(self.y) - np.min(self.y)) * np.ones(len(self.x)) elif np.isscalar(self.effective_dy) and self.effective_dy > 0: self.effective_dy = self.effective_dy * np.ones(len(self.x)) @@ -207,9 +205,7 @@ def defaultvars(self, *args): self.baseline = self.baseline.actualize(epars, "internal") logger.info("Estimating baseline: %s" % self.baseline) except (NotImplementedError, SrMiseEstimationError): - logger.error( - "Could not estimate baseline from provided BaselineFunction, trying default values." - ) + logger.error("Could not estimate baseline from provided BaselineFunction, trying default values.") self.baseline = None if self.baseline is None or "baseline" in args: @@ -282,10 +278,10 @@ def read(self, filename): except SrMiseDataFormatError as err: logger.exception("") basename = os.path.basename(filename) - emsg = ( - "Could not open '%s' due to unsupported file format " - + "or corrupted data. [%s]" - ) % (basename, err) + emsg = ("Could not open '%s' due to unsupported file format " + "or corrupted data. [%s]") % ( + basename, + err, + ) raise SrMiseFileError(emsg) return self @@ -466,10 +462,7 @@ def readstr(self, datastring): for line in start_data.split("\n"): l = line.split() if len(arrays) != len(l): - emsg = ( - "Number of value fields does not match that given by '%s'" - % start_data_info - ) + emsg = "Number of value fields does not match that given by '%s'" % start_data_info for a, v in zip(arrays, line.split()): a.append(float(v)) except (ValueError, IndexError) as err: @@ -503,9 +496,7 @@ def readstr(self, datastring): if re.match(r"^None$", mc): self.extracted = None else: - self.extracted = ModelCluster.factory( - mc, pfbaselist=safepf, blfbaselist=safebf - ) + self.extracted = ModelCluster.factory(mc, pfbaselist=safepf, blfbaselist=safebf) def write(self, filename): """Write string representation of PeakExtraction instance to file. @@ -706,11 +697,7 @@ def estimate_peak(self, x, add=True): # Determine clusters using initial_peaks and pre-defined or estimated baseline rangeslice = self.getrangeslice() x1 = self.x[rangeslice] - y1 = ( - self.y[rangeslice] - - self.baseline.value(x1) - - self.initial_peaks.value(x1) - ) + y1 = self.y[rangeslice] - self.baseline.value(x1) - self.initial_peaks.value(x1) dy = self.effective_dy[rangeslice] if x < x1[0] or x > x1[-1]: @@ -730,9 +717,7 @@ def estimate_peak(self, x, add=True): y1 = y1[cslice] dy = dy[cslice] - mcluster = ModelCluster( - None, None, x1, y1, dy, None, self.error_method, self.pf - ) + mcluster = ModelCluster(None, None, x1, y1, dy, None, self.error_method, self.pf) mcluster.fit() if len(mcluster.model) > 0: @@ -798,11 +783,7 @@ def extract_single(self, recursion_depth=1): y = self.y[rangeslice] - ip.value(x) # List of ModelClusters containing extracted peaks. - mclusters = [ - ModelCluster( - None, bl, x, y, dy, dclusters.cut(0), self.error_method, self.pf - ) - ] + mclusters = [ModelCluster(None, bl, x, y, dy, dclusters.cut(0), self.error_method, self.pf)] # The minimum number of points required to make a valid fit, as # determined by the peak functions and error method used. This is a @@ -844,9 +825,7 @@ def extract_single(self, recursion_depth=1): ) else: # Update an existing cluster - mclusters[step.lastcluster_idx].change_slice( - step.cut(step.lastcluster_idx) - ) + mclusters[step.lastcluster_idx].change_slice(step.cut(step.lastcluster_idx)) # Find newly adjacent clusters adjacent = step.find_adjacent_clusters().ravel() @@ -937,9 +916,7 @@ def extract_single(self, recursion_depth=1): right_data = min(len(x), x.searchsorted(peak_pos[pivot + 1]) + 1) near_peaks = np.append(near_peaks, pivot) - other_peaks = np.concatenate( - [np.arange(0, pivot - 1), np.arange(pivot + 1, len(peak_pos))] - ) + other_peaks = np.concatenate([np.arange(0, pivot - 1), np.arange(pivot + 1, len(peak_pos))]) # Go from indices to lists of peaks. near_peaks = Peaks([full_cluster.model[i] for i in near_peaks]) @@ -983,9 +960,7 @@ def extract_single(self, recursion_depth=1): self.error_method, self.pf, ) - recurse = len(near_peaks) > 0 and checkrec.quality().growth_justified( - checkrec, min_npars - ) + recurse = len(near_peaks) > 0 and checkrec.quality().growth_justified(checkrec, min_npars) if recurse and recursion_depth < 3: logger.info( @@ -1004,8 +979,7 @@ def extract_single(self, recursion_depth=1): rec_search.extract_single(recursion_depth + 1) rec = rec_search.extracted logger.info( - "*********ENDING RECURSION level %s (full boundary) ************\n" - % (recursion_depth + 1) + "*********ENDING RECURSION level %s (full boundary) ************\n" % (recursion_depth + 1) ) # Incorporate best peaks from recursive search. @@ -1024,9 +998,7 @@ def extract_single(self, recursion_depth=1): "%s", "---End of combining clusters---", ] - logger.debug( - "\n".join(msg), mclusters[step.lastcluster_idx], full_cluster - ) + logger.debug("\n".join(msg), mclusters[step.lastcluster_idx], full_cluster) mclusters[step.lastcluster_idx] = full_cluster ### End update cluster fits ### @@ -1043,9 +1015,7 @@ def extract_single(self, recursion_depth=1): cleft = step.clusters[idx - 1] cright = step.clusters[idx] - new_cluster = ModelCluster.join_adjacent( - mclusters[idx - 1], mclusters[idx] - ) + new_cluster = ModelCluster.join_adjacent(mclusters[idx - 1], mclusters[idx]) # Estimate coordinate where clusters combine. border_x = 0.5 * (x[cleft[1]] + x[cright[0]]) @@ -1089,9 +1059,7 @@ def extract_single(self, recursion_depth=1): right_data = min(len(x), x.searchsorted(peak_pos[pivot + 1]) + 1) near_peaks = np.append(near_peaks, pivot) - other_peaks = np.concatenate( - [np.arange(0, pivot - 1), np.arange(pivot + 1, len(peak_pos))] - ) + other_peaks = np.concatenate([np.arange(0, pivot - 1), np.arange(pivot + 1, len(peak_pos))]) # Go from indices to lists of peaks. near_peaks = Peaks([new_cluster.model[i] for i in near_peaks]) @@ -1202,14 +1170,11 @@ def extract_single(self, recursion_depth=1): self.error_method, self.pf, ) - recurse2 = len(near_peaks) > 0 and checkrec.quality().growth_justified( - checkrec, min_npars - ) + recurse2 = len(near_peaks) > 0 and checkrec.quality().growth_justified(checkrec, min_npars) if recurse2 and recursion_depth < 3: logger.info( - "\n*********STARTING RECURSION level %s (prefit)************" - % (recursion_depth + 1) + "\n*********STARTING RECURSION level %s (prefit)************" % (recursion_depth + 1) ) rec_search2 = PeakExtraction() rec_search2.setdata(rec_r2, rec_y2, None, rec_error2) @@ -1223,8 +1188,7 @@ def extract_single(self, recursion_depth=1): rec_search2.extract_single(recursion_depth + 1) rec2 = rec_search2.extracted logger.info( - "*********ENDING RECURSION level %s (prefit) ************\n" - % (recursion_depth + 1) + "*********ENDING RECURSION level %s (prefit) ************\n" % (recursion_depth + 1) ) # Incorporate best peaks from recursive search. @@ -1255,9 +1219,7 @@ def extract_single(self, recursion_depth=1): "---End of combining clusters---", ] - logger.debug( - "\n".join(msg), mclusters[idx - 1], mclusters[idx], new_cluster - ) + logger.debug("\n".join(msg), mclusters[idx - 1], mclusters[idx], new_cluster) mclusters[idx - 1] = new_cluster del mclusters[idx] diff --git a/diffpy/srmise/peaks/base.py b/diffpy/srmise/peaks/base.py index d8e3469..6e87d83 100644 --- a/diffpy/srmise/peaks/base.py +++ b/diffpy/srmise/peaks/base.py @@ -22,6 +22,7 @@ logger = logging.getLogger("diffpy.srmise") + class PeakFunction(BaseFunction): """Base class for functions which represent peaks. @@ -60,7 +61,15 @@ class PeakFunction(BaseFunction): transform_parameters() """ - def __init__(self, parameterdict, parformats, default_formats, metadict, base=None, Cache=None): + def __init__( + self, + parameterdict, + parformats, + default_formats, + metadict, + base=None, + Cache=None, + ): """Set parameterdict defined by subclass parameterdict: A dictionary mapping string keys to their index in a @@ -82,24 +91,31 @@ def __init__(self, parameterdict, parformats, default_formats, metadict, base=No raise ValueError(emsg) BaseFunction.__init__(self, parameterdict, parformats, default_formats, metadict, base, Cache) - #### "Virtual" class methods #### def scale_at(self, peak, x, scale): emsg = "scale_at must be implemented in a PeakFunction subclass." raise NotImplementedError(emsg) - #### Methods required by BaseFunction #### - def actualize(self, pars, in_format="default_input", free=None, removable=True, static_owner=False): + def actualize( + self, + pars, + in_format="default_input", + free=None, + removable=True, + static_owner=False, + ): converted = self.transform_parameters(pars, in_format, out_format="internal") return Peak(self, converted, free, removable, static_owner) def getmodule(self): return __name__ -#end of class PeakFunction + +# end of class PeakFunction + class Peaks(ModelParts): """A collection for Peak objects.""" @@ -110,12 +126,12 @@ def __init__(self, *args, **kwds): def argsort(self, key="position"): """Return sequence of indices which sort peaks in order specified by key.""" - keypars=np.array([p[key] for p in self]) + keypars = np.array([p[key] for p in self]) # In normal use the peaks will already be sorted, so check for it. - sorted=True - for i in range(len(keypars)-1): - if keypars[i] > keypars[i+1]: - sorted=False + sorted = True + for i in range(len(keypars) - 1): + if keypars[i] > keypars[i + 1]: + sorted = False break if not sorted: return keypars.argsort().tolist() @@ -142,14 +158,14 @@ def match_at(self, x, y): orig = self.copy() try: - scale = y/height + scale = y / height # First attempt at scaling peaks. Record which peaks, if any, # were not scaled in case a second attempt is required. scaled = [] all_scaled = True any_scaled = False - fixed_height = 0. + fixed_height = 0.0 for peak in self: scaled.append(peak.scale_at(x, scale)) all_scaled = all_scaled and scaled[-1] @@ -161,13 +177,13 @@ def match_at(self, x, y): if not all_scaled and fixed_height < y and fixed_height < height: self[:] = orig[:] any_scaled = False - scale = (y - fixed_height)/(height - fixed_height) + scale = (y - fixed_height) / (height - fixed_height) for peak, s in (self, scaled): if s: # "or" is short-circuited, so scale_at() must be first # to guarantee it is called. any_scaled = peak.scale_at(x, scale) or any_scaled - except Exception, e: + except Exception as e: logger.debug("An exception prevented matching -- %s", e) self[:] = orig[:] return False @@ -175,13 +191,15 @@ def match_at(self, x, y): def sort(self, key="position"): """Sort peaks in order specified by key.""" - keypars=np.array([p[key] for p in self]) + keypars = np.array([p[key] for p in self]) order = keypars.argsort() self[:] = [self[idx] for idx in order] return + # End of class Peaks + class Peak(ModelPart): """Represents a single peak associated with a PeakFunction subclass.""" @@ -225,7 +243,7 @@ def scale_at(self, x, scale): try: adj_pars = self._owner.scale_at(self.pars, x, scale) - except SrMiseScalingError, err: + except SrMiseScalingError as err: logger.debug("Cannot scale peak:", err) return False @@ -256,10 +274,10 @@ def factory(peakstr, ownerlist): try: pdict[l[0]] = eval(l[1]) except Exception: - emsg = ("Invalid parameter: %s" %d) + emsg = "Invalid parameter: %s" % d raise SrMiseDataFormatError(emsg) else: - emsg = ("Invalid parameter: %s" %d) + emsg = "Invalid parameter: %s" % d raise SrMiseDataFormatError(emsg) # Correctly initialize the base function, if one exists. @@ -271,10 +289,11 @@ def factory(peakstr, ownerlist): return Peak(**pdict) + # End of class Peak # simple test code -if __name__ == '__main__': +if __name__ == "__main__": import matplotlib.pyplot as plt from numpy.random import randn @@ -283,26 +302,26 @@ def factory(peakstr, ownerlist): from diffpy.srmise.modelevaluators import AICc from diffpy.srmise.peaks import GaussianOverR - res = .01 - r = np.arange(2,4,res) - err = np.ones(len(r)) #default unknown errors - pf = GaussianOverR(.7) + res = 0.01 + r = np.arange(2, 4, res) + err = np.ones(len(r)) # default unknown errors + pf = GaussianOverR(0.7) evaluator = AICc() - pars = [[3, .2, 10], [3.5, .2, 10]] + pars = [[3, 0.2, 10], [3.5, 0.2, 10]] ideal_peaks = Peaks([pf.actualize(p, "pwa") for p in pars]) - y = ideal_peaks.value(r) + .1*randn(len(r)) + y = ideal_peaks.value(r) + 0.1 * randn(len(r)) - guesspars = [[2.7, .15, 5], [3.7, .3, 5]] + guesspars = [[2.7, 0.15, 5], [3.7, 0.3, 5]] guess_peaks = Peaks([pf.actualize(p, "pwa") for p in guesspars]) cluster = ModelCluster(guess_peaks, r, y, err, None, AICc, [pf]) qual1 = cluster.quality() - print qual1.stat + print(qual1.stat) cluster.fit() yfit = cluster.calc() qual2 = cluster.quality() - print qual2.stat + print(qual2.stat) plt.figure(1) plt.plot(r, y, r, yfit) diff --git a/diffpy/srmise/peaks/gaussian.py b/diffpy/srmise/peaks/gaussian.py index 3eae227..3913b6c 100644 --- a/diffpy/srmise/peaks/gaussian.py +++ b/diffpy/srmise/peaks/gaussian.py @@ -22,20 +22,21 @@ logger = logging.getLogger("diffpy.srmise") -class Gaussian (PeakFunction): + +class Gaussian(PeakFunction): """Methods for evaluation and parameter estimation of width-limited Gaussian. - Allowed formats are - internal: [position, parameterized width-squared, area] - pwa: [position, full width at half maximum, area] - mu_sigma_area: [mu, sigma, area] + Allowed formats are + internal: [position, parameterized width-squared, area] + pwa: [position, full width at half maximum, area] + mu_sigma_area: [mu, sigma, area] - The internal parameterization is unconstrained, but are interpreted - so that the width is between 0 and a user-provided maximum full width - at half maximum, and the area is positive. + The internal parameterization is unconstrained, but are interpreted + so that the width is between 0 and a user-provided maximum full width + at half maximum, and the area is positive. - Note that all full width at half maximum values are for the - corresponding Gaussian. + Note that all full width at half maximum values are for the + corresponding Gaussian. """ # Possibly implement cutoff later, but low priority. @@ -46,9 +47,9 @@ class Gaussian (PeakFunction): def __init__(self, maxwidth, Cache=None): """maxwidth defined as full width at half maximum for the corresponding Gaussian, which is physically relevant.""" - parameterdict={'position':0,'width':1,'area':2} - formats=['internal','pwa','mu_sigma_area'] - default_formats={'default_input':'internal', 'default_output':'pwa'} + parameterdict = {"position": 0, "width": 1, "area": 2} + formats = ["internal", "pwa", "mu_sigma_area"] + default_formats = {"default_input": "internal", "default_output": "pwa"} metadict = {} metadict["maxwidth"] = (maxwidth, repr) PeakFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) @@ -59,16 +60,16 @@ def __init__(self, maxwidth, Cache=None): self.maxwidth = maxwidth ### Useful constants ### - #c1 and c2 help with function values - self.c1 = self.maxwidth*np.sqrt(np.pi/(8*np.log(2))) - self.c2 = self.maxwidth**2/(8*np.log(2)) + # c1 and c2 help with function values + self.c1 = self.maxwidth * np.sqrt(np.pi / (8 * np.log(2))) + self.c2 = self.maxwidth**2 / (8 * np.log(2)) - #c3 and c4 help with parameter estimation - self.c3 = .5*np.sqrt(np.pi/np.log(2)) - self.c4 = np.pi/(self.maxwidth*2) + # c3 and c4 help with parameter estimation + self.c3 = 0.5 * np.sqrt(np.pi / np.log(2)) + self.c4 = np.pi / (self.maxwidth * 2) - #convert sigma to fwhm: fwhm = 2 sqrt(2 log 2) sigma - self.sigma2fwhm = 2*np.sqrt(2*np.log(2)) + # convert sigma to fwhm: fwhm = 2 sqrt(2 log 2) sigma + self.sigma2fwhm = 2 * np.sqrt(2 * np.log(2)) return @@ -102,39 +103,49 @@ def estimate_parameters(self, r, y): raise SrMiseEstimationError(emsg) #### Estimation #### - guesspars = np.array([0., 0., 0.], dtype=float) + guesspars = np.array([0.0, 0.0, 0.0], dtype=float) min_y = use_y.min() max_y = use_y.max() center = use_r[use_y.argmax()] if min_y != max_y: - weights = (use_y-min_y)**2 - guesspars[0] = np.sum(use_r*weights)/sum(weights) + weights = (use_y - min_y) ** 2 + guesspars[0] = np.sum(use_r * weights) / sum(weights) # guesspars[0] = center if use_y[0] < max_y: - sigma_left = np.sqrt(-.5*(use_r[0]-guesspars[0])**2/np.log(use_y[0]/max_y)) + sigma_left = np.sqrt(-0.5 * (use_r[0] - guesspars[0]) ** 2 / np.log(use_y[0] / max_y)) else: - sigma_left = np.sqrt(-.5*np.mean(np.abs(np.array([use_r[0]-guesspars[0], use_r[-1]-guesspars[0]])))**2/np.log(min_y/max_y)) - if use_y[-1] self.maxwidth: - #account for width-limit - guesspars[2] = self.c3*max_y*self.maxwidth - guesspars[1] = np.pi/2 #parameterized in terms of sin + # account for width-limit + guesspars[2] = self.c3 * max_y * self.maxwidth + guesspars[1] = np.pi / 2 # parameterized in terms of sin else: - guesspars[2] = self.c3*max_y*guesspars[1] - guesspars[1] = np.arcsin(2*guesspars[1]**2/self.maxwidth**2-1.) #parameterized in terms of sin + guesspars[2] = self.c3 * max_y * guesspars[1] + guesspars[1] = np.arcsin( + 2 * guesspars[1] ** 2 / self.maxwidth**2 - 1.0 + ) # parameterized in terms of sin return guesspars @@ -149,17 +160,17 @@ def scale_at(self, pars, x, scale): x: (float) Position of the border scale: (float > 0) Size of scaling at x.""" if scale <= 0: - emsg = ''.join(["Cannot scale by ", str(scale), "."]) + emsg = "".join(["Cannot scale by ", str(scale), "."]) raise SrMiseScalingError(emsg) if scale == 1: return pars else: - ratio = 1/scale # Ugly: Equations orig. solved in terms of ratio + ratio = 1 / scale # Ugly: Equations orig. solved in terms of ratio tpars = self.transform_parameters(pars, in_format="internal", out_format="mu_sigma_area") - #solves 1. f(rmax;mu1,sigma1,area1)=f(rmax;mu2,sigma2,area2) + # solves 1. f(rmax;mu1,sigma1,area1)=f(rmax;mu2,sigma2,area2) # 2. f(x;mu1,sigma1,area1)=ratio*f(x;mu1,sigma2,area2) # 3. mu1=mu2=rmax (the maximum of a Gaussian occurs at r=mu) # for mu2, sigma2, area2 (with appropriate unit conversions to fwhm at the end). @@ -168,51 +179,57 @@ def scale_at(self, pars, x, scale): # the semi-nasty algebra reduces to something nice mu2 = mu1 - area2 = np.sqrt(area1**2/(2*np.log(ratio)*sigma1**2/(x-mu1)**2+1)) - sigma2 = sigma1*area2/area1 + area2 = np.sqrt(area1**2 / (2 * np.log(ratio) * sigma1**2 / (x - mu1) ** 2 + 1)) + sigma2 = sigma1 * area2 / area1 tpars[0] = mu2 tpars[1] = sigma2 tpars[2] = area2 try: tpars = self.transform_parameters(tpars, in_format="mu_sigma_area", out_format="internal") - except SrMiseTransformationError, err: + except SrMiseTransformationError as err: raise SrMiseScalingError(str(err)) return tpars def _jacobianraw(self, pars, r, free): """Return Jacobian of width-limited Gaussian. - pars: Sequence of parameters for a single width-limited Gaussian - pars[0]=peak position - pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. - =tan(pi/2*fwhm/maxwidth) - pars[2]=multiplicative constant a, equivalent to peak area - r: sequence or scalar over which pars is evaluated - free: sequence of booleans which determines which derivatives are - needed. True for evaluation, False for no evaluation. + pars: Sequence of parameters for a single width-limited Gaussian + pars[0]=peak position + pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. + =tan(pi/2*fwhm/maxwidth) + pars[2]=multiplicative constant a, equivalent to peak area + r: sequence or scalar over which pars is evaluated + free: sequence of booleans which determines which derivatives are + needed. True for evaluation, False for no evaluation. """ - jacobian=[None, None, None] + jacobian = [None, None, None] if (free == False).sum() == self.npars: return jacobian - #Optimization - sin_p = np.sin(pars[1]) + 1. - p0minusr = pars[0]-r - exp_p = np.exp(-(p0minusr)**2/(self.c2*sin_p))/(self.c1*np.sqrt(sin_p)) + # Optimization + sin_p = np.sin(pars[1]) + 1.0 + p0minusr = pars[0] - r + exp_p = np.exp(-((p0minusr) ** 2) / (self.c2 * sin_p)) / (self.c1 * np.sqrt(sin_p)) if free[0]: - #derivative with respect to peak position - jacobian[0] = -2.*exp_p*p0minusr*np.abs(pars[2])/(self.c2*sin_p) + # derivative with respect to peak position + jacobian[0] = -2.0 * exp_p * p0minusr * np.abs(pars[2]) / (self.c2 * sin_p) if free[1]: - #derivative with respect to reparameterized peak width - jacobian[1] = -exp_p*np.abs(pars[2])*np.cos(pars[1])*(self.c2*sin_p-2*p0minusr**2)/(2.*self.c2*sin_p**2) + # derivative with respect to reparameterized peak width + jacobian[1] = ( + -exp_p + * np.abs(pars[2]) + * np.cos(pars[1]) + * (self.c2 * sin_p - 2 * p0minusr**2) + / (2.0 * self.c2 * sin_p**2) + ) if free[2]: - #derivative with respect to peak area - #abs'(x)=sign(x) for real x except at 0 where it is undetermined. Since any real peak necessarily has - #non-zero area and the function is paramaterized such that values of either sign represent equivalent - #curves I arbitrarily choose positive sign for pars[2]==0 in order to push the system back into a realistic - #parameter space should this improbable scenario occur. + # derivative with respect to peak area + # abs'(x)=sign(x) for real x except at 0 where it is undetermined. Since any real peak necessarily has + # non-zero area and the function is paramaterized such that values of either sign represent equivalent + # curves I arbitrarily choose positive sign for pars[2]==0 in order to push the system back into a realistic + # parameter space should this improbable scenario occur. # jacobian[2] = sign(pars[2])*exp_p if pars[2] >= 0: jacobian[2] = exp_p @@ -223,18 +240,18 @@ def _jacobianraw(self, pars, r, free): def _transform_parametersraw(self, pars, in_format, out_format): """Convert parameter values from in_format to out_format. - Also restores parameters to a preferred range if it permits multiple - values that correspond to the same physical result. - - Parameters - pars: Sequence of parameters - in_format: A format defined for this class - out_format: A format defined for this class + Also restores parameters to a preferred range if it permits multiple + values that correspond to the same physical result. - Defined Formats - internal: [position, parameterized width-squared, area] - pwa: [position, full width at half maximum, area] - mu_sigma_area: [mu, sigma, area] + Parameters + pars: Sequence of parameters + in_format: A format defined for this class + out_format: A format defined for this class + + Defined Formats + internal: [position, parameterized width-squared, area] + pwa: [position, full width at half maximum, area] + mu_sigma_area: [mu, sigma, area] """ temp = np.array(pars) @@ -248,51 +265,58 @@ def _transform_parametersraw(self, pars, in_format, out_format): if in_format == "internal": # put the parameter for width in the "physical" quadrant [-pi/2,pi/2], # where .5*(sin(p)+1) covers fwhm = [0, maxwidth] - n = np.floor((temp[1]+np.pi/2)/np.pi) + n = np.floor((temp[1] + np.pi / 2) / np.pi) if np.mod(n, 2) == 0: - temp[1] = temp[1] - np.pi*n + temp[1] = temp[1] - np.pi * n else: - temp[1] = np.pi*n - temp[1] - temp[2] = np.abs(temp[2]) # map negative area to equivalent positive one + temp[1] = np.pi * n - temp[1] + temp[2] = np.abs(temp[2]) # map negative area to equivalent positive one elif in_format == "pwa": if temp[1] > self.maxwidth: - emsg = "Width %s (FWHM) greater than maximum allowed width %s" %(temp[1], self.maxwidth) + emsg = "Width %s (FWHM) greater than maximum allowed width %s" % ( + temp[1], + self.maxwidth, + ) raise SrMiseTransformationError(emsg) - temp[1] = np.arcsin(2.*temp[1]**2/self.maxwidth**2-1.) + temp[1] = np.arcsin(2.0 * temp[1] ** 2 / self.maxwidth**2 - 1.0) elif in_format == "mu_sigma_area": - fwhm = temp[1]*self.sigma2fwhm + fwhm = temp[1] * self.sigma2fwhm if fwhm > self.maxwidth: - emsg = "Width %s (FWHM) greater than maximum allowed width %s" %(fwhm, self.maxwidth) + emsg = "Width %s (FWHM) greater than maximum allowed width %s" % ( + fwhm, + self.maxwidth, + ) raise SrMiseTransformationError(emsg) - temp[1] = np.arcsin(2.*fwhm**2/self.maxwidth**2-1.) + temp[1] = np.arcsin(2.0 * fwhm**2 / self.maxwidth**2 - 1.0) else: - raise ValueError("Argument 'in_format' must be one of %s." \ - % self.parformats) + raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) # Convert to specified output format from "internal" format. if out_format == "internal": pass elif out_format == "pwa": - temp[1] = np.sqrt(.5*(np.sin(temp[1])+1.)*self.maxwidth**2) + temp[1] = np.sqrt(0.5 * (np.sin(temp[1]) + 1.0) * self.maxwidth**2) elif out_format == "mu_sigma_area": - temp[1] = np.sqrt(.5*(np.sin(temp[1])+1.)*self.maxwidth**2)/self.sigma2fwhm + temp[1] = np.sqrt(0.5 * (np.sin(temp[1]) + 1.0) * self.maxwidth**2) / self.sigma2fwhm else: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) return temp def _valueraw(self, pars, r): """Return value of width-limited Gaussian for the given parameters and r values. - pars: Sequence of parameters for a single width-limited Gaussian - pars[0]=peak position - pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. - =tan(pi/2*fwhm/maxwidth) - pars[2]=multiplicative constant a, equivalent to peak area - r: sequence or scalar over which pars is evaluated + pars: Sequence of parameters for a single width-limited Gaussian + pars[0]=peak position + pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. + =tan(pi/2*fwhm/maxwidth) + pars[2]=multiplicative constant a, equivalent to peak area + r: sequence or scalar over which pars is evaluated """ - return np.abs(pars[2])/(self.c1*np.sqrt(np.sin(pars[1])+1.))* \ - np.exp(-(r-pars[0])**2/(self.c2*(np.sin(pars[1])+1.))) + return ( + np.abs(pars[2]) + / (self.c1 * np.sqrt(np.sin(pars[1]) + 1.0)) + * np.exp(-((r - pars[0]) ** 2) / (self.c2 * (np.sin(pars[1]) + 1.0))) + ) def getmodule(self): return __name__ @@ -312,10 +336,11 @@ def max(self, pars): ymax = self._valueraw(pars, rmax) return np.array([rmax, ymax]) -#end of class Gaussian + +# end of class Gaussian # simple test code -if __name__ == '__main__': +if __name__ == "__main__": import matplotlib.pyplot as plt from numpy.random import randn @@ -324,26 +349,26 @@ def max(self, pars): from diffpy.srmise.modelevaluators import AICc from diffpy.srmise.peaks import Peaks - res = .01 - r = np.arange(2,4,res) - err = np.ones(len(r)) # default unknown errors - pf = Gaussian(.7) + res = 0.01 + r = np.arange(2, 4, res) + err = np.ones(len(r)) # default unknown errors + pf = Gaussian(0.7) evaluator = AICc() - pars = [[3, .2, 10], [3.5, .2, 10]] + pars = [[3, 0.2, 10], [3.5, 0.2, 10]] ideal_peaks = Peaks([pf.createpeak(p, "pwa") for p in pars]) - y = ideal_peaks.value(r) + .1*randn(len(r)) + y = ideal_peaks.value(r) + 0.1 * randn(len(r)) - guesspars = [[2.7, .15, 5], [3.7, .3, 5]] + guesspars = [[2.7, 0.15, 5], [3.7, 0.3, 5]] guess_peaks = Peaks([pf.createpeak(p, "pwa") for p in guesspars]) cluster = ModelCluster(guess_peaks, r, y, err, None, AICc, [pf]) qual1 = cluster.quality() - print qual1.stat + print(qual1.stat) cluster.fit() yfit = cluster.calc() qual2 = cluster.quality() - print qual2.stat + print(qual2.stat) plt.figure(1) plt.plot(r, y, r, yfit) diff --git a/diffpy/srmise/peaks/gaussianoverr.py b/diffpy/srmise/peaks/gaussianoverr.py index f699d9f..d11467d 100644 --- a/diffpy/srmise/peaks/gaussianoverr.py +++ b/diffpy/srmise/peaks/gaussianoverr.py @@ -22,20 +22,21 @@ logger = logging.getLogger("diffpy.srmise") -class GaussianOverR (PeakFunction): + +class GaussianOverR(PeakFunction): """Methods for evaluation and parameter estimation of width-limited Gaussian/r. - Allowed formats are - internal: [position, parameterized width-squared, area] - pwa: [position, full width at half maximum, area] - mu_sigma_area: [mu, sigma, area] + Allowed formats are + internal: [position, parameterized width-squared, area] + pwa: [position, full width at half maximum, area] + mu_sigma_area: [mu, sigma, area] - The internal parameterization is unconstrained, but are interpreted - so that the width is between 0 and a user-provided maximum full width - at half maximum, and the area is positive. + The internal parameterization is unconstrained, but are interpreted + so that the width is between 0 and a user-provided maximum full width + at half maximum, and the area is positive. - Note that all full width at half maximum values are for the - corresponding Gaussian. + Note that all full width at half maximum values are for the + corresponding Gaussian. """ # Possibly implement cutoff later, but low priority. @@ -46,9 +47,9 @@ class GaussianOverR (PeakFunction): def __init__(self, maxwidth, Cache=None): """maxwidth defined as full width at half maximum for the corresponding Gaussian, which is physically relevant.""" - parameterdict={'position':0,'width':1,'area':2} - formats=['internal','pwa','mu_sigma_area'] - default_formats={'default_input':'internal', 'default_output':'pwa'} + parameterdict = {"position": 0, "width": 1, "area": 2} + formats = ["internal", "pwa", "mu_sigma_area"] + default_formats = {"default_input": "internal", "default_output": "pwa"} metadict = {} metadict["maxwidth"] = (maxwidth, repr) PeakFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) @@ -59,16 +60,16 @@ def __init__(self, maxwidth, Cache=None): self.maxwidth = maxwidth ### Useful constants ### - #c1 and c2 help with function values - self.c1 = self.maxwidth*np.sqrt(np.pi/(8*np.log(2))) - self.c2 = self.maxwidth**2/(8*np.log(2)) + # c1 and c2 help with function values + self.c1 = self.maxwidth * np.sqrt(np.pi / (8 * np.log(2))) + self.c2 = self.maxwidth**2 / (8 * np.log(2)) - #c3 and c4 help with parameter estimation - self.c3 = .5*np.sqrt(np.pi/np.log(2)) - self.c4 = np.pi/(self.maxwidth*2) + # c3 and c4 help with parameter estimation + self.c3 = 0.5 * np.sqrt(np.pi / np.log(2)) + self.c4 = np.pi / (self.maxwidth * 2) - #convert sigma to fwhm: fwhm = 2 sqrt(2 log 2) sigma - self.sigma2fwhm = 2*np.sqrt(2*np.log(2)) + # convert sigma to fwhm: fwhm = 2 sqrt(2 log 2) sigma + self.sigma2fwhm = 2 * np.sqrt(2 * np.log(2)) return @@ -102,39 +103,49 @@ def estimate_parameters(self, r, y): raise SrMiseEstimationError(emsg) #### Estimation #### - guesspars = np.array([0., 0., 0.], dtype=float) + guesspars = np.array([0.0, 0.0, 0.0], dtype=float) min_y = use_y.min() max_y = use_y.max() center = use_r[use_y.argmax()] if min_y != max_y: - weights = (use_y-min_y)**2 - guesspars[0] = np.sum(use_r*weights)/sum(weights) + weights = (use_y - min_y) ** 2 + guesspars[0] = np.sum(use_r * weights) / sum(weights) # guesspars[0] = center if use_y[0] < max_y: - sigma_left = np.sqrt(-.5*(use_r[0]-guesspars[0])**2/np.log(use_y[0]/max_y)) + sigma_left = np.sqrt(-0.5 * (use_r[0] - guesspars[0]) ** 2 / np.log(use_y[0] / max_y)) else: - sigma_left = np.sqrt(-.5*np.mean(np.abs(np.array([use_r[0]-guesspars[0], use_r[-1]-guesspars[0]])))**2/np.log(min_y/max_y)) - if use_y[-1] self.maxwidth: - #account for width-limit - guesspars[2] = self.c3*max_y*guesspars[0]*self.maxwidth - guesspars[1] = np.pi/2 #parameterized in terms of sin + # account for width-limit + guesspars[2] = self.c3 * max_y * guesspars[0] * self.maxwidth + guesspars[1] = np.pi / 2 # parameterized in terms of sin else: - guesspars[2] = self.c3*max_y*guesspars[0]*guesspars[1] - guesspars[1] = np.arcsin(2*guesspars[1]**2/self.maxwidth**2-1.) #parameterized in terms of sin + guesspars[2] = self.c3 * max_y * guesspars[0] * guesspars[1] + guesspars[1] = np.arcsin( + 2 * guesspars[1] ** 2 / self.maxwidth**2 - 1.0 + ) # parameterized in terms of sin return guesspars @@ -149,17 +160,17 @@ def scale_at(self, pars, x, scale): x: (float) Position of the border scale: (float > 0) Size of scaling at x.""" if scale <= 0: - emsg = ''.join(["Cannot scale by ", str(scale), "."]) + emsg = "".join(["Cannot scale by ", str(scale), "."]) raise SrMiseScalingError(emsg) if scale == 1: return pars else: - ratio = 1/scale # Ugly: Equations orig. solved in terms of ratio + ratio = 1 / scale # Ugly: Equations orig. solved in terms of ratio tpars = self.transform_parameters(pars, in_format="internal", out_format="mu_sigma_area") - #solves 1. f(rmax;mu1,sigma1,area1)=f(rmax;mu2,sigma2,area2) + # solves 1. f(rmax;mu1,sigma1,area1)=f(rmax;mu2,sigma2,area2) # 2. f(x;mu1,sigma1,area1)=ratio*f(x;mu1,sigma2,area2) # 3. 1/2*(mu1+sqrt(mu1^2+sigma1^2))=1/2*(mu2+sqrt(mu2^2+sigma2^2))=rmax # for mu2, sigma2, area2 (with appropriate unit conversions to fwhm at the end). @@ -169,59 +180,70 @@ def scale_at(self, pars, x, scale): # position of the peak maximum try: rmax = self.max(pars)[0] - except ValueError, err: + except ValueError as err: raise SrMiseScalingError(str(err)) # lhs of eqn1/eqn2 multiplied by ratio. Then take the log. - log_ratio_prime = np.log(ratio)+(x-rmax)*(x-2*mu1+rmax)/(2*sigma1**2) + log_ratio_prime = np.log(ratio) + (x - rmax) * (x - 2 * mu1 + rmax) / (2 * sigma1**2) # the semi-nasty algebra reduces to something nice - sigma2 = np.sqrt(.5*rmax*(x-rmax)**2/(x-rmax+rmax*log_ratio_prime)) - mu2 = (sigma2**2+rmax**2)/rmax - area2 = area1*(sigma2/sigma1)*np.exp(-(rmax-mu1)**2/(2*sigma1**2))/np.exp(-(rmax-mu2)**2/(2*sigma2**2)) + sigma2 = np.sqrt(0.5 * rmax * (x - rmax) ** 2 / (x - rmax + rmax * log_ratio_prime)) + mu2 = (sigma2**2 + rmax**2) / rmax + area2 = ( + area1 + * (sigma2 / sigma1) + * np.exp(-((rmax - mu1) ** 2) / (2 * sigma1**2)) + / np.exp(-((rmax - mu2) ** 2) / (2 * sigma2**2)) + ) tpars[0] = mu2 tpars[1] = sigma2 tpars[2] = area2 try: tpars = self.transform_parameters(tpars, in_format="mu_sigma_area", out_format="internal") - except SrMiseTransformationError, err: + except SrMiseTransformationError as err: raise SrMiseScalingError(str(err)) return tpars def _jacobianraw(self, pars, r, free): """Return Jacobian of width-limited Gaussian/r. - pars: Sequence of parameters for a single width-limited Gaussian - pars[0]=peak position - pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. - =tan(pi/2*fwhm/maxwidth) - pars[2]=multiplicative constant a, equivalent to peak area - r: sequence or scalar over which pars is evaluated - free: sequence of booleans which determines which derivatives are - needed. True for evaluation, False for no evaluation. + pars: Sequence of parameters for a single width-limited Gaussian + pars[0]=peak position + pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. + =tan(pi/2*fwhm/maxwidth) + pars[2]=multiplicative constant a, equivalent to peak area + r: sequence or scalar over which pars is evaluated + free: sequence of booleans which determines which derivatives are + needed. True for evaluation, False for no evaluation. """ - jacobian=[None, None, None] + jacobian = [None, None, None] if (free == False).sum() == self.npars: return jacobian - #Optimization - sin_p = np.sin(pars[1]) + 1. - p0minusr = pars[0]-r - exp_p = np.exp(-(p0minusr)**2/(self.c2*sin_p))/(np.abs(r)*self.c1*np.sqrt(sin_p)) + # Optimization + sin_p = np.sin(pars[1]) + 1.0 + p0minusr = pars[0] - r + exp_p = np.exp(-((p0minusr) ** 2) / (self.c2 * sin_p)) / (np.abs(r) * self.c1 * np.sqrt(sin_p)) if free[0]: - #derivative with respect to peak position - jacobian[0] = -2.*exp_p*p0minusr*np.abs(pars[2])/(self.c2*sin_p) + # derivative with respect to peak position + jacobian[0] = -2.0 * exp_p * p0minusr * np.abs(pars[2]) / (self.c2 * sin_p) if free[1]: - #derivative with respect to reparameterized peak width - jacobian[1] = -exp_p*np.abs(pars[2])*np.cos(pars[1])*(self.c2*sin_p-2*p0minusr**2)/(2.*self.c2*sin_p**2) + # derivative with respect to reparameterized peak width + jacobian[1] = ( + -exp_p + * np.abs(pars[2]) + * np.cos(pars[1]) + * (self.c2 * sin_p - 2 * p0minusr**2) + / (2.0 * self.c2 * sin_p**2) + ) if free[2]: - #derivative with respect to peak area - #abs'(x)=sign(x) for real x except at 0 where it is undetermined. Since any real peak necessarily has - #non-zero area and the function is paramaterized such that values of either sign represent equivalent - #curves I arbitrarily choose positive sign for pars[2]==0 in order to push the system back into a realistic - #parameter space should this improbable scenario occur. + # derivative with respect to peak area + # abs'(x)=sign(x) for real x except at 0 where it is undetermined. Since any real peak necessarily has + # non-zero area and the function is paramaterized such that values of either sign represent equivalent + # curves I arbitrarily choose positive sign for pars[2]==0 in order to push the system back into a realistic + # parameter space should this improbable scenario occur. # jacobian[2] = sign(pars[2])*exp_p if pars[2] >= 0: jacobian[2] = exp_p @@ -232,15 +254,15 @@ def _jacobianraw(self, pars, r, free): def _transform_derivativesraw(self, pars, in_format, out_format): """Return gradient matrix for the pars converted from in_format to out_format. - Parameters - pars: Sequence of parameters - in_format: A format defined for this class - out_format: A format defined for this class - - Defined Formats - internal: [position, parameterized width-squared, area] - pwa: [position, full width at half maximum, area] - mu_sigma_area: [mu, sigma, area] + Parameters + pars: Sequence of parameters + in_format: A format defined for this class + out_format: A format defined for this class + + Defined Formats + internal: [position, parameterized width-squared, area] + pwa: [position, full width at half maximum, area] + mu_sigma_area: [mu, sigma, area] """ # With these three formats only the width-related parameter changes. # Therefore the gradient matrix is the identity matrix with the possible @@ -252,49 +274,50 @@ def _transform_derivativesraw(self, pars, in_format, out_format): if in_format == "internal": if out_format == "pwa": - g[1,1] = self.maxwidth/(2*np.sqrt(2))*np.cos(pars[1])/np.sqrt(1+np.sin(pars[1])) + g[1, 1] = self.maxwidth / (2 * np.sqrt(2)) * np.cos(pars[1]) / np.sqrt(1 + np.sin(pars[1])) elif out_format == "mu_sigma_area": - g[1,1] = self.maxwidth/(2*np.sqrt(2)*self.sigma2fwhm)*np.cos(pars[1])/np.sqrt(1+np.sin(pars[1])) + g[1, 1] = ( + self.maxwidth + / (2 * np.sqrt(2) * self.sigma2fwhm) + * np.cos(pars[1]) + / np.sqrt(1 + np.sin(pars[1])) + ) else: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) elif in_format == "pwa": if out_format == "internal": - g[1,1] = 2/np.sqrt(self.maxwidth**2-pars[1]**2) + g[1, 1] = 2 / np.sqrt(self.maxwidth**2 - pars[1] ** 2) elif out_format == "mu_sigma_area": - g[1,1] = 1/self.sigma2fwhm + g[1, 1] = 1 / self.sigma2fwhm else: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) elif in_format == "mu_sigma_area": if out_format == "internal": - g[1,1] = 2*self.sigma2fwhm/np.sqrt(self.maxwidth**2-(self.sigma2fwhm*pars[1])**2) + g[1, 1] = 2 * self.sigma2fwhm / np.sqrt(self.maxwidth**2 - (self.sigma2fwhm * pars[1]) ** 2) elif out_format == "pwa": - g[1,1] = self.sigma2fwhm + g[1, 1] = self.sigma2fwhm else: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) else: - raise ValueError("Argument 'in_format' must be one of %s." \ - % self.parformats) + raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) return g def _transform_parametersraw(self, pars, in_format, out_format): """Convert parameter values from in_format to out_format. - Also restores parameters to a preferred range if it permits multiple - values that correspond to the same physical result. + Also restores parameters to a preferred range if it permits multiple + values that correspond to the same physical result. - Parameters - pars: Sequence of parameters - in_format: A format defined for this class - out_format: A format defined for this class - - Defined Formats - internal: [position, parameterized width-squared, area] - pwa: [position, full width at half maximum, area] - mu_sigma_area: [mu, sigma, area] + Parameters + pars: Sequence of parameters + in_format: A format defined for this class + out_format: A format defined for this class + + Defined Formats + internal: [position, parameterized width-squared, area] + pwa: [position, full width at half maximum, area] + mu_sigma_area: [mu, sigma, area] """ temp = np.array(pars) @@ -308,51 +331,58 @@ def _transform_parametersraw(self, pars, in_format, out_format): if in_format == "internal": # put the parameter for width in the "physical" quadrant [-pi/2,pi/2], # where .5*(sin(p)+1) covers fwhm = [0, maxwidth] - n = np.floor((temp[1]+np.pi/2)/np.pi) + n = np.floor((temp[1] + np.pi / 2) / np.pi) if np.mod(n, 2) == 0: - temp[1] = temp[1] - np.pi*n + temp[1] = temp[1] - np.pi * n else: - temp[1] = np.pi*n - temp[1] - temp[2] = np.abs(temp[2]) # map negative area to equivalent positive one + temp[1] = np.pi * n - temp[1] + temp[2] = np.abs(temp[2]) # map negative area to equivalent positive one elif in_format == "pwa": if temp[1] > self.maxwidth: - emsg = "Width %s (FWHM) greater than maximum allowed width %s" %(temp[1], self.maxwidth) + emsg = "Width %s (FWHM) greater than maximum allowed width %s" % ( + temp[1], + self.maxwidth, + ) raise SrMiseTransformationError(emsg) - temp[1] = np.arcsin(2.*temp[1]**2/self.maxwidth**2-1.) + temp[1] = np.arcsin(2.0 * temp[1] ** 2 / self.maxwidth**2 - 1.0) elif in_format == "mu_sigma_area": - fwhm = temp[1]*self.sigma2fwhm + fwhm = temp[1] * self.sigma2fwhm if fwhm > self.maxwidth: - emsg = "Width %s (FWHM) greater than maximum allowed width %s" %(fwhm, self.maxwidth) + emsg = "Width %s (FWHM) greater than maximum allowed width %s" % ( + fwhm, + self.maxwidth, + ) raise SrMiseTransformationError(emsg) - temp[1] = np.arcsin(2.*fwhm**2/self.maxwidth**2-1.) + temp[1] = np.arcsin(2.0 * fwhm**2 / self.maxwidth**2 - 1.0) else: - raise ValueError("Argument 'in_format' must be one of %s." \ - % self.parformats) + raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) # Convert to specified output format from "internal" format. if out_format == "internal": pass elif out_format == "pwa": - temp[1] = np.sqrt(.5*(np.sin(temp[1])+1.)*self.maxwidth**2) + temp[1] = np.sqrt(0.5 * (np.sin(temp[1]) + 1.0) * self.maxwidth**2) elif out_format == "mu_sigma_area": - temp[1] = np.sqrt(.5*(np.sin(temp[1])+1.)*self.maxwidth**2)/self.sigma2fwhm + temp[1] = np.sqrt(0.5 * (np.sin(temp[1]) + 1.0) * self.maxwidth**2) / self.sigma2fwhm else: - raise ValueError("Argument 'out_format' must be one of %s." \ - % self.parformats) + raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) return temp def _valueraw(self, pars, r): """Return value of width-limited Gaussian/r for the given parameters and r values. - pars: Sequence of parameters for a single width-limited Gaussian - pars[0]=peak position - pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. - =tan(pi/2*fwhm/maxwidth) - pars[2]=multiplicative constant a, equivalent to peak area - r: sequence or scalar over which pars is evaluated + pars: Sequence of parameters for a single width-limited Gaussian + pars[0]=peak position + pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. + =tan(pi/2*fwhm/maxwidth) + pars[2]=multiplicative constant a, equivalent to peak area + r: sequence or scalar over which pars is evaluated """ - return np.abs(pars[2])/(np.abs(r)*self.c1*np.sqrt(np.sin(pars[1])+1.))* \ - np.exp(-(r-pars[0])**2/(self.c2*(np.sin(pars[1])+1.))) + return ( + np.abs(pars[2]) + / (np.abs(r) * self.c1 * np.sqrt(np.sin(pars[1]) + 1.0)) + * np.exp(-((r - pars[0]) ** 2) / (self.c2 * (np.sin(pars[1]) + 1.0))) + ) def getmodule(self): return __name__ @@ -371,18 +401,19 @@ def max(self, pars): # The Gaussian/r only has a local maximum under this condition. # Physically realistic peaks will always meet this condition, but # trying to fit a signal down to r=0 could conceivably lead to issues. - if tpars[0]**2 <= 4*tpars[1]**2: - emsg = ''.join(["No local maximum with parameters\n", str(pars)]) + if tpars[0] ** 2 <= 4 * tpars[1] ** 2: + emsg = "".join(["No local maximum with parameters\n", str(pars)]) raise ValueError(emsg) - rmax = .5*(tpars[0]+np.sqrt(tpars[0]**2-4*tpars[1]**2)) + rmax = 0.5 * (tpars[0] + np.sqrt(tpars[0] ** 2 - 4 * tpars[1] ** 2)) ymax = self._valueraw(pars, rmax) return np.array([rmax, ymax]) -#end of class GaussianOverR + +# end of class GaussianOverR # simple test code -if __name__ == '__main__': +if __name__ == "__main__": import matplotlib.pyplot as plt from numpy.random import randn @@ -391,26 +422,26 @@ def max(self, pars): from diffpy.srmise.modelevaluators import AICc from diffpy.srmise.peaks import Peaks - res = .01 - r = np.arange(2,4,res) - err = np.ones(len(r)) # default unknown errors - pf = GaussianOverR(.7) + res = 0.01 + r = np.arange(2, 4, res) + err = np.ones(len(r)) # default unknown errors + pf = GaussianOverR(0.7) evaluator = AICc() - pars = [[3, .2, 10], [3.5, .2, 10]] + pars = [[3, 0.2, 10], [3.5, 0.2, 10]] ideal_peaks = Peaks([pf.createpeak(p, "pwa") for p in pars]) - y = ideal_peaks.value(r) + .1*randn(len(r)) + y = ideal_peaks.value(r) + 0.1 * randn(len(r)) - guesspars = [[2.7, .15, 5], [3.7, .3, 5]] + guesspars = [[2.7, 0.15, 5], [3.7, 0.3, 5]] guess_peaks = Peaks([pf.createpeak(p, "pwa") for p in guesspars]) cluster = ModelCluster(guess_peaks, r, y, err, None, AICc, [pf]) qual1 = cluster.quality() - print qual1.stat + print(qual1.stat) cluster.fit() yfit = cluster.calc() qual2 = cluster.quality() - print qual2.stat + print(qual2.stat) plt.figure(1) plt.plot(r, y, r, yfit) diff --git a/diffpy/srmise/peaks/terminationripples.py b/diffpy/srmise/peaks/terminationripples.py index cdce773..68f52ba 100644 --- a/diffpy/srmise/peaks/terminationripples.py +++ b/diffpy/srmise/peaks/terminationripples.py @@ -21,10 +21,11 @@ logger = logging.getLogger("diffpy.srmise") -class TerminationRipples (PeakFunction): + +class TerminationRipples(PeakFunction): """Methods for evaluation and parameter estimation of a peak function with termination ripples.""" - def __init__(self, base, qmax, extension=4., supersample=5., Cache=None): + def __init__(self, base, qmax, extension=4.0, supersample=5.0, Cache=None): """Peak function which adds termination ripples to existing function. Unlike other peak functions, TerminationRipples can only be evaluated @@ -73,7 +74,6 @@ def estimate_parameters(self, r, y): reason.""" return self.base.estimate_parameters(r, y) - # TODO: Can this be implemented sanely for termination ripples? def scale_at(self, pars, x, scale): """Change parameters so value(x)->scale*value(x) for the base function. @@ -90,36 +90,36 @@ def scale_at(self, pars, x, scale): def _jacobianraw(self, pars, r, free): """Return Jacobian of base function with termination ripples. - Parameters - pars: Sequence of parameters for a single peak - r: sequence or scalar over which pars is evaluated - free: sequence of booleans which determines which derivatives are - needed. True for evaluation, False for no evaluation.""" + Parameters + pars: Sequence of parameters for a single peak + r: sequence or scalar over which pars is evaluated + free: sequence of booleans which determines which derivatives are + needed. True for evaluation, False for no evaluation.""" return self.base._jacobianraw(pars, r, free) def _transform_derivativesraw(self, pars, in_format, out_format): """Return gradient matrix for the pars converted from in_format to out_format. - Parameters - pars: Sequence of parameters - in_format: A format defined for base peak function - out_format: A format defined for base peak function""" + Parameters + pars: Sequence of parameters + in_format: A format defined for base peak function + out_format: A format defined for base peak function""" return self.base._transform_derivativesraw(pars, in_format, out_format) def _transform_parametersraw(self, pars, in_format, out_format): """Convert parameter values from in_format to out_format. - Parameters - pars: Sequence of parameters - in_format: A format defined for base peak function - out_format: A format defined for base peak function""" + Parameters + pars: Sequence of parameters + in_format: A format defined for base peak function + out_format: A format defined for base peak function""" return self.base._transform_parametersraw(pars, in_format, out_format) def _valueraw(self, pars, r): """Return value of base peak function for the given parameters and r values. - pars: Sequence of parameters for a single peak - r: sequence or scalar over which pars is evaluated""" + pars: Sequence of parameters for a single peak + r: sequence or scalar over which pars is evaluated""" return self.base._valueraw(pars, r) #### Overridden PeakFunction functions #### @@ -137,17 +137,19 @@ def jacobian(self, peak, r, rng=None): a default value of 0. If caching is enabled these may be previously calculated values instead.""" if self is not peak._owner: - raise ValueError("Argument 'peak' must be evaluated by the " - "PeakFunction subclass instance with which " - "it is associated.") + raise ValueError( + "Argument 'peak' must be evaluated by the " + "PeakFunction subclass instance with which " + "it is associated." + ) # normally r will be a sequence, but also allow single numeric values try: if len(r) > 1: - dr = (r[-1]-r[0])/(len(r)-1) + dr = (r[-1] - r[0]) / (len(r) - 1) else: # dr is ad hoc if r is a single point - dr = 2*np.pi/(self.supersample*self.qmax) + dr = 2 * np.pi / (self.supersample * self.qmax) if rng is None: rng = slice(0, len(r)) @@ -158,12 +160,12 @@ def jacobian(self, peak, r, rng=None): for idx in range(len(output)): if jac[idx] is not None: jac[idx] = self.cut_freq(jac[idx], dr) - output[idx] = r * 0. + output[idx] = r * 0.0 output[idx][rng] = jac[idx][ext_slice] return output - except (TypeError): + except TypeError: # dr is ad hoc if r is a single point. - dr = 2*np.pi/(self.supersample*self.qmax) + dr = 2 * np.pi / (self.supersample * self.qmax) (ext_r, ext_slice) = self.extend_grid(np.array([r]), dr) jac = self._jacobianraw(peak.pars, ext_r, peak.free) for idx in range(len(output)): @@ -171,7 +173,6 @@ def jacobian(self, peak, r, rng=None): jac[idx] = self.cut_freq(jac[idx], dr)[ext_slice][0] return jac - def value(self, peak, r, rng=None): """Calculate (rippled) value of peak, possibly restricted by range. @@ -187,13 +188,15 @@ def value(self, peak, r, rng=None): previously calculated values instead. """ if self is not peak._owner: - raise ValueError("Argument 'peak' must be evaluated by the " - "PeakFunction subclass instance with which " - "it is associated.") + raise ValueError( + "Argument 'peak' must be evaluated by the " + "PeakFunction subclass instance with which " + "it is associated." + ) # normally r will be a sequence, but also allow single numeric values - dr_super = 2*np.pi/(self.supersample*self.qmax) + dr_super = 2 * np.pi / (self.supersample * self.qmax) if np.isscalar(r): # dr is ad hoc if r is a single point. (ext_r, ext_slice) = self.extend_grid(np.array([r]), dr_super) @@ -204,7 +207,7 @@ def value(self, peak, r, rng=None): if rng is None: rng = slice(0, len(r)) - output = r * 0. + output = r * 0.0 # Make sure the actual dr used for finding termination ripples # is at least as fine as dr_super, while still calculating the @@ -215,13 +218,13 @@ def value(self, peak, r, rng=None): # of sampling needed to avoid the worst of these discretization # issues is difficult to determine without detailed knowledge # of the underlying function. - dr = (r[-1]-r[0])/(len(r)-1) - segments = np.ceil(dr/dr_super) - dr_segmented = dr/segments + dr = (r[-1] - r[0]) / (len(r) - 1) + segments = np.ceil(dr / dr_super) + dr_segmented = dr / segments rpart = r[rng] if segments > 1: - rpart = np.arange(rpart[0], rpart[-1] + dr_segmented/2, dr_segmented) + rpart = np.arange(rpart[0], rpart[-1] + dr_segmented / 2, dr_segmented) (ext_r, ext_slice) = self.extend_grid(rpart, dr_segmented) value = self._valueraw(peak.pars, ext_r) @@ -244,31 +247,32 @@ def cut_freq(self, sequence, delta): Parameters sequence: (numpy array) The sequence to alter. delta: The spacing between elements in sequence.""" - padlen = int(2**np.ceil(np.log2(len(sequence)))) + padlen = int(2 ** np.ceil(np.log2(len(sequence)))) padseq = fp.fft(sequence, padlen) - dq = 2*np.pi/((padlen-1)*delta) - lowidx = int(np.ceil(self.qmax/dq)) - hiidx = padlen+1-lowidx + dq = 2 * np.pi / ((padlen - 1) * delta) + lowidx = int(np.ceil(self.qmax / dq)) + hiidx = padlen + 1 - lowidx # Remove hi-frequency components - padseq[lowidx:hiidx]=0 + padseq[lowidx:hiidx] = 0 padseq = fp.ifft(padseq) - return np.real(padseq[0:len(sequence)]) + return np.real(padseq[0 : len(sequence)]) def extend_grid(self, r, dr): """Return (extended r, slice giving original range).""" - ext = self.extension*2*np.pi/self.qmax - left_ext = np.arange(r[0]-dr, max(0., r[0]-ext-dr), -dr)[::-1] - right_ext = np.arange(r[-1]+dr, r[-1]+ext+dr, dr) + ext = self.extension * 2 * np.pi / self.qmax + left_ext = np.arange(r[0] - dr, max(0.0, r[0] - ext - dr), -dr)[::-1] + right_ext = np.arange(r[-1] + dr, r[-1] + ext + dr, dr) ext_r = np.concatenate((left_ext, r, right_ext)) - ext_slice = slice(len(left_ext), len(ext_r)-len(right_ext)) + ext_slice = slice(len(left_ext), len(ext_r) - len(right_ext)) return (ext_r, ext_slice) -#end of class TerminationRipples + +# end of class TerminationRipples # simple test code -if __name__ == '__main__': +if __name__ == "__main__": import matplotlib.pyplot as plt from numpy.random import randn @@ -279,29 +283,29 @@ def extend_grid(self, r, dr): from diffpy.srmise.peakfunctions.peaks import Peaks from diffpy.srmise.peakfunctions.terminationripples import TerminationRipples - res = .01 - r = np.arange(2,4,res) - err = np.ones(len(r)) #default unknown errors - pf1 = GaussianOverR(.7) - pf2 = TerminationRipples(pf1, 20.) + res = 0.01 + r = np.arange(2, 4, res) + err = np.ones(len(r)) # default unknown errors + pf1 = GaussianOverR(0.7) + pf2 = TerminationRipples(pf1, 20.0) evaluator = AICc() - pars = [[3, .2, 10], [3.5, .2, 10]] + pars = [[3, 0.2, 10], [3.5, 0.2, 10]] ideal_peaks = Peaks([pf1.createpeak(p, "pwa") for p in pars]) ripple_peaks = Peaks([pf2.createpeak(p, "pwa") for p in pars]) y_ideal = ideal_peaks.value(r) - y_ripple = ripple_peaks.value(r) + .1*randn(len(r)) + y_ripple = ripple_peaks.value(r) + 0.1 * randn(len(r)) - guesspars = [[2.7, .15, 5], [3.7, .3, 5]] + guesspars = [[2.7, 0.15, 5], [3.7, 0.3, 5]] guess_peaks = Peaks([pf2.createpeak(p, "pwa") for p in guesspars]) cluster = ModelCluster(guess_peaks, r, y_ripple, err, None, AICc, [pf2]) qual1 = cluster.quality() - print qual1.stat + print(qual1.stat) cluster.fit() yfit = cluster.calc() qual2 = cluster.quality() - print qual2.stat + print(qual2.stat) plt.figure(1) plt.plot(r, y_ideal, r, y_ripple, r, yfit) diff --git a/diffpy/srmise/peakstability.py b/diffpy/srmise/peakstability.py index fcf5e31..d704b41 100644 --- a/diffpy/srmise/peakstability.py +++ b/diffpy/srmise/peakstability.py @@ -127,9 +127,7 @@ def setcurrent(self, idx): self.current = idx if idx is not None: result = self.results[idx] - self.ppe.setvars( - quiet=True, effective_dy=result[0] * np.ones(len(self.ppe.x)) - ) + self.ppe.setvars(quiet=True, effective_dy=result[0] * np.ones(len(self.ppe.x))) (r, y, dr, dy) = self.ppe.resampledata(result[3]) self.ppe.extracted = ModelCluster( result[1], result[2], r, y, dy, None, self.ppe.error_method, self.ppe.pf @@ -178,12 +176,10 @@ def run(self, err, savecovs=False): covs.append(self.ppe.extract()) else: self.ppe.extract() - dr = ( - self.ppe.extracted.r_cluster[-1] - self.ppe.extracted.r_cluster[0] - ) / (len(self.ppe.extracted.r_cluster) - 1) - self.results.append( - [e, self.ppe.extracted.model, self.ppe.extracted.baseline, dr] + dr = (self.ppe.extracted.r_cluster[-1] - self.ppe.extracted.r_cluster[0]) / ( + len(self.ppe.extracted.r_cluster) - 1 ) + self.results.append([e, self.ppe.extracted.model, self.ppe.extracted.baseline, dr]) for e, r, bl, dr in self.results: print("---- Results for uncertainty %s ----" % e) diff --git a/diffpy/srmise/srmiselog.py b/diffpy/srmise/srmiselog.py index d971b73..f93ac2e 100644 --- a/diffpy/srmise/srmiselog.py +++ b/diffpy/srmise/srmiselog.py @@ -242,10 +242,10 @@ def read(self, filename): except SrMiseDataFormatError as err: logger.exception("") basename = os.path.basename(filename) - emsg = ( - "Could not open '%s' due to unsupported file format " - + "or corrupted data. [%s]" - ) % (basename, err) + emsg = ("Could not open '%s' due to unsupported file format " + "or corrupted data. [%s]") % ( + basename, + err, + ) raise SrMiseFileError(emsg) return None @@ -336,9 +336,7 @@ def reset_trace(self): # filter property def setfilter(self, filter): - self.__filter = compile( - " and ".join(["(%s)" % f for f in filter]), "", "eval" - ) + self.__filter = compile(" and ".join(["(%s)" % f for f in filter]), "", "eval") def getfilter(self): return self.__filter diff --git a/doc/examples/multimodel_known_dG2.py b/doc/examples/multimodel_known_dG2.py index d061981..934e4bb 100644 --- a/doc/examples/multimodel_known_dG2.py +++ b/doc/examples/multimodel_known_dG2.py @@ -39,8 +39,23 @@ from diffpy.srmise.applications.plot import makeplot # distances from ideal Ag (refined to PDF) -dcif = np.array([11.2394, 11.608, 11.9652, 12.3121, 12.6495, 12.9781, 13.2986, - 13.6116, 13.9175, 14.2168, 14.51, 14.7973]) +dcif = np.array( + [ + 11.2394, + 11.608, + 11.9652, + 12.3121, + 12.6495, + 12.9781, + 13.2986, + 13.6116, + 13.9175, + 14.2168, + 14.51, + 14.7973, + ] +) + def run(plot=True): @@ -56,8 +71,8 @@ def run(plot=True): # Standard AIC analysis assumes the data have independent uncertainties. # Nyquist sampling minimizes correlations in the PDF, which is the closest # approximation to independence possible for the PDF. - dr = np.pi/ms.ppe.qmax - (r,y,dr2,dy) = ms.ppe.resampledata(dr) + dr = np.pi / ms.ppe.qmax + (r, y, dr2, dy) = ms.ppe.resampledata(dr) ## Classify models # All models are placed into classes. Models in the same class @@ -78,11 +93,11 @@ def run(plot=True): ## Summarize various facts about the analysis. num_models = len(ms.results) num_classes = len(ms.classes) - print "------- Multimodeling Summary --------" - print "Models: %i" %num_models - print "Classes: %i (tol=%s)" %(num_classes, tolerance) - print "Range of dgs: %f-%f" %(ms.dgs[0], ms.dgs[-1]) - print "Nyquist-sampled data points: %i" %len(r) + print("------- Multimodeling Summary --------") + print("Models: %i" % num_models) + print("Classes: %i (tol=%s)" % (num_classes, tolerance)) + print("Range of dgs: %f-%f" % (ms.dgs[0], ms.dgs[-1])) + print("Nyquist-sampled data points: %i" % len(r)) ## Get dG usable as key in analysis. # The Akaike probabilities were calculated for many assumed values of the @@ -101,8 +116,8 @@ def run(plot=True): # # The present PDF satisifes these conditions, so the rankings below reflect # an AIC-based estimate of which of the tested models the data best support. - print "\n--------- Model Rankings for dG = %f ---------" %dG - print "Rank Model Class Free AIC Prob File" + print("\n--------- Model Rankings for dG = %f ---------" % dG) + print("Rank Model Class Free AIC Prob File") for i in range(len(ms.classes)): ## Generate information about best model in ith best class. @@ -117,23 +132,25 @@ def run(plot=True): # "prob" -> The AIC probability given uncertainty dG # These all have dedicated getter functions. For example, the model # index can also be obtained using get_model(dG, corder=i) - (model, cls, nfree, aic, prob) = \ - ms.get(dG, "model", "class", "nfree", "aic", "prob", corder=i) + (model, cls, nfree, aic, prob) = ms.get(dG, "model", "class", "nfree", "aic", "prob", corder=i) - filename_base = "output/known_dG_m"+str(model) + filename_base = "output/known_dG_m" + str(model) - # Print info for this model - print "%4i %5i %5i %4i %10.4e %6.3f %s" \ - %(i+1, model, cls, nfree, aic, prob, filename_base + ".pwa") + # print(info for this model + print( + "%4i %5i %5i %4i %10.4e %6.3f %s" % (i + 1, model, cls, nfree, aic, prob, filename_base + ".pwa") + ) # A message added as a comment to saved .pwa file. - msg = ["Multimodeling Summary", - "---------------------", - "Evaluated at dG: %s" %dG, - "Model: %i (of %i)" %(model, num_models), - "Class: %i (of %i, tol=%s)" %(cls, num_classes, tolerance), - "Akaike probability: %g" %prob, - "Rank: %i" %(i+1),] + msg = [ + "Multimodeling Summary", + "---------------------", + "Evaluated at dG: %s" % dG, + "Model: %i (of %i)" % (model, num_models), + "Class: %i (of %i, tol=%s)" % (cls, num_classes, tolerance), + "Akaike probability: %g" % prob, + "Rank: %i" % (i + 1), + ] msg = "\n".join(msg) # Make this the active model @@ -146,12 +163,10 @@ def run(plot=True): if plot: plt.figure() makeplot(ms.ppe, dcif) - plt.title("Model %i/Class %i (Rank %i, AIC prob=%f)" \ - %(model, cls, i+1, prob)) + plt.title("Model %i/Class %i (Rank %i, AIC prob=%f)" % (model, cls, i + 1, prob)) # Uncomment line below to save figures. # plt.savefig(filename_base + ".png", format="png") - ## 3D plot of Akaike probabilities # This plot shows the Akaike probabilities of all classes as a function # of assumed uncertainty dG. This gives a rough sense of how the models @@ -161,13 +176,14 @@ def run(plot=True): # are highlighted. if plot: plt.figure() - ms.plot3dclassprobs(probfilter=[0.0, 1.], highlight=[dG]) + ms.plot3dclassprobs(probfilter=[0.0, 1.0], highlight=[dG]) plt.tight_layout() # Uncomment line below to save figure. - #plt.savefig("output/known_dG_probs.png", format="png", bbox_inches="tight") + # plt.savefig("output/known_dG_probs.png", format="png", bbox_inches="tight") if plot: plt.show() -if __name__ == '__main__': + +if __name__ == "__main__": run() diff --git a/doc/examples/multimodel_unknown_dG2.py b/doc/examples/multimodel_unknown_dG2.py index 1bc9f60..1bd793d 100644 --- a/doc/examples/multimodel_unknown_dG2.py +++ b/doc/examples/multimodel_unknown_dG2.py @@ -46,11 +46,32 @@ from diffpy.srmise.applications.plot import makeplot # distances from ideal (unrefined) C60 -dcif = np.array([1.44, 2.329968944, 2.494153163, 2.88, 3.595985339, - 3.704477734, 4.132591264, 4.520339129, 4.659937888, - 4.877358006, 5.209968944, 5.405310018, 5.522583786, - 5.818426502, 6.099937888, 6.164518388, 6.529777754, - 6.686673127, 6.745638756, 6.989906831, 7.136693738]) +dcif = np.array( + [ + 1.44, + 2.329968944, + 2.494153163, + 2.88, + 3.595985339, + 3.704477734, + 4.132591264, + 4.520339129, + 4.659937888, + 4.877358006, + 5.209968944, + 5.405310018, + 5.522583786, + 5.818426502, + 6.099937888, + 6.164518388, + 6.529777754, + 6.686673127, + 6.745638756, + 6.989906831, + 7.136693738, + ] +) + def run(plot=True): @@ -66,8 +87,8 @@ def run(plot=True): # Standard AIC analysis assumes the data have independent uncertainties. # Nyquist sampling minimizes correlations in the PDF, which is the closest # approximation to independence possible for the PDF. - dr = np.pi/ms.ppe.qmax - (r,y,dr2,dy) = ms.ppe.resampledata(dr) + dr = np.pi / ms.ppe.qmax + (r, y, dr2, dy) = ms.ppe.resampledata(dr) ## Classify models # All models are placed into classes. Models in the same class @@ -88,11 +109,11 @@ def run(plot=True): ## Summarize various facts about the analysis. num_models = len(ms.results) num_classes = len(ms.classes) - print "------- Multimodeling Summary --------" - print "Models: %i" %num_models - print "Classes: %i (tol=%s)" %(num_classes, tolerance) - print "Range of dgs: %f-%f" %(ms.dgs[0], ms.dgs[-1]) - print "Nyquist-sampled data points: %i" %len(r) + print("------- Multimodeling Summary --------") + print("Models: %i" % num_models) + print("Classes: %i (tol=%s)" % (num_classes, tolerance)) + print("Range of dgs: %f-%f" % (ms.dgs[0], ms.dgs[-1])) + print("Nyquist-sampled data points: %i" % len(r)) ## Find "best" models. # In short, models with greatest Akaike probability. Akaike probabilities @@ -115,13 +136,12 @@ def run(plot=True): best_classes = np.unique([ms.get_class(dG) for dG in ms.dgs]) best_dGs = [] for cls in best_classes: - cls_probs = [ms.get_prob(dG) if ms.get_class(dG) == cls else 0 \ - for dG in ms.dgs] + cls_probs = [ms.get_prob(dG) if ms.get_class(dG) == cls else 0 for dG in ms.dgs] dG = ms.dgs[np.argmax(cls_probs)] best_dGs.append(dG) - print "\n--------- Best models for at least one dG ---------" %dG - print " Best dG Model Class Free AIC Prob File" + print("\n--------- Best models for at least one dG ---------" % dG) + print(" Best dG Model Class Free AIC Prob File") for dG in best_dGs: ## Generate information about best model. @@ -135,24 +155,26 @@ def run(plot=True): # "aic" -> The AIC for this model given uncertainty dG # "prob" -> The AIC probability given uncertainty dG # These all have dedicated getter functions. - (model, cls, nfree, aic, prob) = \ - ms.get(dG, "model", "class", "nfree", "aic", "prob") + (model, cls, nfree, aic, prob) = ms.get(dG, "model", "class", "nfree", "aic", "prob") - filename_base = "output/unknown_dG_m"+str(model) + filename_base = "output/unknown_dG_m" + str(model) - # Print info for this model - print "%10.4e %5i %5i %4i %10.4e %6.3f %s" \ - %(dG, model, cls, nfree, aic, prob, filename_base + ".pwa") + # print(info for this model + print( + "%10.4e %5i %5i %4i %10.4e %6.3f %s" % (dG, model, cls, nfree, aic, prob, filename_base + ".pwa") + ) # A message added as a comment to saved .pwa file. best_from = [dg for dg in ms.dgs if ms.get_class(dg) == cls] - msg = ["Multimodeling Summary", - "---------------------", - "Model: %i (of %i)" %(model, num_models), - "Class: %i (of %i, tol=%s)" %(cls, num_classes, tolerance), - "Best model from dG: %s-%s" %(best_from[0], best_from[-1]), - "Evaluated at dG: %s" %dG, - "Akaike probability: %g" %prob] + msg = [ + "Multimodeling Summary", + "---------------------", + "Model: %i (of %i)" % (model, num_models), + "Class: %i (of %i, tol=%s)" % (cls, num_classes, tolerance), + "Best model from dG: %s-%s" % (best_from[0], best_from[-1]), + "Evaluated at dG: %s" % dG, + "Akaike probability: %g" % prob, + ] msg = "\n".join(msg) # Make this the active model @@ -165,12 +187,10 @@ def run(plot=True): if plot: plt.figure() makeplot(ms.ppe, dcif) - plt.title("Model %i/Class %i (Best dG=%f, AIC prob=%f)" \ - %(model, cls, dG, prob)) + plt.title("Model %i/Class %i (Best dG=%f, AIC prob=%f)" % (model, cls, dG, prob)) # Uncomment line below to save figures. # plt.savefig(filename_base + ".png", format="png") - ## 3D plot of Akaike probabilities # This plot shows the Akaike probabilities of all classes as a function # of assumed uncertainty dG. This gives a rough sense of how the models @@ -179,13 +199,14 @@ def run(plot=True): # are highlighted at the various dG values found above. if plot: plt.figure() - ms.plot3dclassprobs(probfilter=[0.1, 1.], highlight=best_dGs) + ms.plot3dclassprobs(probfilter=[0.1, 1.0], highlight=best_dGs) plt.tight_layout() # Uncomment line below to save figure. - #plt.savefig("output/unknown_dG_probs.png", format="png", bbox_inches="tight") + # plt.savefig("output/unknown_dG_probs.png", format="png", bbox_inches="tight") if plot: plt.show() -if __name__ == '__main__': + +if __name__ == "__main__": run() diff --git a/doc/examples/query_results.py b/doc/examples/query_results.py index c861ee5..57c4346 100644 --- a/doc/examples/query_results.py +++ b/doc/examples/query_results.py @@ -53,7 +53,7 @@ def run(plot=True): # Peaks are extracted between 2 and 10 angstroms, using the baseline # from the isolated peak example. kwds = {} - kwds["rng"] = [2.0, 10.] + kwds["rng"] = [2.0, 10.0] kwds["baseline"] = baseline # Apply peak extraction parameters. @@ -63,8 +63,7 @@ def run(plot=True): # model and the full covariance matrix. cov = ppe.extract() - - print "\n======= Accessing SrMise Results ========" + print("\n======= Accessing SrMise Results ========") ## Accessing results of extraction # # Model parameters are organized using a nested structure, with a list @@ -90,43 +89,39 @@ def run(plot=True): # peak. Thus, this parameter can be referenced as (1,2). Several examples # are presented below. - - print "\n------ Parameter values and uncertainties ------" + print("\n------ Parameter values and uncertainties ------") # ModelCovariance.get() returns a (value, uncertainty) tuple for a given # parameter. These are the results for the nearest-neighbor peak. - p0 = cov.get((0,0)) - w0 = cov.get((0,1)) - a0 = cov.get((0,2)) - print "Nearest-neighbor peak: " - print " position = %f +/- %f" %p0 - print " width = %f +/- %f" %w0 - print " area = %f +/- %f" %a0 - print " Covariance(width, area) = ", cov.getcovariance((0,1),(0,2)) + p0 = cov.get((0, 0)) + w0 = cov.get((0, 1)) + a0 = cov.get((0, 2)) + print("Nearest-neighbor peak: ") + print(" position = %f +/- %f" % p0) + print(" width = %f +/- %f" % w0) + print(" area = %f +/- %f" % a0) + print(" Covariance(width, area) = ", cov.getcovariance((0, 1), (0, 2))) # Baseline parameters. By convention, baseline is final element in cov. (slope, intercept) = cov.model[-1] - print "\nThe linear baseline B(r)=%f*r + %f" \ - % tuple(par for par in cov.model[-1]) - + print("\nThe linear baseline B(r)=%f*r + %f" % tuple(par for par in cov.model[-1])) - print "\n ------ Uncertainties from a Saved File --------" + print("\n ------ Uncertainties from a Saved File --------") # A .srmise file does not save the full covariance matrix, so it must be # recalculated when loading from these files. For example, here is the # nearest-neighbor peak in the file which we used to define the initial # baseline. cov2 = ModelCovariance() ppebl.extracted.fit(fitbaseline=True, cov=cov2, cov_format="default_output") - p0_saved = cov2.get((0,0)) - w0_saved = cov2.get((0,1)) - a0_saved = cov2.get((0,2)) - print "Nearest-neighbor peak:" - print " position = %f +/- %f" %p0_saved - print " width == %f +/- %f" %w0_saved - print " area = = %f +/- %f" %a0_saved - print " Covariance(width, area) = ", cov2.getcovariance((0,1),(0,2)) - - - print "\n ---------- Alternate Parameterizations ---------" + p0_saved = cov2.get((0, 0)) + w0_saved = cov2.get((0, 1)) + a0_saved = cov2.get((0, 2)) + print("Nearest-neighbor peak:") + print(" position = %f +/- %f" % p0_saved) + print(" width == %f +/- %f" % w0_saved) + print(" area = = %f +/- %f" % a0_saved) + print(" Covariance(width, area) = ", cov2.getcovariance((0, 1), (0, 2))) + + print("\n ---------- Alternate Parameterizations ---------") ## Different Parameterizations # Peaks and baselines may have equivalent parameterizations that are useful # in different situations. For example, the types defined by the @@ -151,26 +146,24 @@ def run(plot=True): # would transform the second, third, and fourth peaks). If the keyword # is omitted, the transformation is attempted for all parts of the fit. cov.transform(in_format="pwa", out_format="mu_sigma_area", parts="peaks") - print "Width (sigma) of nearest-neighbor peak: %f +/- %f" %cov.get((0,1)) + print("Width (sigma) of nearest-neighbor peak: %f +/- %f" % cov.get((0, 1))) - - print "\n ------------ Highly Correlated Parameters ------------" + print("\n ------------ Highly Correlated Parameters ------------") # Highly-correlated parameters can indicate difficulties constraining the # fit. This function lists all pairs of parameters with an absolute value # of correlation which exceeds a given threshold. - print "|Correlation| > 0.9:" - print "par1 par2 corr(par1, par2)" - print "\n".join(str(c) for c in cov.correlationwarning(.9)) - + print("|Correlation| > 0.9:") + print("par1 par2 corr(par1, par2)") + print("\n".join(str(c) for c in cov.correlationwarning(0.9))) - print "\n-------- Estimate coordination shell occupancy ---------" + print("\n-------- Estimate coordination shell occupancy ---------") # Estimate the scale factor and its uncertainty from first peak's intensity. # G_normalized = scale * G_observed # dscale = scale * dG_observed/G_observed - scale = 12./a0[0] - dscale = scale * a0[1]/a0[0] - print "Estimate scale factor assuming nearest-neighbor intensity = 12" - print "Scale factor is %f +/- %f" %(scale, dscale) + scale = 12.0 / a0[0] + dscale = scale * a0[1] / a0[0] + print("Estimate scale factor assuming nearest-neighbor intensity = 12") + print("Scale factor is %f +/- %f" % (scale, dscale)) # Reference for number of atoms in coordination shells for FCC. # http://chem-faculty.lsu.edu/watkins/MERLOT/cubic_neighbors/cubic_near_neighbors.html @@ -178,35 +171,33 @@ def run(plot=True): # Calculated the scaled intensities and uncertainties. intensity = [] - for i in range(0, len(cov.model)-1): - (area, darea) = cov.get((i,2)) + for i in range(0, len(cov.model) - 1): + (area, darea) = cov.get((i, 2)) area *= scale - darea = area*np.sqrt((dscale/scale)**2 + (darea/area)**2) + darea = area * np.sqrt((dscale / scale) ** 2 + (darea / area) ** 2) intensity.append((ideal_intensity[i], area, darea)) - print "\nIntensity" - print "Ideal: Estimated" + print("\nIntensity") + print("Ideal: Estimated") for i in intensity: - print "%i: %f +/- %f" %i + print("%i: %f +/- %f" % i) - print "\nTotal intensity" + print("\nTotal intensity") # It is possible to iterate over peaks directly without using indices. # In addition, peak parameters can be accessed using string keys. For the # Gaussian over r all of "position", "width", and "area" are valid. total_observed_intensity = 0 total_ideal_intensity = 0 for peak, ii in zip(cov.model[:-1], ideal_intensity): - total_observed_intensity += scale*peak["area"] + total_observed_intensity += scale * peak["area"] total_ideal_intensity += ii - print "Ideal: Observed (using estimated scale factor)" - print "%i: %f" %(total_ideal_intensity, total_observed_intensity) - + print("Ideal: Observed (using estimated scale factor)") + print("%i: %f" % (total_ideal_intensity, total_observed_intensity)) ## Save output ppe.write("output/query_results.srmise") ppe.writepwa("output/query_results.pwa") - ## Evaluating a model. # Although the ModelCovariance object is useful, the model used for fitting # can be directly accessed through PDFPeakExtraction as well, albeit @@ -217,14 +208,15 @@ def run(plot=True): # peaks are kept separate. if plot: plt.figure() - grid = np.arange(2, 10, .01) + grid = np.arange(2, 10, 0.01) bl = ppe.extracted.baseline everysecondpeak = ppe.extracted.model[::2] - plt.plot(ppe.x, ppe.y, 'o') + plt.plot(ppe.x, ppe.y, "o") for peak in everysecondpeak: plt.plot(grid, bl.value(grid) + peak.value(grid)) plt.xlim(2, 10) plt.show() -if __name__ == '__main__': + +if __name__ == "__main__": run() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3239179 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[tool.black] +line-length = 115 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + + # The following are specific to Black, you probably don't want those. + | blib2to3 + | tests/data +)/ +''' diff --git a/setup.py b/setup.py index e0e9b14..9828c76 100755 --- a/setup.py +++ b/setup.py @@ -77,10 +77,7 @@ def getversioncfg(): }, author="Luke Granlund", author_email="luke.r.granlund@gmail.com", - description=( - "Peak extraction and peak fitting tool for atomic " - "pair distribution functions." - ), + description=("Peak extraction and peak fitting tool for atomic " "pair distribution functions."), license="BSD-style license", url="https://github.com/diffpy/diffpy.srmise/", keywords="peak extraction fitting PDF AIC multimodeling", From 7c77b11e24e47d7339aeacd4b9d3a69860de08ee Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:09:24 +0800 Subject: [PATCH 06/65] fix too many leading #, import modules, and unused var (#29) --- diffpy/srmise/peaks/gaussian.py | 17 +++++++---------- diffpy/srmise/peaks/gaussianoverr.py | 17 +++++++---------- diffpy/srmise/peaks/terminationripples.py | 15 +++++++-------- diffpy/srmise/peakstability.py | 6 +++--- diffpy/srmise/srmiseerrors.py | 4 ++-- 5 files changed, 26 insertions(+), 33 deletions(-) diff --git a/diffpy/srmise/peaks/gaussian.py b/diffpy/srmise/peaks/gaussian.py index 3913b6c..15ea447 100644 --- a/diffpy/srmise/peaks/gaussian.py +++ b/diffpy/srmise/peaks/gaussian.py @@ -13,10 +13,8 @@ import logging -import matplotlib.pyplot as plt import numpy as np -import diffpy.srmise.srmiselog from diffpy.srmise.peaks.base import PeakFunction from diffpy.srmise.srmiseerrors import SrMiseEstimationError, SrMiseScalingError, SrMiseTransformationError @@ -59,7 +57,7 @@ def __init__(self, maxwidth, Cache=None): raise ValueError(emsg) self.maxwidth = maxwidth - ### Useful constants ### + # Useful constants ### # c1 and c2 help with function values self.c1 = self.maxwidth * np.sqrt(np.pi / (8 * np.log(2))) self.c2 = self.maxwidth**2 / (8 * np.log(2)) @@ -73,7 +71,7 @@ def __init__(self, maxwidth, Cache=None): return - #### Methods required by PeakFunction #### + # Methods required by PeakFunction #### def estimate_parameters(self, r, y): """Estimate parameters for single peak from data provided. @@ -102,11 +100,10 @@ def estimate_parameters(self, r, y): emsg = "Not enough data for successful estimation." raise SrMiseEstimationError(emsg) - #### Estimation #### + # Estimation #### guesspars = np.array([0.0, 0.0, 0.0], dtype=float) min_y = use_y.min() max_y = use_y.max() - center = use_r[use_y.argmax()] if min_y != max_y: weights = (use_y - min_y) ** 2 @@ -204,7 +201,7 @@ def _jacobianraw(self, pars, r, free): needed. True for evaluation, False for no evaluation. """ jacobian = [None, None, None] - if (free == False).sum() == self.npars: + if (free is False).sum() == self.npars: return jacobian # Optimization @@ -228,8 +225,8 @@ def _jacobianraw(self, pars, r, free): # derivative with respect to peak area # abs'(x)=sign(x) for real x except at 0 where it is undetermined. Since any real peak necessarily has # non-zero area and the function is paramaterized such that values of either sign represent equivalent - # curves I arbitrarily choose positive sign for pars[2]==0 in order to push the system back into a realistic - # parameter space should this improbable scenario occur. + # curves I arbitrarily choose positive sign for pars[2]==0 in order to + # push the system back into a realistic parameter space should this improbable scenario occur. # jacobian[2] = sign(pars[2])*exp_p if pars[2] >= 0: jacobian[2] = exp_p @@ -321,7 +318,7 @@ def _valueraw(self, pars, r): def getmodule(self): return __name__ - #### Other methods #### + # Other methods #### def max(self, pars): """Return position and height of the peak maximum.""" diff --git a/diffpy/srmise/peaks/gaussianoverr.py b/diffpy/srmise/peaks/gaussianoverr.py index d11467d..28df1a8 100644 --- a/diffpy/srmise/peaks/gaussianoverr.py +++ b/diffpy/srmise/peaks/gaussianoverr.py @@ -13,10 +13,8 @@ import logging -import matplotlib.pyplot as plt import numpy as np -import diffpy.srmise.srmiselog from diffpy.srmise.peaks.base import PeakFunction from diffpy.srmise.srmiseerrors import SrMiseEstimationError, SrMiseScalingError, SrMiseTransformationError @@ -59,7 +57,7 @@ def __init__(self, maxwidth, Cache=None): raise ValueError(emsg) self.maxwidth = maxwidth - ### Useful constants ### + # Useful constants ### # c1 and c2 help with function values self.c1 = self.maxwidth * np.sqrt(np.pi / (8 * np.log(2))) self.c2 = self.maxwidth**2 / (8 * np.log(2)) @@ -73,7 +71,7 @@ def __init__(self, maxwidth, Cache=None): return - #### Methods required by PeakFunction #### + # Methods required by PeakFunction #### def estimate_parameters(self, r, y): """Estimate parameters for single peak from data provided. @@ -102,11 +100,10 @@ def estimate_parameters(self, r, y): emsg = "Not enough data for successful estimation." raise SrMiseEstimationError(emsg) - #### Estimation #### + # Estimation #### guesspars = np.array([0.0, 0.0, 0.0], dtype=float) min_y = use_y.min() max_y = use_y.max() - center = use_r[use_y.argmax()] if min_y != max_y: weights = (use_y - min_y) ** 2 @@ -218,7 +215,7 @@ def _jacobianraw(self, pars, r, free): needed. True for evaluation, False for no evaluation. """ jacobian = [None, None, None] - if (free == False).sum() == self.npars: + if (free is False).sum() == self.npars: return jacobian # Optimization @@ -242,8 +239,8 @@ def _jacobianraw(self, pars, r, free): # derivative with respect to peak area # abs'(x)=sign(x) for real x except at 0 where it is undetermined. Since any real peak necessarily has # non-zero area and the function is paramaterized such that values of either sign represent equivalent - # curves I arbitrarily choose positive sign for pars[2]==0 in order to push the system back into a realistic - # parameter space should this improbable scenario occur. + # curves I arbitrarily choose positive sign for pars[2]==0 in order to + # push the system back into a realistic parameter space should this improbable scenario occur. # jacobian[2] = sign(pars[2])*exp_p if pars[2] >= 0: jacobian[2] = exp_p @@ -387,7 +384,7 @@ def _valueraw(self, pars, r): def getmodule(self): return __name__ - #### Other methods #### + # Other methods #### def max(self, pars): """Return position and height of the peak maximum.""" diff --git a/diffpy/srmise/peaks/terminationripples.py b/diffpy/srmise/peaks/terminationripples.py index 68f52ba..4706e8f 100644 --- a/diffpy/srmise/peaks/terminationripples.py +++ b/diffpy/srmise/peaks/terminationripples.py @@ -16,7 +16,6 @@ import numpy as np import scipy.fftpack as fp -import diffpy.srmise.srmiselog from diffpy.srmise.peaks.base import PeakFunction logger = logging.getLogger("diffpy.srmise") @@ -55,7 +54,7 @@ def __init__(self, base, qmax, extension=4.0, supersample=5.0, Cache=None): PeakFunction.__init__(self, parameterdict, formats, default_formats, metadict, base, Cache) return - #### Methods required by PeakFunction #### + # Methods required by PeakFunction #### # TODO: A smart way to convert from the basefunctions estimate to an # appropriate one when ripples are considered. This may not be necessary, @@ -122,7 +121,7 @@ def _valueraw(self, pars, r): r: sequence or scalar over which pars is evaluated""" return self.base._valueraw(pars, r) - #### Overridden PeakFunction functions #### + # Overridden PeakFunction functions #### # jacobian() and value() are not normally overridden by PeakFunction # subclasses, but are here to minimize the effect of edge-effects while # introducing termination ripples. @@ -236,7 +235,7 @@ def value(self, peak, r, rng=None): def getmodule(self): return __name__ - #### Other methods #### + # Other methods #### def cut_freq(self, sequence, delta): """Remove high-frequency components from sequence. @@ -278,10 +277,10 @@ def extend_grid(self, r, dr): from numpy.random import randn from diffpy.srmise.modelcluster import ModelCluster - from diffpy.srmise.modelevaluator import AICc - from diffpy.srmise.peakfunctions.gaussianoverr import GaussianOverR - from diffpy.srmise.peakfunctions.peaks import Peaks - from diffpy.srmise.peakfunctions.terminationripples import TerminationRipples + from diffpy.srmise.modelevaluators import AICc + from diffpy.srmise.peaks import Peaks + from diffpy.srmise.peaks.gaussianoverr import GaussianOverR + from diffpy.srmise.peaks.terminationripples import TerminationRipples res = 0.01 r = np.arange(2, 4, res) diff --git a/diffpy/srmise/peakstability.py b/diffpy/srmise/peakstability.py index d704b41..26952b2 100644 --- a/diffpy/srmise/peakstability.py +++ b/diffpy/srmise/peakstability.py @@ -40,7 +40,7 @@ def setppe(self, ppe): def load(self, filename): try: import cPickle as pickle - except: + except ImportError: import pickle in_s = open(filename, "rb") @@ -68,7 +68,7 @@ def load(self, filename): def save(self, filename): try: import cPickle as pickle - except: + except ImportError: import pickle out_s = open(filename, "wb") try: @@ -150,7 +150,7 @@ def animate(self, results=None, step=False, **kwds): self.setcurrent(0) plt.ion() plt.plot(*self.ppe.extracted.plottable()) - a = plt.axis() + plt.axis() for i in results: self.setcurrent(i) plt.ioff() diff --git a/diffpy/srmise/srmiseerrors.py b/diffpy/srmise/srmiseerrors.py index f7ef037..ed5287f 100644 --- a/diffpy/srmise/srmiseerrors.py +++ b/diffpy/srmise/srmiseerrors.py @@ -28,7 +28,7 @@ """ -### Superclass class for diffpy.srmise.mise +# Superclass class for diffpy.srmise.mise class SrMiseError(Exception): """Superclass of all diffpy.srmise exceptions.""" @@ -43,7 +43,7 @@ def __str__(self): return self.info -### SrMiseError subclasses ### +# SrMiseError subclasses ### class SrMiseDataFormatError(SrMiseError): From 21d1a5386f4f4f176a10a80c621029f5bfa20e71 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 30 Jul 2024 06:23:08 -0400 Subject: [PATCH 07/65] requirements (#30) --- requirements/run.txt | 3 +++ requirements/test.txt | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 requirements/run.txt create mode 100644 requirements/test.txt diff --git a/requirements/run.txt b/requirements/run.txt new file mode 100644 index 0000000..1b57b14 --- /dev/null +++ b/requirements/run.txt @@ -0,0 +1,3 @@ +numpy +scipy +matplotlib-base diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 0000000..7666f95 --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,4 @@ +flake8 +pytest +codecov +coverage From 181a54ad5a72e977e74d7017d7a1f1a9164e97b1 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:27:01 +0800 Subject: [PATCH 08/65] fix import module not used & string check (#25) --- devutils/prep.py | 6 +++-- diffpy/srmise/applications/extract.py | 11 +++----- diffpy/srmise/applications/plot.py | 13 ++++------ diffpy/srmise/basefunction.py | 19 +++++++------- doc/examples/parameter_summary.py | 36 +++++++++++++-------------- doc/examples/query_results.py | 15 ++++++----- doc/manual/source/conf.py | 9 ++++--- 7 files changed, 52 insertions(+), 57 deletions(-) diff --git a/devutils/prep.py b/devutils/prep.py index 8571e2b..af3b446 100644 --- a/devutils/prep.py +++ b/devutils/prep.py @@ -8,6 +8,8 @@ __basedir__ = os.getcwdu() +from numpy.compat import unicode + # Example imports @@ -86,7 +88,7 @@ def rm(directory, filerestr): rm("../doc/examples/output", r"known_dG.*\.pwa") rm("../doc/examples/output", r"unknown_dG.*\.pwa") - ### Testing examples + # Testing examples examples = Test() test_names = [ "extract_single_peak", @@ -108,7 +110,7 @@ def rm(directory, filerestr): examples.report() - ### Convert output of example files to Unix-style endlines for sdist. + # Convert output of example files to Unix-style endlines for sdist. if os.linesep != "\n": print("==== Scrubbing Endlines ====") # All *.srmise and *.pwa files in examples directory. diff --git a/diffpy/srmise/applications/extract.py b/diffpy/srmise/applications/extract.py index 6b89be9..5f54118 100755 --- a/diffpy/srmise/applications/extract.py +++ b/diffpy/srmise/applications/extract.py @@ -11,7 +11,8 @@ # ############################################################################## -from optparse import OptionGroup, OptionParser +import textwrap +from optparse import IndentedHelpFormatter, OptionGroup, OptionParser import matplotlib.pyplot as plt import numpy as np @@ -539,7 +540,6 @@ def main(): cov = None if options.performextraction: cov = ext.extract() - out = ext.extracted if options.savefile is not None: try: @@ -591,13 +591,10 @@ def parsepars(mp, parseq): return mp.actualize(pars, "internal", free=free) -### Class to preserve newlines in optparse +# Class to preserve newlines in optparse # Borrowed, with minor changes, from # http://groups.google.com/group/comp.lang.python/browse_frm/thread/6df6e6b541a15bc2/09f28e26af0699b1 -import textwrap -from optparse import IndentedHelpFormatter - class IndentedHelpFormatterWithNL(IndentedHelpFormatter): def _format_text(self, text): @@ -652,7 +649,7 @@ def format_option(self, option): return "".join(result) -### End class +# End class if __name__ == "__main__": main() diff --git a/diffpy/srmise/applications/plot.py b/diffpy/srmise/applications/plot.py index 0c82cb6..ba4070a 100755 --- a/diffpy/srmise/applications/plot.py +++ b/diffpy/srmise/applications/plot.py @@ -19,7 +19,6 @@ import mpl_toolkits.axisartist as AA import numpy as np from matplotlib.ticker import MultipleLocator -from mpl_toolkits.axes_grid1 import make_axes_locatable from mpl_toolkits.axes_grid1.inset_locator import inset_axes from diffpy.srmise import PDFPeakExtraction, PeakStability @@ -107,7 +106,6 @@ def comparepositions(ppe, ip=None, **kwds): plt.plot(xe, ye, "g", lw=1.5, **ep_style) if ip is not None: - yb = (base, base) plt.axhline(base, linestyle=":", color="k") ax.yaxis.set_ticks([base + 0.5 * yideal, base + 0.5 * yext]) ax.yaxis.set_ticklabels([yideal_label, yext_label]) @@ -134,7 +132,7 @@ def comparepositions(ppe, ip=None, **kwds): def dgseries(stability, **kwds): - ax = kwds.get("ax", plt.gca()) + kwds.get("ax", plt.gca()) dg_style = kwds.get("dg_style", default_dg_style) scale = kwds.get("scale", 1.0) @@ -222,7 +220,7 @@ def makeplot(ppe_or_stability, ip=None, **kwds): bottom_offset = kwds.get("bottom_offset", 3.0) # Style options - dg_style = kwds.get("dg_style", default_dg_style) + kwds.get("dg_style", default_dg_style) gobs_style = kwds.get("gobs_style", default_gobs_style) gfit_style = kwds.get("gfit_style", default_gfit_style) gind_style = kwds.get("gind_style", default_gind_style) @@ -240,7 +238,7 @@ def makeplot(ppe_or_stability, ip=None, **kwds): # Other options datalabel = kwds.get("datalabel", None) dgformatstr = kwds.get("dgformatstr", r"$\delta$g=%f") - dgformatpost = kwds.get("dgformatpost", None) # ->userfunction(string) + kwds.get("dgformatpost", None) # ->userfunction(string) show_fit = kwds.get("show_fit", True) show_individual = kwds.get("show_individual", True) fill_individual = kwds.get("fill_individual", True) @@ -532,7 +530,6 @@ def on_draw(event): def readcompare(filename): """Returns a list of distances read from filename, otherwise None.""" - from diffpy.srmise.srmiseerrors import SrMiseDataFormatError, SrMiseFileError # TODO: Make this safer try: @@ -551,7 +548,7 @@ def readcompare(filename): try: for line in datastring.split("\n"): distances.append(float(line)) - except (ValueError, IndexError) as err: + except (ValueError, IndexError): print("Could not read distances from '%s'. Ignoring file." % filename) if len(distances) == 0: @@ -620,7 +617,7 @@ def main(): distances = readcompare(opts.compare) setfigformat(figsize=(6.0, 4.0)) - figdict = makeplot(toplot, distances) + makeplot(toplot, distances) if opts.output: plt.savefig(opts.output, format=opts.format, dpi=600) if opts.show: diff --git a/diffpy/srmise/basefunction.py b/diffpy/srmise/basefunction.py index f56c5c2..3451baf 100644 --- a/diffpy/srmise/basefunction.py +++ b/diffpy/srmise/basefunction.py @@ -19,8 +19,7 @@ import numpy as np from numpy.compat import unicode -from diffpy.srmise.modelparts import ModelPart, ModelParts -from diffpy.srmise.srmiseerrors import * +from diffpy.srmise.srmiseerrors import SrMiseDataFormatError logger = logging.getLogger("diffpy.srmise") @@ -120,7 +119,7 @@ def __init__( emsg = "Argument default_formats must specify 'default_input' " + "and 'default_output' as keys." raise ValueError(emsg) for f in self.default_formats.values(): - if not f in self.parformats: + if f not in self.parformats: emsg = "Keys of argument default_formats must map to a " + "value within argument parformats." raise ValueError() @@ -140,7 +139,7 @@ def __init__( pass return - #### "Virtual" class methods #### + # "Virtual" class methods #### def actualize(self, *args, **kwds): """Create ModelPart instance of self with given parameters. ("Virtual" method)""" @@ -172,7 +171,7 @@ def _valueraw(self, *args, **kwds): emsg = "_valueraw must() be implemented in a BaseFunction subclass." raise NotImplementedError(emsg) - #### Class methods #### + # Class methods #### def jacobian(self, p, r, rng=None): """Calculate jacobian of p, possibly restricted by range. @@ -228,9 +227,9 @@ def transform_derivatives(self, pars, in_format=None, out_format=None): elif out_format == "default_input": out_format = self.default_formats["default_input"] - if not in_format in self.parformats: + if in_format not in self.parformats: raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) - if not out_format in self.parformats: + if out_format not in self.parformats: raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) if in_format == out_format: return np.identity(self.npars) @@ -263,9 +262,9 @@ def transform_parameters(self, pars, in_format=None, out_format=None): elif out_format == "default_input": out_format = self.default_formats["default_input"] - if not in_format in self.parformats: + if in_format not in self.parformats: raise ValueError("Argument 'in_format' must be one of %s." % self.parformats) - if not out_format in self.parformats: + if out_format not in self.parformats: raise ValueError("Argument 'out_format' must be one of %s." % self.parformats) # if in_format == out_format: # return pars @@ -335,7 +334,7 @@ def writestr(self, baselist): """ if self.base is not None and self.base not in baselist: emsg = "baselist does not include this BaseFunction's base function." - raise ValueError("emsg") + raise ValueError(emsg) lines = [] # Write function type lines.append("function=%s" % repr(self.__class__.__name__)) diff --git a/doc/examples/parameter_summary.py b/doc/examples/parameter_summary.py index 2ba1ac1..1b8a195 100644 --- a/doc/examples/parameter_summary.py +++ b/doc/examples/parameter_summary.py @@ -37,25 +37,25 @@ def run(plot=True): - ## Initialize peak extraction + # Initialize peak extraction # Create peak extraction object ppe = PDFPeakExtraction() # Load the PDF from a file ppe.loadpdf("data/TiO2_fine_qmax26.gr") - ###### Set up extraction parameters. + # Set up extraction parameters. # In this section we'll examine the major extraction parameters in detail. # diffpy.srmise strives to provide reasonable default values for these # parameters. For normal use setting the range, baseline, and uncertainty # should be sufficient. kwds = {} - ## Range + # Range # Range defaults to the entire PDF if not specified. kwds["rng"] = [1.5, 10.0] - ## dg + # dg # diffpy.srmise selects model complexity based primarily on the uncertainty # of the PDF. Note that very small uncertainties (<1%) can make peak # extraction excessively slow. In general, the smaller the uncertainty the @@ -80,7 +80,7 @@ def run(plot=True): # 1273-1283. doi:10.1107/S1600576714010516 kwds["dg"] = 0.35 # Play with this value! - ## baseline + # baseline # As a crystal PDF, a linear baseline crossing the origin is appropriate. # Here we define the linear baseline B(r) = -.5*r + 0, and explicitly set # the y-intercept as a fixed parameter which will not be fit. For @@ -91,7 +91,7 @@ def run(plot=True): slope = -0.65 # Play with this value! y_intercept = 0.0 kwds["baseline"] = blfunc.actualize([slope, y_intercept], free=[True, False]) - ## pf + # pf # The pf (peakfunction) parameter allows setting the shape of peaks to be # extracted. Termination effects are added automatically to the peak # function during extraction. In the harmonic approximation of atomic @@ -109,7 +109,7 @@ def run(plot=True): pf = GaussianOverR(0.7) kwds["pf"] = [pf] # Despite the list, only one entry is currently supported. - ## qmax + # qmax # PDFs typically report the value of qmax (i.e. the maximum momentum # transfer q in the measurement), but it can be specified explicitly also. # If the PDF does not report qmax, diffpy.srmise attempts to estimate it @@ -119,7 +119,7 @@ def run(plot=True): # diffpy.srmise does not consider Nyquist sampling or termination effects. kwds["qmax"] = 26.0 - ## nyquist + # nyquist # This parameter governs whether diffpy.srmise attempts to find a model # on a Nyquist-sampled grid with dr=pi/qmax, which is a grid where data # uncertainties are least correlated without loss of information. By @@ -132,7 +132,7 @@ def run(plot=True): # doi:10.1103/PhysRevB.84.134105 kwds["nyquist"] = True - ## supersample + # supersample # This parameter dictates the data be oversampled by at least this factor # (relative to the Nyquist rate) during the early stages of peak # extraction. If the input PDF is even more finely sampled, that level of @@ -141,7 +141,7 @@ def run(plot=True): # finding and clustering process, but reduces speed. kwds["supersample"] = 4.0 - ## cres + # cres # The cres (clustering resolution) parameter governs the sensitivity of the # clustering method used by diffpy.srmise. In short, when the data are # being clustered, data which are further than the clustering resolution @@ -156,7 +156,7 @@ def run(plot=True): # Apply peak extraction parameters. ppe.setvars(**kwds) - ## initial_peaks + # initial_peaks # Initial peaks are peaks which are kept fixed during the early stages of # peak extraction, effectively condition results upon their values. Since # initial peaks are sometimes dependent on other SrMise parameters (e.g. @@ -168,7 +168,7 @@ def run(plot=True): # diffpy.srmise estimate the peak parameters. # 2) Explicit specification of peak parameters. - ## Initial peaks from approximate positions. + # Initial peaks from approximate positions. # This routine estimates peak parameters by finding the peak-like cluster # containing the specified point. It does not search for occluded peaks, # so works best on well-separated peaks. It does, however, take any @@ -177,7 +177,7 @@ def run(plot=True): for p in positions: ppe.estimate_peak(p) # adds to initial_peaks - ## Initial peaks from explicit parameters. + # Initial peaks from explicit parameters. # Adding initial peaks explicitly is similar to defining a baseline. # Namely, choosing a peak function and then actualizing it with given # parameters. For this example peaks are created from the same GaussianOverR @@ -194,22 +194,22 @@ def run(plot=True): peaks.append(pf.actualize(p, free=[True, False, True], in_format="pwa")) ppe.add_peaks(peaks) # adds to initial_peaks - ## Initial peaks and pruning + # Initial peaks and pruning # While initial peaks condition what other peaks can be extracted, by # default they can also be pruned if a simpler model appears better. To # prevent this, they can be set as non-removable. for ip in ppe.initial_peaks: ip.removable = False - ## Plot initial parameters + # Plot initial parameters if plot: makeplot(ppe) plt.title("Initial Peaks") - ###### Perform peak extraction + # Perform peak extraction ppe.extract() - ## Save output + # Save output # The write() method saves a file which preserves all aspects of peak # extraction and its results, by convention using the .srmise extension, # and which can later be read by diffpy.srmise. @@ -222,7 +222,7 @@ def run(plot=True): ppe.write("output/parameter_summary.srmise") ppe.writepwa("output/parameter_summary.pwa") - ## Plot results. + # Plot results. # Display plot of extracted peak. It is also possible to plot an existing # .srmise file from the command line using # srmise output/TiO2_parameterdetail.srmise --no-extract --plot diff --git a/doc/examples/query_results.py b/doc/examples/query_results.py index 57c4346..8035921 100644 --- a/doc/examples/query_results.py +++ b/doc/examples/query_results.py @@ -30,12 +30,11 @@ import numpy as np from diffpy.srmise import ModelCovariance, PDFPeakExtraction -from diffpy.srmise.applications.plot import makeplot def run(plot=True): - ## Initialize peak extraction + # Initialize peak extraction # Create peak extraction object ppe = PDFPeakExtraction() @@ -49,7 +48,7 @@ def run(plot=True): ppebl.read("output/extract_single_peak.srmise") baseline = ppebl.extracted.baseline - ## Set up extraction parameters. + # Set up extraction parameters. # Peaks are extracted between 2 and 10 angstroms, using the baseline # from the isolated peak example. kwds = {} @@ -59,12 +58,12 @@ def run(plot=True): # Apply peak extraction parameters. ppe.setvars(**kwds) - ## Perform peak extraction, and retain object containing a copy of the + # Perform peak extraction, and retain object containing a copy of the # model and the full covariance matrix. cov = ppe.extract() print("\n======= Accessing SrMise Results ========") - ## Accessing results of extraction + # Accessing results of extraction # # Model parameters are organized using a nested structure, with a list # of peaks each of which is a list of parameters, similar to the the @@ -122,7 +121,7 @@ def run(plot=True): print(" Covariance(width, area) = ", cov2.getcovariance((0, 1), (0, 2))) print("\n ---------- Alternate Parameterizations ---------") - ## Different Parameterizations + # Different Parameterizations # Peaks and baselines may have equivalent parameterizations that are useful # in different situations. For example, the types defined by the # GaussianOverR peak function are: @@ -194,11 +193,11 @@ def run(plot=True): print("Ideal: Observed (using estimated scale factor)") print("%i: %f" % (total_ideal_intensity, total_observed_intensity)) - ## Save output + # Save output ppe.write("output/query_results.srmise") ppe.writepwa("output/query_results.pwa") - ## Evaluating a model. + # Evaluating a model. # Although the ModelCovariance object is useful, the model used for fitting # can be directly accessed through PDFPeakExtraction as well, albeit # without uncertainties. This is particularly helpful when evaluating a diff --git a/doc/manual/source/conf.py b/doc/manual/source/conf.py index 965010c..d856aef 100644 --- a/doc/manual/source/conf.py +++ b/doc/manual/source/conf.py @@ -16,6 +16,8 @@ import sys import time +from setup import versiondata + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -59,7 +61,6 @@ # |version| and |release|, also used in various other places throughout the # built documents. sys.path.insert(0, os.path.abspath("../../..")) -from setup import versiondata fullversion = versiondata.get("DEFAULT", "version") # The short X.Y version. @@ -205,11 +206,11 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', + # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', + # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. - #'preamble': '', + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples From 8772a951e51f096c2e13af250c7a9587a88a1f9c Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:28:01 +0800 Subject: [PATCH 09/65] fix too many leading "#" in string block (#26) --- doc/examples/fit_initial.py | 11 +++++------ doc/examples/multimodel_known_dG1.py | 15 +++++++-------- doc/examples/multimodel_known_dG2.py | 16 ++++++++-------- doc/examples/multimodel_unknown_dG1.py | 14 +++++++------- doc/examples/multimodel_unknown_dG2.py | 14 +++++++------- 5 files changed, 34 insertions(+), 36 deletions(-) diff --git a/doc/examples/fit_initial.py b/doc/examples/fit_initial.py index 33bf4e6..71979e8 100644 --- a/doc/examples/fit_initial.py +++ b/doc/examples/fit_initial.py @@ -20,7 +20,6 @@ grid.""" import matplotlib.pyplot as plt -import numpy as np from diffpy.srmise import PDFPeakExtraction from diffpy.srmise.applications.plot import makeplot @@ -30,11 +29,11 @@ def run(plot=True): - ## Initialize peak extraction + # Initialize peak extraction ppe = PDFPeakExtraction() ppe.loadpdf("data/C60_fine_qmax21.gr") - ## Set up interpolated baseline. + # Set up interpolated baseline. # The FromSequence baseline creates an interpolated baseline from provided # r and G(r) values, either two lists or a file containing (r, G(r)) pairs. # The baseline has no parameters. This particular baseline was estimated @@ -43,7 +42,7 @@ def run(plot=True): blf = FromSequence("data/C60baseline.dat") bl = blf.actualize([]) - ## Set up fitting parameters + # Set up fitting parameters # A summary of how parameters impact fitting is given below. # "rng" - Same as peak extraction # "baseline" - Same as peak extraction @@ -66,7 +65,7 @@ def run(plot=True): kwds["dg"] = 5000 # ad hoc, but gives each point equal weight in fit. ppe.setvars(**kwds) - ## Set up termination ripples + # Set up termination ripples # Peak fitting never changes the peak function, so termination ripples # are not applied automatically as they are in peak extraction. # Termination ripples require setting the underlying peak function and qmax. @@ -95,7 +94,7 @@ def run(plot=True): # Perform fit. ppe.fit() - ## Save results + # Save results ppe.write("output/fit_initial.srmise") ppe.writepwa("output/fit_initial.pwa") diff --git a/doc/examples/multimodel_known_dG1.py b/doc/examples/multimodel_known_dG1.py index aa50c41..f0ceb1b 100644 --- a/doc/examples/multimodel_known_dG1.py +++ b/doc/examples/multimodel_known_dG1.py @@ -38,46 +38,45 @@ import diffpy.srmise.srmiselog as sml from diffpy.srmise import MultimodelSelection, PDFPeakExtraction -from diffpy.srmise.applications.plot import makeplot def run(plot=True): - ## Suppress mundane output + # Suppress mundane output # When running scripts, especially involving multiple trials, it can be # useful to suppress many of the diffpy.srmise messages. Valid levels # include "debug", "info" (the default), "warning", "error", and # "critical." See diffpy.srmise.srmiselog for more information. sml.setlevel("warning") - ## Initialize peak extraction from saved trial + # Initialize peak extraction from saved trial ppe = PDFPeakExtraction() ppe.read("output/query_results.srmise") ppe.clearcalc() - ## Set up extraction parameters + # Set up extraction parameters # All parameters loaded from .srmise file. # Setting new values will override the previous values. kwds = {} kwds["rng"] = [10.9, 15] # Region of PDF with some overlap. ppe.setvars(**kwds) - ## Create multimodel selection object. + # Create multimodel selection object. # The MultimodelSelection class keeps track of the results of peak # extraction as the assumed uncertainty dg is varied. ms = MultimodelSelection() ms.setppe(ppe) - ## Define range of dg values + # Define range of dg values # For the purpose of illustration use 15 evenly-spaced values of dg where # 50% < dg < 120% of mean experimental dG in extraction range. dg_mean = np.mean(ppe.dy[ppe.getrangeslice()]) dgs = np.linspace(0.5 * dg_mean, 1.2 * dg_mean, 15) - ## Perform peak extraction for each of the assumed uncertainties. + # Perform peak extraction for each of the assumed uncertainties. ms.run(dgs) - ## Save results + # Save results # The file known_dG_models.dat saves the models generated above. The file # known_dG_aics.dat saves the value of the AIC of each model when evaluated # on a Nyquist-sampled grid using each of the dg values used to generate diff --git a/doc/examples/multimodel_known_dG2.py b/doc/examples/multimodel_known_dG2.py index 934e4bb..e72db46 100644 --- a/doc/examples/multimodel_known_dG2.py +++ b/doc/examples/multimodel_known_dG2.py @@ -62,19 +62,19 @@ def run(plot=True): # Suppress mundane output sml.setlevel("warning") - ## Create multimodeling object and load diffpy.srmise results from file. + # Create multimodeling object and load diffpy.srmise results from file. ms = MultimodelSelection() ms.load("output/known_dG_models.dat") ms.loadaics("output/known_dG_aics.dat") - ## Use Nyquist sampling + # Use Nyquist sampling # Standard AIC analysis assumes the data have independent uncertainties. # Nyquist sampling minimizes correlations in the PDF, which is the closest # approximation to independence possible for the PDF. dr = np.pi / ms.ppe.qmax (r, y, dr2, dy) = ms.ppe.resampledata(dr) - ## Classify models + # Classify models # All models are placed into classes. Models in the same class # should be essentially identical (same peak parameters, etc.) # up to a small tolerance determined by comparing individual peaks. The @@ -90,7 +90,7 @@ def run(plot=True): tolerance = 0.2 ms.classify(r, tolerance) - ## Summarize various facts about the analysis. + # Summarize various facts about the analysis. num_models = len(ms.results) num_classes = len(ms.classes) print("------- Multimodeling Summary --------") @@ -99,7 +99,7 @@ def run(plot=True): print("Range of dgs: %f-%f" % (ms.dgs[0], ms.dgs[-1])) print("Nyquist-sampled data points: %i" % len(r)) - ## Get dG usable as key in analysis. + # Get dG usable as key in analysis. # The Akaike probabilities were calculated for many assumed values of the # experimental uncertainty dG, and each of these assumed dG is used as a # key when obtaining the corresponding results. Numerical precision can @@ -107,7 +107,7 @@ def run(plot=True): # the key closest to its argument. dG = ms.dg_key(np.mean(ms.ppe.dy)) - ## Find "best" models. + # Find "best" models. # In short, models with greatest Akaike probability. Akaike probabilities # can only be validly compared if they were calculated for identical data, # namely identical PDF values *and* uncertainties, and are only reliable @@ -120,7 +120,7 @@ def run(plot=True): print("Rank Model Class Free AIC Prob File") for i in range(len(ms.classes)): - ## Generate information about best model in ith best class. + # Generate information about best model in ith best class. # The get(dG, *args, **kwds) method returns a tuple of values # corresponding to string arguments for the best model in best class at # given dG. When the corder keyword is given it returns the model from @@ -167,7 +167,7 @@ def run(plot=True): # Uncomment line below to save figures. # plt.savefig(filename_base + ".png", format="png") - ## 3D plot of Akaike probabilities + # 3D plot of Akaike probabilities # This plot shows the Akaike probabilities of all classes as a function # of assumed uncertainty dG. This gives a rough sense of how the models # selected by an AIC-based analysis would vary if the experimental diff --git a/doc/examples/multimodel_unknown_dG1.py b/doc/examples/multimodel_unknown_dG1.py index 3016fb3..7a6a2b8 100644 --- a/doc/examples/multimodel_unknown_dG1.py +++ b/doc/examples/multimodel_unknown_dG1.py @@ -42,18 +42,18 @@ def run(plot=True): - ## Suppress mundane output + # Suppress mundane output # When running scripts, especially involving multiple trials, it can be # useful to suppress many of the diffpy.srmise messages. Valid levels # include "debug", "info" (the default), "warning", "error", and # "critical." See diffpy.srmise.srmiselog for more information. sml.setlevel("warning") - ## Initialize peak extraction + # Initialize peak extraction ppe = PDFPeakExtraction() ppe.loadpdf("data/C60_fine_qmax21.gr") - ## Set up extraction parameters + # Set up extraction parameters # The FromSequence baseline interpolates (r, G(r)) values read from a # specified file. It has parameters. This particular baseline was # calculated by approximating the C60 sample as a face-centered cubic @@ -65,22 +65,22 @@ def run(plot=True): kwds["cres"] = 0.05 ppe.setvars(**kwds) - ## Create multimodel selection object. + # Create multimodel selection object. # The MultimodelSelection class keeps track of the results of peak # extraction as the assumed uncertainty dg is varied. ms = MultimodelSelection() ms.setppe(ppe) - ## Define range of dg values + # Define range of dg values # For the purpose of illustration use 20 evenly-spaced values of dg where # 1% < dg < 10% of max gr value between r=1 and 7.25. grmax = np.max(ppe.y[ppe.getrangeslice()]) dgs = np.linspace(0.01 * grmax, 0.10 * grmax, 20) - ## Perform peak extraction for each of the assumed uncertainties. + # Perform peak extraction for each of the assumed uncertainties. ms.run(dgs) - ## Save results + # Save results # The file C60_models.dat saves the models generated above. The file # C60_aics.dat saves the value of the AIC of each model when evaluated # on a Nyquist-sampled grid using each of the dg values used to generate diff --git a/doc/examples/multimodel_unknown_dG2.py b/doc/examples/multimodel_unknown_dG2.py index 1bd793d..9a4b24a 100644 --- a/doc/examples/multimodel_unknown_dG2.py +++ b/doc/examples/multimodel_unknown_dG2.py @@ -78,19 +78,19 @@ def run(plot=True): # Suppress mundane output sml.setlevel("warning") - ## Create multimodeling object and load diffpy.srmise results from file. + # Create multimodeling object and load diffpy.srmise results from file. ms = MultimodelSelection() ms.load("output/unknown_dG_models.dat") ms.loadaics("output/unknown_dG_aics.dat") - ## Use Nyquist sampling + # Use Nyquist sampling # Standard AIC analysis assumes the data have independent uncertainties. # Nyquist sampling minimizes correlations in the PDF, which is the closest # approximation to independence possible for the PDF. dr = np.pi / ms.ppe.qmax (r, y, dr2, dy) = ms.ppe.resampledata(dr) - ## Classify models + # Classify models # All models are placed into classes. Models in the same class # should be essentially identical (same peak parameters, etc.) # up to a small tolerance determined by comparing individual peaks. The @@ -106,7 +106,7 @@ def run(plot=True): tolerance = 0.2 ms.classify(r, tolerance) - ## Summarize various facts about the analysis. + # Summarize various facts about the analysis. num_models = len(ms.results) num_classes = len(ms.classes) print("------- Multimodeling Summary --------") @@ -115,7 +115,7 @@ def run(plot=True): print("Range of dgs: %f-%f" % (ms.dgs[0], ms.dgs[-1])) print("Nyquist-sampled data points: %i" % len(r)) - ## Find "best" models. + # Find "best" models. # In short, models with greatest Akaike probability. Akaike probabilities # can only be validly compared if they were calculated for identical data, # namely identical PDF values *and* uncertainties, and are only reliable @@ -144,7 +144,7 @@ def run(plot=True): print(" Best dG Model Class Free AIC Prob File") for dG in best_dGs: - ## Generate information about best model. + # Generate information about best model. # The get(dG, *args, **kwds) method returns a tuple of values # corresponding to string arguments for the best model in best class at # given dG. When the corder keyword is given it returns the model from @@ -191,7 +191,7 @@ def run(plot=True): # Uncomment line below to save figures. # plt.savefig(filename_base + ".png", format="png") - ## 3D plot of Akaike probabilities + # 3D plot of Akaike probabilities # This plot shows the Akaike probabilities of all classes as a function # of assumed uncertainty dG. This gives a rough sense of how the models # selected by an AIC-based analysis would vary if the experimental From ffe5fc76bdac650a7a29cdad3acebc00f8f57365 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:29:10 +0800 Subject: [PATCH 10/65] lint check, remove unused import modules & remove too many "#". (#27) --- diffpy/srmise/baselines/arbitrary.py | 6 ++---- diffpy/srmise/baselines/base.py | 16 ++++++---------- diffpy/srmise/baselines/fromsequence.py | 8 +++----- diffpy/srmise/baselines/nanospherical.py | 9 +++------ 4 files changed, 14 insertions(+), 25 deletions(-) diff --git a/diffpy/srmise/baselines/arbitrary.py b/diffpy/srmise/baselines/arbitrary.py index d2bc487..4e78be4 100644 --- a/diffpy/srmise/baselines/arbitrary.py +++ b/diffpy/srmise/baselines/arbitrary.py @@ -13,10 +13,8 @@ import logging -import matplotlib.pyplot as plt import numpy as np -import diffpy.srmise.srmiselog from diffpy.srmise.baselines import Polynomial from diffpy.srmise.baselines.base import BaselineFunction from diffpy.srmise.srmiseerrors import SrMiseEstimationError @@ -97,7 +95,7 @@ def __init__(self, npars, valuef, jacobianf=None, estimatef=None, Cache=None): metadict["estimatef"] = (estimatef, repr) BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) - #### Methods required by BaselineFunction #### + # Methods required by BaselineFunction #### def estimate_parameters(self, r, y): """Estimate parameters for data baseline. @@ -133,7 +131,7 @@ def _jacobianraw(self, pars, r, free): needed. True for evaluation, False for no evaluation.""" nfree = None if self.jacobianf is None: - nfree = (pars == True).sum() + nfree = (pars is True).sum() if nfree != 0: emsg = "No jacobian routine provided to Arbitrary." raise NotImplementedError(emsg) diff --git a/diffpy/srmise/baselines/base.py b/diffpy/srmise/baselines/base.py index 3f3844a..781d617 100644 --- a/diffpy/srmise/baselines/base.py +++ b/diffpy/srmise/baselines/base.py @@ -15,10 +15,9 @@ import numpy as np -import diffpy.srmise.srmiselog from diffpy.srmise.basefunction import BaseFunction from diffpy.srmise.modelparts import ModelPart -from diffpy.srmise.srmiseerrors import * +from diffpy.srmise.srmiseerrors import SrMiseDataFormatError logger = logging.getLogger("diffpy.srmise") @@ -85,9 +84,9 @@ def __init__( evaluations.""" BaseFunction.__init__(self, parameterdict, parformats, default_formats, metadict, base, Cache) - #### "Virtual" class methods #### + # "Virtual" class methods #### - #### Methods required by BaseFunction #### + # Methods required by BaseFunction #### def actualize( self, @@ -136,17 +135,16 @@ def factory(baselinestr, ownerlist): baselinestr: string representing Baseline ownerlist: List of BaseFunctions that owner is in """ - from numpy import array data = baselinestr.strip().splitlines() # dictionary of parameters pdict = {} for d in data: - l = d.split("=", 1) - if len(l) == 2: + result = d.split("=", 1) + if len(result) == 2: try: - pdict[l[0]] = eval(l[1]) + pdict[result[0]] = eval(result[1]) except Exception: emsg = "Invalid parameter: %s" % d raise SrMiseDataFormatError(emsg) @@ -169,10 +167,8 @@ def factory(baselinestr, ownerlist): # simple test code if __name__ == "__main__": - import matplotlib.pyplot as plt from numpy.random import randn - from diffpy.srmise.modelcluster import ModelCluster from diffpy.srmise.modelevaluators import AICc from diffpy.srmise.peaks import GaussianOverR, Peaks diff --git a/diffpy/srmise/baselines/fromsequence.py b/diffpy/srmise/baselines/fromsequence.py index 718051a..5d770a5 100644 --- a/diffpy/srmise/baselines/fromsequence.py +++ b/diffpy/srmise/baselines/fromsequence.py @@ -13,11 +13,9 @@ import logging -import matplotlib.pyplot as plt import numpy as np import scipy.interpolate as spi -import diffpy.srmise.srmiselog from diffpy.srmise.baselines.base import BaselineFunction logger = logging.getLogger("diffpy.srmise") @@ -80,7 +78,7 @@ def __init__(self, *args, **kwds): metadict["y"] = (y, self.xyrepr) BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache=None) - #### Methods required by BaselineFunction #### + # Methods required by BaselineFunction #### def estimate_parameters(self, r, y): """Return empty numpy array. @@ -151,7 +149,7 @@ def _valueraw(self, pars, r): [r[0], r[-1]], [self.minx, self.maxx], ) - except (IndexError, TypeError) as e: + except (IndexError, TypeError): if r < self.minx or r > self.maxx: logger.warn( "Warning: Evaluating interpolating function at %s, outside safe range of %s.", @@ -169,7 +167,7 @@ def xyrepr(self, var): def readxy(self, filename): """ """ - from diffpy.srmise.srmiseerrors import SrMiseDataFormatError, SrMiseFileError + from diffpy.srmise.srmiseerrors import SrMiseDataFormatError # TODO: Make this safer try: diff --git a/diffpy/srmise/baselines/nanospherical.py b/diffpy/srmise/baselines/nanospherical.py index 88de784..929a66f 100644 --- a/diffpy/srmise/baselines/nanospherical.py +++ b/diffpy/srmise/baselines/nanospherical.py @@ -13,12 +13,9 @@ import logging -import matplotlib.pyplot as plt import numpy as np -import diffpy.srmise.srmiselog from diffpy.srmise.baselines.base import BaselineFunction -from diffpy.srmise.srmiseerrors import SrMiseEstimationError logger = logging.getLogger("diffpy.srmise") @@ -54,7 +51,7 @@ def __init__(self, Cache=None): metadict = {} BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) - #### Methods required by BaselineFunction #### + # Methods required by BaselineFunction #### # def estimate_parameters(self, r, y): # """Estimate parameters for spherical baseline. (Not implemented!) @@ -90,7 +87,7 @@ def _jacobianraw(self, pars, r, free): emsg = "Argument free must have " + str(self.npars) + " elements." raise ValueError(emsg) jacobian = [None for p in range(self.npars)] - if (free == False).sum() == self.npars: + if (free is False).sum() == self.npars: return jacobian if np.isscalar(r): @@ -123,7 +120,7 @@ def _jacobianrawscale(self, pars, r): pars[1] = radius r - sequence or scalar over which pars is evaluated. """ - s = np.abs(pars[0]) + np.abs(pars[0]) R = np.abs(pars[1]) rdivR = r / R # From abs'(s) in derivative, which is equivalent to sign(s) except at 0 where it From edaa35db3faa6feb0f3519219f708cfb2fc777ef Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:29:48 +0800 Subject: [PATCH 11/65] remove unused modules, ambiguous variable name (#28) --- diffpy/srmise/baselines/polynomial.py | 6 ++--- diffpy/srmise/dataclusters.py | 2 -- diffpy/srmise/modelcluster.py | 37 ++++++++++++--------------- diffpy/srmise/srmiselog.py | 4 +-- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/diffpy/srmise/baselines/polynomial.py b/diffpy/srmise/baselines/polynomial.py index 646e9bf..c831c19 100644 --- a/diffpy/srmise/baselines/polynomial.py +++ b/diffpy/srmise/baselines/polynomial.py @@ -13,10 +13,8 @@ import logging -import matplotlib.pyplot as plt import numpy as np -import diffpy.srmise.srmiselog from diffpy.srmise.baselines.base import BaselineFunction from diffpy.srmise.srmiseerrors import SrMiseEstimationError @@ -54,7 +52,7 @@ def __init__(self, degree, Cache=None): metadict["degree"] = (degree, repr) BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) - #### Methods required by BaselineFunction #### + # Methods required by BaselineFunction #### def estimate_parameters(self, r, y): """Estimate parameters for polynomial baseline. @@ -124,7 +122,7 @@ def _jacobianraw(self, pars, r, free): emsg = "Argument free must have " + str(self.npars) + " elements." raise ValueError(emsg) jacobian = [None for p in range(self.npars)] - if (free == False).sum() == self.npars: + if (free is False).sum() == self.npars: return jacobian # The partial derivative with respect to the nth coefficient of a diff --git a/diffpy/srmise/dataclusters.py b/diffpy/srmise/dataclusters.py index ccfa27c..27755cd 100644 --- a/diffpy/srmise/dataclusters.py +++ b/diffpy/srmise/dataclusters.py @@ -17,8 +17,6 @@ import matplotlib.pyplot as plt import numpy as np -import diffpy.srmise.srmiselog - logger = logging.getLogger("diffpy.srmise") diff --git a/diffpy/srmise/modelcluster.py b/diffpy/srmise/modelcluster.py index 2367297..bab672d 100644 --- a/diffpy/srmise/modelcluster.py +++ b/diffpy/srmise/modelcluster.py @@ -22,11 +22,9 @@ import re import sys -import matplotlib.pyplot as plt import numpy as np -import scipy as sp -from scipy.optimize import leastsq +from diffpy.srmise import srmiselog from diffpy.srmise.baselines import Baseline from diffpy.srmise.modelparts import ModelParts from diffpy.srmise.peaks import Peak, Peaks @@ -39,8 +37,6 @@ logger = logging.getLogger("diffpy.srmise") -from diffpy.srmise import srmiselog - class ModelCovariance(object): """Helper class preserves uncertainty info (full covariance matrix) for a fit model. @@ -201,8 +197,8 @@ def transform(self, in_format, out_format, **kwds): subg = np.identity(p.npars(True)) except Exception as e: logger.warning( - "Transformation gradient failed for part %i: %s. Failed with message %s. Ignoring transformation." - % (i, str(p), str(e)) + "Transformation gradient failed for part %i: %s. " + "Failed with message %s. Ignoring transformation." % (i, str(p), str(e)) ) subg = np.identity(p.npars(True)) @@ -211,8 +207,8 @@ def transform(self, in_format, out_format, **kwds): p.pars = p.owner().transform_parameters(p.pars, in_format, out_format) except Exception as e: logger.warning( - "Parameter transformation failed for part %i: %s. Failed with message %s. Ignoring transformation." - % (i, str(p), str(e)) + "Parameter transformation failed for part %i: %s. " + "Failed with message %s. Ignoring transformation." % (i, str(p), str(e)) ) subg = np.identity(p.npars(True)) @@ -600,21 +596,21 @@ def factory(mcstr, **kwds): baselinefunctions = header[res.end() :].strip() header = header[: res.start()] - ### Instantiating baseline functions + # Instantiating baseline functions if readblf: blfbaselist = [] res = re.split(r"(?m)^#+ BaselineFunction \d+\s*(?:#.*\s+)*", baselinefunctions) for s in res[1:]: blfbaselist.append(BaseFunction.factory(s, blfbaselist)) - ### Instantiating peak functions + # Instantiating peak functions if readpf: pfbaselist = [] res = re.split(r"(?m)^#+ PeakFunction \d+\s*(?:#.*\s+)*", peakfunctions) for s in res[1:]: pfbaselist.append(BaseFunction.factory(s, pfbaselist)) - ### Instantiating header data + # Instantiating header data # peak_funcs res = re.search(r"^peak_funcs=(.*)$", header, re.M) peak_funcs = eval(res.groups()[0].strip()) @@ -631,19 +627,19 @@ def factory(mcstr, **kwds): res = re.search(r"^slice=(.*)$", header, re.M) cluster_slice = eval(res.groups()[0].strip()) - ### Instantiating BaselineObject + # Instantiating BaselineObject if re.match(r"^None$", baselineobject): baseline = None else: baseline = Baseline.factory(baselineobject, blfbaselist) - ### Instantiating model + # Instantiating model model = Peaks() res = re.split(r"(?m)^#+ ModelPeak\s*(?:#.*\s+)*", model_peaks) for s in res[1:]: model.append(Peak.factory(s, pfbaselist)) - ### Instantiating start data + # Instantiating start data # read actual data - r, y, dy arrays = [] if hasr: @@ -664,9 +660,10 @@ def factory(mcstr, **kwds): # raise SrMiseDataFormatError if something goes wrong try: for line in start_data.split("\n"): - l = line.split() - if len(arrays) != len(l): + lines = line.split() + if len(arrays) != len(lines): emsg = "Number of value fields does not match that given by '%s'" % start_data_info + raise IndexError(emsg) for a, v in zip(arrays, line.split()): a.append(float(v)) except (ValueError, IndexError) as err: @@ -847,7 +844,7 @@ def deletepeak(self, idx): def estimatepeak(self): """Attempt to add single peak to empty cluster. Return True if successful.""" - ### STUB!!! ### + # STUB!!! ### # Currently only a single peak function is supported. Dynamic # selection from multiple types may require additional support # within peak functions themselves. The simplest method would @@ -901,7 +898,7 @@ def fit( if estimate: try: self.estimatepeak() - except SrMiseEstimationError as e: + except SrMiseEstimationError: logger.info("Fit: No model to fit, estimation not possible.") return else: @@ -1278,7 +1275,7 @@ def prune(self): msg = ["====Pruning fits:====", "Original model:", "%s", "w/ quality: %s"] logger.info("\n".join(msg), best_model, best_qual.stat) - #### Main prune loop #### + # Main prune loop #### while check_models.count(None) < len(check_models): # Cache value of individual peaks for best current model. diff --git a/diffpy/srmise/srmiselog.py b/diffpy/srmise/srmiselog.py index f93ac2e..4677ed1 100644 --- a/diffpy/srmise/srmiselog.py +++ b/diffpy/srmise/srmiselog.py @@ -287,14 +287,14 @@ def readstr(self, datastring): res = re.search(r"^recursion=(.*)$", header, re.M) if res: - recursion = eval(res.groups()[0].strip()) + eval(res.groups()[0].strip()) else: emsg = "Required field 'recursion' not found." raise SrMiseDataFormatError(emsg) res = re.search(r"^counter=(.*)$", header, re.M) if res: - counter = eval(res.groups()[0].strip()) + eval(res.groups()[0].strip()) else: emsg = "Required field 'counter' not found." raise SrMiseDataFormatError(emsg) From 1f67292a115167125e3ae57e82812680c76078d0 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 30 Jul 2024 06:55:20 -0400 Subject: [PATCH 12/65] cleaning (#31) * requirements * clean out __init__ * replace ### * ins not none in modelevaluators base --- diffpy/srmise/__init__.py | 23 -------------- diffpy/srmise/modelevaluators/base.py | 23 ++++++-------- diffpy/srmise/modelparts.py | 2 +- diffpy/srmise/multimodelselection.py | 2 +- diffpy/srmise/peakextraction.py | 44 +++++++++++++-------------- diffpy/srmise/peaks/base.py | 4 +-- 6 files changed, 36 insertions(+), 62 deletions(-) diff --git a/diffpy/srmise/__init__.py b/diffpy/srmise/__init__.py index 532c767..a304442 100644 --- a/diffpy/srmise/__init__.py +++ b/diffpy/srmise/__init__.py @@ -13,28 +13,5 @@ """Tools for peak extraction from PDF.""" -__all__ = [ - "basefunction", - "srmiseerrors", - "srmiselog", - "dataclusters", - "modelcluster", - "modelparts", - "pdfdataset", - "pdfpeakextraction", - "peakextraction", - "peakstability", - "multimodelselection", -] - -from basefunction import BaseFunction -from dataclusters import DataClusters -from modelcluster import ModelCluster, ModelCovariance -from modelparts import ModelPart, ModelParts -from multimodelselection import MultimodelSelection -from pdfdataset import PDFDataSet -from pdfpeakextraction import PDFPeakExtraction -from peakextraction import PeakExtraction -from peakstability import PeakStability from diffpy.srmise.version import __version__ diff --git a/diffpy/srmise/modelevaluators/base.py b/diffpy/srmise/modelevaluators/base.py index 54c304a..3b94e78 100644 --- a/diffpy/srmise/modelevaluators/base.py +++ b/diffpy/srmise/modelevaluators/base.py @@ -43,9 +43,6 @@ import numpy as np -import diffpy.srmise.srmiselog -from diffpy.srmise.srmiseerrors import SrMiseModelEvaluatorError - logger = logging.getLogger("diffpy.srmise") @@ -68,9 +65,9 @@ def __lt__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert self.stat != None and other.stat != None # The statistic must already be calculated + assert self.stat is not None and other.stat is not None # The statistic must already be calculated - if self.higher_is_better: + if self.higher_is_better is not None: return self.stat < other.stat else: return other.stat < self.stat @@ -79,9 +76,9 @@ def __le__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert self.stat != None and other.stat != None # The statistic must already be calculated + assert self.stat is not None and other.stat is not None # The statistic must already be calculated - if self.higher_is_better: + if self.higher_is_better is not None: return self.stat <= other.stat else: return other.stat <= self.stat @@ -90,7 +87,7 @@ def __eq__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert self.stat != None and other.stat != None # The statistic must already be calculated + assert self.stat is not None and other.stat is not None # The statistic must already be calculated return self.stat == other.stat @@ -98,7 +95,7 @@ def __ne__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert self.stat != None and other.stat != None # The statistic must already be calculated + assert self.stat is not None and other.stat is not None # The statistic must already be calculated return self.stat != other.stat @@ -106,9 +103,9 @@ def __gt__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert self.stat != None and other.stat != None # The statistic must already be calculated + assert self.stat is not None and other.stat is not None # The statistic must already be calculated - if self.higher_is_better: + if self.higher_is_better is not None: return self.stat > other.stat else: return other.stat > self.stat @@ -117,9 +114,9 @@ def __ge__(self, other): """ """ assert self.method == other.method # Comparison between same types required - assert self.stat != None and other.stat != None # The statistic must already be calculated + assert self.stat is not None and other.stat is not None # The statistic must already be calculated - if self.higher_is_better: + if self.higher_is_better is not None: return self.stat >= other.stat else: return other.stat >= self.stat diff --git a/diffpy/srmise/modelparts.py b/diffpy/srmise/modelparts.py index 046b2f1..03f7bd3 100644 --- a/diffpy/srmise/modelparts.py +++ b/diffpy/srmise/modelparts.py @@ -207,7 +207,7 @@ def fit( return - #### Notes on the fit f + # # Notes on the fit f # f[0] = solution # f[1] = Uses the fjac and ipvt optional outputs to construct an estimate of the jacobian around the solution. # None if a singular matrix encountered (indicates very flat curvature in some direction). diff --git a/diffpy/srmise/multimodelselection.py b/diffpy/srmise/multimodelselection.py index 53773f1..b2d4cf9 100644 --- a/diffpy/srmise/multimodelselection.py +++ b/diffpy/srmise/multimodelselection.py @@ -527,7 +527,7 @@ def plot3dclassprobs(self, **kwds): verts.append(np.concatenate([[p0], zip(xs, ys), [p1], [p0]])) zlabels.append(i) - ### Define face colors + # Define face colors fc = np.array([len(self.classes[z]) for z in zlabels]) if class_size is "fraction": fc = fc / float(len(self.results)) diff --git a/diffpy/srmise/peakextraction.py b/diffpy/srmise/peakextraction.py index 30235e6..53c5358 100644 --- a/diffpy/srmise/peakextraction.py +++ b/diffpy/srmise/peakextraction.py @@ -375,17 +375,17 @@ def readstr(self, datastring): baselinefunctions = header[res.end() :].strip() header = header[: res.start()] - ### Instantiating baseline functions + # Instantiating baseline functions res = re.split(r"(?m)^#+ BaselineFunction \d+\s*(?:#.*\s+)*", baselinefunctions) for s in res[1:]: safebf.append(BaseFunction.factory(s, safebf)) - ### Instantiating peak functions + # Instantiating peak functions res = re.split(r"(?m)^#+ PeakFunction \d+\s*(?:#.*\s+)*", peakfunctions) for s in res[1:]: safepf.append(BaseFunction.factory(s, safepf)) - ### Instantiating Baseline object + # Instantiating Baseline object if re.match(r"^None$", baselineobject): self.baseline = None elif re.match(r"^\d+$", baselineobject): @@ -393,7 +393,7 @@ def readstr(self, datastring): else: self.baseline = Baseline.factory(baselineobject, safebf) - ### Instantiating initial peaks + # Instantiating initial peaks if re.match(r"^None$", initial_peaks): self.initial_peaks = None else: @@ -402,7 +402,7 @@ def readstr(self, datastring): for s in res[1:]: self.initial_peaks.append(Peak.factory(s, safepf)) - ### Instantiating srmise metatdata + # Instantiating srmise metatdata # pf res = re.search(r"^pf=(.*)$", srmisemetadata, re.M) @@ -426,10 +426,10 @@ def readstr(self, datastring): res = re.search(r"^Range=(.*)$", srmisemetadata, re.M) self.rng = eval(res.groups()[0].strip()) - ### Instantiating other metadata + # Instantiating other metadata self.readmetadata(metadata) - ### Instantiating start data + # Instantiating start data # read actual data - x, y, dx, dy, plus effective_dy arrays = [] if hasx: @@ -478,7 +478,7 @@ def readstr(self, datastring): if hasedy: self.effective_dy = np.array(self.effective_dy) - ### Instantiating results + # Instantiating results res = re.search(r"^#+ ModelCluster\s*(?:#.*\s+)*", results, re.M) if res: mc = results[res.end() :].strip() @@ -638,7 +638,7 @@ def writestr(self): line.append("%g" % self.effective_dy[i]) lines.append(" ".join(line)) - ### Calculated members + # Calculated members lines.append("##### Results") lines.append("extraction_type=%s" % repr(self.extraction_type)) @@ -792,8 +792,8 @@ def extract_single(self, recursion_depth=1): stepcounter = 0 - ############################ - ### Main extraction loop ### + # ######################### + # Main extraction loop ### for step in dclusters: stepcounter += 1 @@ -839,7 +839,7 @@ def extract_single(self, recursion_depth=1): # three clusters can become adjacent at any given step. assert len(adjacent) <= 3 - ### Update cluster fits ### + # Update cluster fits ### # 1. Refit clusters adjacent to at least one other cluster. for a in adjacent: mclusters[a].fit(justify=True) @@ -922,7 +922,7 @@ def extract_single(self, recursion_depth=1): near_peaks = Peaks([full_cluster.model[i] for i in near_peaks]) other_peaks = Peaks([full_cluster.model[i] for i in other_peaks]) - ### Remove contribution of peaks outside neighborhood + # Remove contribution of peaks outside neighborhood # Define range of fitting/recursion to the interpeak range # The adjusted error is passed unchanged. This may introduce # a few more peaks than is justified, but they can be pruned @@ -985,7 +985,7 @@ def extract_single(self, recursion_depth=1): # Incorporate best peaks from recursive search. adj_cluster.augment(rec) - ### Select which model to use + # Select which model to use full_cluster.model = other_peaks full_cluster.replacepeaks(adj_cluster.model) full_cluster.fit(True) @@ -1001,9 +1001,9 @@ def extract_single(self, recursion_depth=1): logger.debug("\n".join(msg), mclusters[step.lastcluster_idx], full_cluster) mclusters[step.lastcluster_idx] = full_cluster - ### End update cluster fits ### + # End update cluster fits ### - ### Combine adjacent clusters ### + # Combine adjacent clusters ### # Iterate in reverse order to preserve earlier indices for idx in adjacent[-1:0:-1]: @@ -1065,7 +1065,7 @@ def extract_single(self, recursion_depth=1): near_peaks = Peaks([new_cluster.model[i] for i in near_peaks]) other_peaks = Peaks([new_cluster.model[i] for i in other_peaks]) - ### Remove contribution of peaks outside neighborhood + # Remove contribution of peaks outside neighborhood # Define range of fitting/recursion to the interpeak range # The adjusted error is passed unchanged. This may introduce # a few more peaks than is justified, but they can be pruned @@ -1075,7 +1075,7 @@ def extract_single(self, recursion_depth=1): adj_y = y[adj_slice] - other_peaks.value(adj_x) adj_error = dy[adj_slice] - #### Perform recursion on a version that is scaled at the + # # Perform recursion on a version that is scaled at the # border, as well as on that is simply fit beforehand. In # many cases these lead to nearly identical results, but # occasionally one works much better than the other. @@ -1194,7 +1194,7 @@ def extract_single(self, recursion_depth=1): # Incorporate best peaks from recursive search. adj_cluster2.augment(rec2) - ### Select which model to use + # Select which model to use new_cluster.model = other_peaks rej_cluster = ModelCluster(new_cluster) q1 = adj_cluster1.quality(self.error_method) @@ -1224,7 +1224,7 @@ def extract_single(self, recursion_depth=1): mclusters[idx - 1] = new_cluster del mclusters[idx] - ### End combine adjacent clusters loop ### + # End combine adjacent clusters loop ### # Finally, combine clusters in dclusters if len(adjacent) > 0: @@ -1232,8 +1232,8 @@ def extract_single(self, recursion_depth=1): tracer.emit(*mclusters) - ### End main extraction loop ### - ################################ + # End main extraction loop ### + # ############################# # Put initial peaks back in mclusters[0].addexternalpeaks(ip) diff --git a/diffpy/srmise/peaks/base.py b/diffpy/srmise/peaks/base.py index 6e87d83..767b0f1 100644 --- a/diffpy/srmise/peaks/base.py +++ b/diffpy/srmise/peaks/base.py @@ -91,13 +91,13 @@ def __init__( raise ValueError(emsg) BaseFunction.__init__(self, parameterdict, parformats, default_formats, metadict, base, Cache) - #### "Virtual" class methods #### + # # "Virtual" class methods #### def scale_at(self, peak, x, scale): emsg = "scale_at must be implemented in a PeakFunction subclass." raise NotImplementedError(emsg) - #### Methods required by BaseFunction #### + # # Methods required by BaseFunction #### def actualize( self, From 30d0909a3573acac470e7f7d5e58ec32a5adc231 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 30 Jul 2024 06:55:44 -0400 Subject: [PATCH 13/65] Copyright (#32) * requirements * basefunction * all the copyright statements --- diffpy/srmise/__init__.py | 3 ++- diffpy/srmise/applications/__init__.py | 3 ++- diffpy/srmise/applications/plot.py | 3 ++- diffpy/srmise/basefunction.py | 4 +++- diffpy/srmise/baselines/__init__.py | 3 ++- diffpy/srmise/baselines/arbitrary.py | 3 ++- diffpy/srmise/baselines/base.py | 3 ++- diffpy/srmise/baselines/fromsequence.py | 3 ++- diffpy/srmise/baselines/nanospherical.py | 3 ++- diffpy/srmise/baselines/polynomial.py | 3 ++- diffpy/srmise/dataclusters.py | 3 ++- diffpy/srmise/modelcluster.py | 3 ++- diffpy/srmise/modelevaluators/__init__.py | 3 ++- diffpy/srmise/modelevaluators/aic.py | 3 ++- diffpy/srmise/modelevaluators/aicc.py | 3 ++- diffpy/srmise/modelevaluators/base.py | 3 ++- diffpy/srmise/modelparts.py | 3 ++- diffpy/srmise/multimodelselection.py | 3 ++- diffpy/srmise/pdfpeakextraction.py | 3 ++- diffpy/srmise/peakextraction.py | 3 ++- diffpy/srmise/peaks/__init__.py | 3 ++- diffpy/srmise/peaks/base.py | 3 ++- diffpy/srmise/peaks/gaussian.py | 3 ++- diffpy/srmise/peaks/gaussianoverr.py | 3 ++- diffpy/srmise/peaks/terminationripples.py | 3 ++- diffpy/srmise/peakstability.py | 3 ++- diffpy/srmise/srmiseerrors.py | 3 ++- diffpy/srmise/srmiselog.py | 3 ++- diffpy/srmise/version.py | 3 ++- 29 files changed, 59 insertions(+), 29 deletions(-) diff --git a/diffpy/srmise/__init__.py b/diffpy/srmise/__init__.py index a304442..f746b0f 100644 --- a/diffpy/srmise/__init__.py +++ b/diffpy/srmise/__init__.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/applications/__init__.py b/diffpy/srmise/applications/__init__.py index e9f32f6..5540acb 100644 --- a/diffpy/srmise/applications/__init__.py +++ b/diffpy/srmise/applications/__init__.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/applications/plot.py b/diffpy/srmise/applications/plot.py index ba4070a..5dc59e1 100755 --- a/diffpy/srmise/applications/plot.py +++ b/diffpy/srmise/applications/plot.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/basefunction.py b/diffpy/srmise/basefunction.py index 3451baf..d0049a5 100644 --- a/diffpy/srmise/basefunction.py +++ b/diffpy/srmise/basefunction.py @@ -2,7 +2,9 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/baselines/__init__.py b/diffpy/srmise/baselines/__init__.py index fc8ad98..0515732 100644 --- a/diffpy/srmise/baselines/__init__.py +++ b/diffpy/srmise/baselines/__init__.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/baselines/arbitrary.py b/diffpy/srmise/baselines/arbitrary.py index 4e78be4..6d1fe0d 100644 --- a/diffpy/srmise/baselines/arbitrary.py +++ b/diffpy/srmise/baselines/arbitrary.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/baselines/base.py b/diffpy/srmise/baselines/base.py index 781d617..7324f8d 100644 --- a/diffpy/srmise/baselines/base.py +++ b/diffpy/srmise/baselines/base.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/baselines/fromsequence.py b/diffpy/srmise/baselines/fromsequence.py index 5d770a5..120a359 100644 --- a/diffpy/srmise/baselines/fromsequence.py +++ b/diffpy/srmise/baselines/fromsequence.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/baselines/nanospherical.py b/diffpy/srmise/baselines/nanospherical.py index 929a66f..4781312 100644 --- a/diffpy/srmise/baselines/nanospherical.py +++ b/diffpy/srmise/baselines/nanospherical.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/baselines/polynomial.py b/diffpy/srmise/baselines/polynomial.py index c831c19..d7ba84a 100644 --- a/diffpy/srmise/baselines/polynomial.py +++ b/diffpy/srmise/baselines/polynomial.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/dataclusters.py b/diffpy/srmise/dataclusters.py index 27755cd..6b64333 100644 --- a/diffpy/srmise/dataclusters.py +++ b/diffpy/srmise/dataclusters.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/modelcluster.py b/diffpy/srmise/modelcluster.py index bab672d..d7d1742 100644 --- a/diffpy/srmise/modelcluster.py +++ b/diffpy/srmise/modelcluster.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/modelevaluators/__init__.py b/diffpy/srmise/modelevaluators/__init__.py index 4cda42c..1d80760 100644 --- a/diffpy/srmise/modelevaluators/__init__.py +++ b/diffpy/srmise/modelevaluators/__init__.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/modelevaluators/aic.py b/diffpy/srmise/modelevaluators/aic.py index 729e556..7321d9f 100644 --- a/diffpy/srmise/modelevaluators/aic.py +++ b/diffpy/srmise/modelevaluators/aic.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/modelevaluators/aicc.py b/diffpy/srmise/modelevaluators/aicc.py index 1e31b90..18e950b 100644 --- a/diffpy/srmise/modelevaluators/aicc.py +++ b/diffpy/srmise/modelevaluators/aicc.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/modelevaluators/base.py b/diffpy/srmise/modelevaluators/base.py index 3b94e78..b51e965 100644 --- a/diffpy/srmise/modelevaluators/base.py +++ b/diffpy/srmise/modelevaluators/base.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/modelparts.py b/diffpy/srmise/modelparts.py index 03f7bd3..8dd0c40 100644 --- a/diffpy/srmise/modelparts.py +++ b/diffpy/srmise/modelparts.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/multimodelselection.py b/diffpy/srmise/multimodelselection.py index b2d4cf9..4c1ffa6 100644 --- a/diffpy/srmise/multimodelselection.py +++ b/diffpy/srmise/multimodelselection.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/pdfpeakextraction.py b/diffpy/srmise/pdfpeakextraction.py index 1971bce..f68a7cf 100644 --- a/diffpy/srmise/pdfpeakextraction.py +++ b/diffpy/srmise/pdfpeakextraction.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peakextraction.py b/diffpy/srmise/peakextraction.py index 53c5358..4693d66 100644 --- a/diffpy/srmise/peakextraction.py +++ b/diffpy/srmise/peakextraction.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peaks/__init__.py b/diffpy/srmise/peaks/__init__.py index d3c7ee3..dba8fb9 100644 --- a/diffpy/srmise/peaks/__init__.py +++ b/diffpy/srmise/peaks/__init__.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peaks/base.py b/diffpy/srmise/peaks/base.py index 767b0f1..ddd6095 100644 --- a/diffpy/srmise/peaks/base.py +++ b/diffpy/srmise/peaks/base.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peaks/gaussian.py b/diffpy/srmise/peaks/gaussian.py index 15ea447..2c12788 100644 --- a/diffpy/srmise/peaks/gaussian.py +++ b/diffpy/srmise/peaks/gaussian.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peaks/gaussianoverr.py b/diffpy/srmise/peaks/gaussianoverr.py index 28df1a8..d5e8656 100644 --- a/diffpy/srmise/peaks/gaussianoverr.py +++ b/diffpy/srmise/peaks/gaussianoverr.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peaks/terminationripples.py b/diffpy/srmise/peaks/terminationripples.py index 4706e8f..7c4785a 100644 --- a/diffpy/srmise/peaks/terminationripples.py +++ b/diffpy/srmise/peaks/terminationripples.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peakstability.py b/diffpy/srmise/peakstability.py index 26952b2..f53eed2 100644 --- a/diffpy/srmise/peakstability.py +++ b/diffpy/srmise/peakstability.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/srmiseerrors.py b/diffpy/srmise/srmiseerrors.py index ed5287f..62953d5 100644 --- a/diffpy/srmise/srmiseerrors.py +++ b/diffpy/srmise/srmiseerrors.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/srmiselog.py b/diffpy/srmise/srmiselog.py index 4677ed1..bfc6002 100644 --- a/diffpy/srmise/srmiselog.py +++ b/diffpy/srmise/srmiselog.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/version.py b/diffpy/srmise/version.py index d0c6e1d..094af4a 100644 --- a/diffpy/srmise/version.py +++ b/diffpy/srmise/version.py @@ -2,7 +2,8 @@ ############################################################################## # # SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University. +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund From 7dd69bc61db25b004e5d623df39864a755bb813d Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 31 Jul 2024 02:08:50 +0800 Subject: [PATCH 14/65] lint check, fix break import modules, remove unused import modules, remove some # (#33) --- diffpy/srmise/applications/extract.py | 34 +++++++++-------------- diffpy/srmise/modelevaluators/aic.py | 13 +++++---- diffpy/srmise/pdfpeakextraction.py | 16 +++++++---- diffpy/srmise/peaks/__init__.py | 5 ---- diffpy/srmise/peaks/base.py | 10 +++---- diffpy/srmise/peaks/terminationripples.py | 1 - diffpy/srmise/srmiselog.py | 13 ++++----- doc/examples/extract_single_peak.py | 20 ++++++------- 8 files changed, 50 insertions(+), 62 deletions(-) diff --git a/diffpy/srmise/applications/extract.py b/diffpy/srmise/applications/extract.py index 5f54118..c070704 100755 --- a/diffpy/srmise/applications/extract.py +++ b/diffpy/srmise/applications/extract.py @@ -419,25 +419,19 @@ def main(): from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction from diffpy.srmise.srmiseerrors import SrMiseDataFormatError, SrMiseFileError - if options.peakfunction is not None: - from diffpy.srmise import peaks - - try: - options.peakfunction = eval("peaks." + options.peakfunction) - except Exception as err: - print(err) - print("Could not create peak function '%s'. Exiting." % options.peakfunction) - return - - if options.modelevaluator is not None: - from diffpy.srmise import modelevaluators - - try: - options.modelevaluator = eval("modelevaluators." + options.modelevaluator) - except Exception as err: - print(err) - print("Could not find ModelEvaluator '%s'. Exiting." % options.modelevaluator) - return + try: + options.peakfunction = eval("peaks." + options.peakfunction) + except Exception as err: + print(err) + print("Could not create peak function '%s'. Exiting." % options.peakfunction) + return + + try: + options.modelevaluator = eval("modelevaluators." + options.modelevaluator) + except Exception as err: + print(err) + print("Could not find ModelEvaluator '%s'. Exiting." % options.modelevaluator) + return if options.bcrystal is not None: from diffpy.srmise.baselines import Polynomial @@ -475,8 +469,6 @@ def main(): bl = NanoSpherical() options.baseline = parsepars(bl, options.bspherical) - elif options.baseline is not None: - from diffpy.srmise import baselines try: options.baseline = eval("baselines." + options.baseline) diff --git a/diffpy/srmise/modelevaluators/aic.py b/diffpy/srmise/modelevaluators/aic.py index 7321d9f..a8d870b 100644 --- a/diffpy/srmise/modelevaluators/aic.py +++ b/diffpy/srmise/modelevaluators/aic.py @@ -16,7 +16,6 @@ import numpy as np -import diffpy.srmise.srmiselog from diffpy.srmise.modelevaluators.base import ModelEvaluator from diffpy.srmise.srmiseerrors import SrMiseModelEvaluatorError @@ -72,7 +71,7 @@ def evaluate(self, fit, count_fixed=False, kshift=0): logger.warn("AIC.evaluate(): too few data to evaluate quality reliably.") n = self.minpoints(k) - if self.chisq == None: + if self.chisq is None: self.chisq = self.chi_squared(fit.value(), fit.y_cluster, fit.error_cluster) self.stat = self.chisq + self.parpenalty(k, n) @@ -94,10 +93,12 @@ def parpenalty(self, k, n): return (2 * k) * fudgefactor def growth_justified(self, fit, k_prime): - """Returns whether adding k_prime parameters to the given model (ModelCluster) is justified given the current quality of the fit. - The assumption is that adding k_prime parameters will result in "effectively 0" chiSquared cost, and so adding it is justified - if the cost of adding these parameters is less than the current chiSquared cost. The validity of this assumption (which - depends on an unknown chiSquared value) and the impact of the errors used should be examined more thoroughly in the future. + """Returns whether adding k_prime parameters to the given model (ModelCluster) is justified + given the current quality of the fit. The assumption is that adding k_prime parameters will + result in "effectively 0" chiSquared cost, and so adding it is justified if the cost of adding + these parameters is less than the current chiSquared cost. + The validity of this assumption (which depends on an unknown chiSquared value) + and the impact of the errors used should be examined more thoroughly in the future. """ if self.chisq is None: diff --git a/diffpy/srmise/pdfpeakextraction.py b/diffpy/srmise/pdfpeakextraction.py index f68a7cf..ab5f03d 100644 --- a/diffpy/srmise/pdfpeakextraction.py +++ b/diffpy/srmise/pdfpeakextraction.py @@ -16,20 +16,24 @@ import os.path import re -import matplotlib.pyplot as plt import numpy as np +from diffpy.srmise import srmiselog from diffpy.srmise.modelcluster import ModelCluster, ModelCovariance # from diffpy.pdfgui.control.pdfdataset import PDFDataSet from diffpy.srmise.pdfdataset import PDFDataSet from diffpy.srmise.peakextraction import PeakExtraction -from diffpy.srmise.srmiseerrors import * +from diffpy.srmise.srmiseerrors import ( + SrMiseDataFormatError, + SrMiseError, + SrMiseQmaxError, + SrMiseStaticOwnerError, + SrMiseUndefinedCovarianceError, +) logger = logging.getLogger("diffpy.srmise") -from diffpy.srmise import srmiselog - class PDFPeakExtraction(PeakExtraction): """Class for peak extraction of peaks from the PDF. @@ -454,7 +458,7 @@ def extract(self, **kwds): try: logger.info(str(cov)) # logger.info("Correlations > .8:\n%s", "\n".join(str(c) for c in cov.correlationwarning(.8))) - except SrMiseUndefinedCovarianceError as e: + except SrMiseUndefinedCovarianceError: logger.warn("Covariance not defined for final model. Fit may not have converged.") logger.info(str(ext)) @@ -527,7 +531,7 @@ def fit(self, **kwds): logger.info(str(ext)) try: logger.info(str(cov)) - except SrMiseUndefinedCovarianceError as e: + except SrMiseUndefinedCovarianceError: logger.warn("Covariance not defined for final model. Fit may not have converged.") # Update calculated instance variables diff --git a/diffpy/srmise/peaks/__init__.py b/diffpy/srmise/peaks/__init__.py index dba8fb9..ea3cdfc 100644 --- a/diffpy/srmise/peaks/__init__.py +++ b/diffpy/srmise/peaks/__init__.py @@ -13,8 +13,3 @@ ############################################################################## __all__ = ["base", "gaussian", "gaussianoverr", "terminationripples"] - -from base import Peak, Peaks -from gaussian import Gaussian -from gaussianoverr import GaussianOverR -from terminationripples import TerminationRipples diff --git a/diffpy/srmise/peaks/base.py b/diffpy/srmise/peaks/base.py index ddd6095..47e7a78 100644 --- a/diffpy/srmise/peaks/base.py +++ b/diffpy/srmise/peaks/base.py @@ -16,10 +16,9 @@ import numpy as np -import diffpy.srmise.srmiselog from diffpy.srmise.basefunction import BaseFunction from diffpy.srmise.modelparts import ModelPart, ModelParts -from diffpy.srmise.srmiseerrors import * +from diffpy.srmise.srmiseerrors import SrMiseDataFormatError, SrMiseScalingError logger = logging.getLogger("diffpy.srmise") @@ -263,17 +262,16 @@ def factory(peakstr, ownerlist): peakstr: string representing peak ownerlist: List of BaseFunctions that owner is in """ - from numpy import array data = peakstr.strip().splitlines() # dictionary of parameters pdict = {} for d in data: - l = d.split("=", 1) - if len(l) == 2: + parse_value = d.split("=", 1) + if len(parse_value) == 2: try: - pdict[l[0]] = eval(l[1]) + pdict[parse_value[0]] = eval(parse_value[1]) except Exception: emsg = "Invalid parameter: %s" % d raise SrMiseDataFormatError(emsg) diff --git a/diffpy/srmise/peaks/terminationripples.py b/diffpy/srmise/peaks/terminationripples.py index 7c4785a..f916d3b 100644 --- a/diffpy/srmise/peaks/terminationripples.py +++ b/diffpy/srmise/peaks/terminationripples.py @@ -281,7 +281,6 @@ def extend_grid(self, r, dr): from diffpy.srmise.modelevaluators import AICc from diffpy.srmise.peaks import Peaks from diffpy.srmise.peaks.gaussianoverr import GaussianOverR - from diffpy.srmise.peaks.terminationripples import TerminationRipples res = 0.01 r = np.arange(2, 4, res) diff --git a/diffpy/srmise/srmiselog.py b/diffpy/srmise/srmiselog.py index bfc6002..361ea4b 100644 --- a/diffpy/srmise/srmiselog.py +++ b/diffpy/srmise/srmiselog.py @@ -41,11 +41,10 @@ import logging import os.path import re -import sys from diffpy.srmise.srmiseerrors import SrMiseDataFormatError, SrMiseFileError, SrMiseLogError -### Default settings ### +# Default settings ### defaultformat = "%(message)s" defaultlevel = logging.INFO @@ -57,7 +56,7 @@ "critical": logging.CRITICAL, } -### Set up logging to stdout ### +# Set up logging to stdout ### logger = logging.getLogger("diffpy.srmise") logger.setLevel(defaultlevel) ch = logging.StreamHandler() @@ -68,10 +67,10 @@ logger.addHandler(ch) -### Optional file logger ### +# Optional file logger ### fh = None -### Make updated plots as fitting progresses. ### +# Make updated plots as fitting progresses. ### liveplots = False wait = False @@ -144,7 +143,7 @@ def liveplotting(lp, w=False): raise ValueError(emsg) -### TracePeaks. Primary purpose is to enable creating movies. ### +# TracePeaks. Primary purpose is to enable creating movies. ### class TracePeaks(object): @@ -345,7 +344,7 @@ def getfilter(self): filter = property(getfilter, setfilter) -### End of class TracePeaks +# End of class TracePeaks def settracer(**kwds): diff --git a/doc/examples/extract_single_peak.py b/doc/examples/extract_single_peak.py index 99edd4c..b32b0be 100644 --- a/doc/examples/extract_single_peak.py +++ b/doc/examples/extract_single_peak.py @@ -27,26 +27,26 @@ import matplotlib.pyplot as plt -from diffpy.srmise import PDFPeakExtraction from diffpy.srmise.applications.plot import makeplot from diffpy.srmise.baselines import Polynomial +from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction def run(plot=True): - ## Initialize peak extraction + # Initialize peak extraction # Create peak extraction object ppe = PDFPeakExtraction() # Load the PDF from a file ppe.loadpdf("data/Ag_nyquist_qmax30.gr") - ## Set up extraction parameters. - # For convenience we add all parameters to a dictionary before passing them + # Set up extraction parameters. + # For convenience, we add all parameters to a dictionary before passing them # to the extraction object. # # The "rng" (range) parameter defines the region over which peaks will be - # extracted and fit. For the well isolated nearest-neighbor silver peak, + # extracted and fit. For the well isolated nearest-neighbor silver peak, # which occurs near 2.9 angstroms, it is sufficient to perform extraction # between 2 and 3.5 angstroms. # @@ -61,24 +61,24 @@ def run(plot=True): # Apply peak extraction parameters. ppe.setvars(**kwds) - ## Perform peak extraction + # Perform peak extraction ppe.extract() - ## Save output + # Save output # The write() method saves a file which preserves all aspects of peak # extraction and its results, by convention using the .srmise extension, # and which can later be read by diffpy.srmise. # # The writepwa() method saves a file intended as a human-readable summary. # In particular, it reports the position, width (as full-width at - # half-maximum), and area of of extracted peaks. The reported values + # half-maximum), and area of extracted peaks. The reported values # are for Gaussians in the radial distribution function (RDF) corresponding # to this PDF. ppe.write("output/extract_single_peak.srmise") ppe.writepwa("output/extract_single_peak.pwa") - ## Plot results. - # Display plot of extracted peak. It is also possible to plot an existing + # Plot results. + # Display plot of extracted peak. It is also possible to plot an existing # .srmise file from the command line using # srmise output/Ag_singlepeak.srmise --no-extract --plot # For additional plotting options, run "srmiseplot --help". From 81b261bfa3d21795b209b6be518acf4306f9e2a4 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 31 Jul 2024 02:12:05 +0800 Subject: [PATCH 15/65] fix break import modules, remove unused import modules, fix docstring length (#34) --- diffpy/srmise/multimodelselection.py | 55 +++++++++++++--------------- diffpy/srmise/pdfdataset.py | 1 - 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/diffpy/srmise/multimodelselection.py b/diffpy/srmise/multimodelselection.py index 4c1ffa6..3fc4f5b 100644 --- a/diffpy/srmise/multimodelselection.py +++ b/diffpy/srmise/multimodelselection.py @@ -18,9 +18,8 @@ import numpy as np from matplotlib import transforms -import diffpy.srmise.srmiselog -from diffpy.srmise import ModelCluster, PeakStability -from diffpy.srmise.modelevaluators.base import ModelEvaluator +from diffpy.srmise.modelcluster import ModelCluster +from diffpy.srmise.peakstability import PeakStability logger = logging.getLogger("diffpy.srmise") @@ -114,7 +113,7 @@ def makeaics(self, dgs, dr, filename=None): if filename is not None: try: import cPickle as pickle - except: + except ImportError: import pickle out_s = open(filename, "wb") pickle.dump(aics_out, out_s) @@ -126,7 +125,7 @@ def loadaics(self, filename): """Load file containing results of the testall method.""" try: import cPickle as pickle - except: + except ImportError: import pickle in_s = open(filename, "rb") aics_in = pickle.load(in_s) @@ -321,7 +320,7 @@ def classify(self, r, tolerance=0.05): exemplar_baseline = self.results[classes[c][0]][2] # Check baseline type and number of parameters - if type(baseline) != type(exemplar_baseline): + if type(baseline) is not type(exemplar_baseline): continue if baseline.npars() != exemplar_baseline.npars(): continue @@ -331,7 +330,7 @@ def classify(self, r, tolerance=0.05): if len(peaks) != len(exemplar_peaks): continue for p, ep in zip(peaks, exemplar_peaks): - if type(p) != type(ep): + if type(p) is not type(ep): badpeak = True break if p.npars() != ep.npars(): @@ -341,7 +340,6 @@ def classify(self, r, tolerance=0.05): continue # check peak values - current_psqval = [] for p, ep in zip(psqval, epsqval[c]): basediff = np.abs(np.sum(p - ep)) # if basediff > tolerance*np.sum(ep): @@ -383,7 +381,6 @@ def classify(self, r, tolerance=0.05): def makesortedclasses(self): self.sortedclasses = {} - em = self.ppe.error_method for dg in self.dgs: bestinclass = [] @@ -468,13 +465,15 @@ def plot3dclassprobs(self, **kwds): dGs - Sequence of dG values to plot. Default is all values. highlight - Sequence of dG values to highlight on plot. Default is []. classes - Sequence of indices of classes to plot. Default is all classes. - probfilter - [float1, float2]. Only show classes with maximum probability in given range. Default is [0., 1.] + probfilter - [float1, float2]. Only show classes with maximum probability in given range. + Default is [0., 1.] class_size - Report the size of each class as a "number" or "fraction". Default is "number". norm - A colors normalization for displaying number/fraction of models in class. Default is "auto". If equal to "full" determined by the total number of models. If equal to "auto" determined by the number of models in displayed classes. - cmap - A colormap or registered colormap name. Default is cm.jet. If class_size is "number" and norm is either "auto" - or "full" the map is converted to an indexed colormap. + cmap - A colormap or registered colormap name. Default is cm.jet. + If class_size is "number" and norm is either "auto" + or "full" the map is converted to an indexed colormap. highlight_cmap - A colormap or registered colormap name for coloring highlights. Default is cm.gray. title - True, False, or a string. Defaults to True, which displays some basic information about the graph. p_alpha - Probability graph alpha. (Colorbar remains opaque). Default is 0.7. @@ -495,12 +494,11 @@ def plot3dclassprobs(self, **kwds): from matplotlib import cm, colorbar, colors from matplotlib.collections import PolyCollection - from mpl_toolkits.mplot3d import Axes3D fig = kwds.pop("figure", plt.gcf()) ax = fig.add_subplot(kwds.pop("subplot", 111), projection="3d") - cbkwds = kwds.copy() + kwds.copy() # Resolve keywords (title resolved later) dGs = kwds.pop("dGs", self.dgs) @@ -530,41 +528,41 @@ def plot3dclassprobs(self, **kwds): # Define face colors fc = np.array([len(self.classes[z]) for z in zlabels]) - if class_size is "fraction": + if class_size == "fraction": fc = fc / float(len(self.results)) # Index the colormap if necessary - if class_size is "number": - if norm is "auto": + if class_size == "number": + if norm == "auto": indexedcolors = cmap(np.linspace(0.0, 1.0, np.max(fc))) cmap = colors.ListedColormap(indexedcolors) - elif norm is "full": + elif norm == "full": indexedcolors = cmap(np.linspace(0.0, 1.0, len(self.results))) cmap = colors.ListedColormap(indexedcolors) # A user-specified norm cannot be used to index a colormap. # Create proper norms for "auto" and "full" types. - if norm is "auto": - if class_size is "number": + if norm == "auto": + if class_size == "number": mic = np.min(fc) mac = np.max(fc) nc = mac - mic + 1 norm = colors.BoundaryNorm(np.linspace(mic, mac + 1, nc + 1), nc) - if class_size is "fraction": + if class_size == "fraction": norm = colors.Normalize() norm.autoscale(fc) - elif norm is "full": + elif norm == "full": mcolor = len(self.results) - if class_size is "number": + if class_size == "number": norm = colors.BoundaryNorm(np.linspace(0, mcolor + 1, mcolor + 2), mcolor + 1) - if class_size is "fraction": + if class_size == "fraction": norm = colors.Normalize(0.0, 1.0) zs = np.arange(len(zlabels)) poly = PolyCollection(verts, facecolors=cmap(norm(fc)), closed=False) poly.set_alpha(p_alpha) - cax = ax.add_collection3d(poly, zs=zs, zdir="y") + ax.add_collection3d(poly, zs=zs, zdir="y") # Highlight values of interest color_idx = np.linspace(0, 1, len(highlight)) @@ -602,12 +600,11 @@ def plot3dclassprobs(self, **kwds): ) if title is not False: - figtitle = fig.suptitle(title) + fig.suptitle(title) # Add colorbar if "cbpos" in kwds: cbpos = kwds.pop("cbpos") - aspect = cbpos[3] / cbpos[2] plt.tight_layout() # do it before cbaxis, so colorbar is ignored. transAtoF = ax.transAxes + fig.transFigure.inverted() rect = transforms.Bbox.from_bounds(*cbpos).transformed(transAtoF).bounds @@ -626,9 +623,9 @@ def plot3dclassprobs(self, **kwds): cb = colorbar.ColorbarBase(cbaxis, cmap=cmap, norm=norm, **kwds) - if class_size is "number": + if class_size == "number": cb.set_label("Models in class") - elif class_size is "fraction": + elif class_size == "fraction": cb.set_label("Fraction of models in class") return {"fig": fig, "axis": ax, "cb": cb, "cbaxis": cbaxis} diff --git a/diffpy/srmise/pdfdataset.py b/diffpy/srmise/pdfdataset.py index 034a93d..8671e28 100644 --- a/diffpy/srmise/pdfdataset.py +++ b/diffpy/srmise/pdfdataset.py @@ -20,7 +20,6 @@ """ -import copy import os.path import re import time From 978dca163b9920eea63b0cf148e5934f48a649ce Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 31 Jul 2024 02:16:07 +0800 Subject: [PATCH 16/65] fix formatting issue and typo in copyright (#35) --- diffpy/srmise/basefunction.py | 3 +-- diffpy/srmise/baselines/arbitrary.py | 14 +++++++------- diffpy/srmise/baselines/base.py | 2 +- diffpy/srmise/baselines/fromsequence.py | 2 +- diffpy/srmise/baselines/nanospherical.py | 4 ++-- diffpy/srmise/baselines/polynomial.py | 10 ++++------ diffpy/srmise/dataclusters.py | 2 +- diffpy/srmise/modelcluster.py | 2 +- diffpy/srmise/modelevaluators/aic.py | 2 +- diffpy/srmise/modelevaluators/aicc.py | 2 +- diffpy/srmise/modelevaluators/base.py | 2 +- diffpy/srmise/peaks/base.py | 2 +- diffpy/srmise/peaks/gaussian.py | 2 +- diffpy/srmise/peaks/gaussianoverr.py | 2 +- diffpy/srmise/peaks/terminationripples.py | 2 +- 15 files changed, 25 insertions(+), 28 deletions(-) diff --git a/diffpy/srmise/basefunction.py b/diffpy/srmise/basefunction.py index d0049a5..d5dd220 100644 --- a/diffpy/srmise/basefunction.py +++ b/diffpy/srmise/basefunction.py @@ -3,8 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/baselines/arbitrary.py b/diffpy/srmise/baselines/arbitrary.py index 6d1fe0d..bda2494 100644 --- a/diffpy/srmise/baselines/arbitrary.py +++ b/diffpy/srmise/baselines/arbitrary.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund @@ -89,11 +89,12 @@ def __init__(self, npars, valuef, jacobianf=None, estimatef=None, Cache=None): # TODO: figure out how the metadict can be used to save the functions # and use them again when a file is loaded... - metadict = {} - metadict["npars"] = (npars, repr) - metadict["valuef"] = (valuef, repr) - metadict["jacobianf"] = (jacobianf, repr) - metadict["estimatef"] = (estimatef, repr) + metadict = { + "npars": (npars, repr), + "valuef": (valuef, repr), + "jacobianf": (jacobianf, repr), + "estimatef": (estimatef, repr), + } BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) # Methods required by BaselineFunction #### @@ -182,7 +183,6 @@ def _transform_parametersraw(self, pars, in_format, out_format): def _valueraw(self, pars, r): """Return value of polynomial for the given parameters and r values. - Parameters Parameters pars: Sequence of parameters pars[0] = a_0 diff --git a/diffpy/srmise/baselines/base.py b/diffpy/srmise/baselines/base.py index 7324f8d..ca58155 100644 --- a/diffpy/srmise/baselines/base.py +++ b/diffpy/srmise/baselines/base.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/baselines/fromsequence.py b/diffpy/srmise/baselines/fromsequence.py index 120a359..4d4c553 100644 --- a/diffpy/srmise/baselines/fromsequence.py +++ b/diffpy/srmise/baselines/fromsequence.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/baselines/nanospherical.py b/diffpy/srmise/baselines/nanospherical.py index 4781312..b45113b 100644 --- a/diffpy/srmise/baselines/nanospherical.py +++ b/diffpy/srmise/baselines/nanospherical.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund @@ -35,7 +35,7 @@ class NanoSpherical(BaselineFunction): scale factor is 4*pi*rho_0, where rho_r is the nanoparticle density. gamma_0(r) Reference: - Guinier et. al. (1955). Small-angle Scattering from X-rays. New York: John Wiley & Sons, Inc. + Guinier et al. (1955). Small-angle Scattering from X-rays. New York: John Wiley & Sons, Inc. """ def __init__(self, Cache=None): diff --git a/diffpy/srmise/baselines/polynomial.py b/diffpy/srmise/baselines/polynomial.py index d7ba84a..70498be 100644 --- a/diffpy/srmise/baselines/polynomial.py +++ b/diffpy/srmise/baselines/polynomial.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund @@ -49,8 +49,7 @@ def __init__(self, degree, Cache=None): parameterdict["a_" + str(d)] = self.degree - d formats = ["internal"] default_formats = {"default_input": "internal", "default_output": "internal"} - metadict = {} - metadict["degree"] = (degree, repr) + metadict = {"degree": (degree, repr)} BaselineFunction.__init__(self, parameterdict, formats, default_formats, metadict, None, Cache) # Methods required by BaselineFunction #### @@ -95,12 +94,11 @@ def estimate_parameters(self, r, y): import numpy.linalg as la - A = np.array([r[cut_idx]]).T - slope = la.lstsq(A, y[cut_idx])[0][0] + a = np.array([r[cut_idx]]).T + slope = la.lstsq(a, y[cut_idx])[0][0] return np.array([slope, 0.0]) except Exception as e: emsg = "Error during estimation -- " + str(e) - raise raise SrMiseEstimationError(emsg) def _jacobianraw(self, pars, r, free): diff --git a/diffpy/srmise/dataclusters.py b/diffpy/srmise/dataclusters.py index 6b64333..d87a6b9 100644 --- a/diffpy/srmise/dataclusters.py +++ b/diffpy/srmise/dataclusters.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/modelcluster.py b/diffpy/srmise/modelcluster.py index d7d1742..cf952b2 100644 --- a/diffpy/srmise/modelcluster.py +++ b/diffpy/srmise/modelcluster.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/modelevaluators/aic.py b/diffpy/srmise/modelevaluators/aic.py index a8d870b..e3ee9e4 100644 --- a/diffpy/srmise/modelevaluators/aic.py +++ b/diffpy/srmise/modelevaluators/aic.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/modelevaluators/aicc.py b/diffpy/srmise/modelevaluators/aicc.py index 18e950b..be1052f 100644 --- a/diffpy/srmise/modelevaluators/aicc.py +++ b/diffpy/srmise/modelevaluators/aicc.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/modelevaluators/base.py b/diffpy/srmise/modelevaluators/base.py index b51e965..2e97b3a 100644 --- a/diffpy/srmise/modelevaluators/base.py +++ b/diffpy/srmise/modelevaluators/base.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peaks/base.py b/diffpy/srmise/peaks/base.py index 47e7a78..653a0e3 100644 --- a/diffpy/srmise/peaks/base.py +++ b/diffpy/srmise/peaks/base.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peaks/gaussian.py b/diffpy/srmise/peaks/gaussian.py index 2c12788..ad2530b 100644 --- a/diffpy/srmise/peaks/gaussian.py +++ b/diffpy/srmise/peaks/gaussian.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peaks/gaussianoverr.py b/diffpy/srmise/peaks/gaussianoverr.py index d5e8656..a5f9794 100644 --- a/diffpy/srmise/peaks/gaussianoverr.py +++ b/diffpy/srmise/peaks/gaussianoverr.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund diff --git a/diffpy/srmise/peaks/terminationripples.py b/diffpy/srmise/peaks/terminationripples.py index f916d3b..e814820 100644 --- a/diffpy/srmise/peaks/terminationripples.py +++ b/diffpy/srmise/peaks/terminationripples.py @@ -3,7 +3,7 @@ # # SrMise by Luke Granlund # (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York +# (c) 2024 trustees of Columbia University in the City of New York # All rights reserved. # # File coded by: Luke Granlund From c0d43e60d5db2ae3ba4e828b8fd34efb63ec102e Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 30 Jul 2024 20:37:51 -0400 Subject: [PATCH 17/65] clean out inits (#38) * clean out inits * [pre-commit.ci] auto fixes from pre-commit hooks * dataclusters.py, modelevaluators/aicc and modelparts.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- diffpy/srmise/baselines/__init__.py | 8 -------- diffpy/srmise/dataclusters.py | 16 ++++++++-------- diffpy/srmise/modelevaluators/__init__.py | 5 ----- diffpy/srmise/modelevaluators/aicc.py | 13 +++++++------ diffpy/srmise/modelparts.py | 22 ++++++++++------------ diffpy/srmise/peaks/__init__.py | 2 -- 6 files changed, 25 insertions(+), 41 deletions(-) diff --git a/diffpy/srmise/baselines/__init__.py b/diffpy/srmise/baselines/__init__.py index 0515732..5540acb 100644 --- a/diffpy/srmise/baselines/__init__.py +++ b/diffpy/srmise/baselines/__init__.py @@ -11,11 +11,3 @@ # See LICENSE.txt for license information. # ############################################################################## - -__all__ = ["base", "arbitrary", "fromsequence", "nanospherical", "polynomial"] - -from arbitrary import Arbitrary -from base import Baseline -from fromsequence import FromSequence -from nanospherical import NanoSpherical -from polynomial import Polynomial diff --git a/diffpy/srmise/dataclusters.py b/diffpy/srmise/dataclusters.py index d87a6b9..1eabb26 100644 --- a/diffpy/srmise/dataclusters.py +++ b/diffpy/srmise/dataclusters.py @@ -365,11 +365,11 @@ def cut(self, idx): def cluster_boundaries(self): """Return sequence with (x,y) of all cluster boundaries.""" boundaries = [] - for l in self.clusters: - xlo = np.mean(self.x[l[0] - 1 : l[0] + 1]) - ylo = np.mean(self.y[l[0] - 1 : l[0] + 1]) - xhi = np.mean(self.x[l[1] : l[1] + 2]) - yhi = np.mean(self.y[l[1] : l[1] + 2]) + for cluster in self.clusters: + xlo = np.mean(self.x[cluster[0] - 1 : cluster[0] + 1]) + ylo = np.mean(self.y[cluster[0] - 1 : cluster[0] + 1]) + xhi = np.mean(self.x[cluster[1] : cluster[1] + 2]) + yhi = np.mean(self.y[cluster[1] : cluster[1] + 2]) boundaries.append((xlo, ylo)) boundaries.append((xhi, yhi)) return np.unique(boundaries) @@ -416,9 +416,9 @@ def animate(self): all_lines[i].set_ydata([0, height]) ax.draw_artist(all_lines[i]) else: - l = plt.axvline(b[0], 0, height, color="k", animated=True) - ax.draw_artist(l) - all_lines.append(l) + line = plt.axvline(b[0], 0, height, color="k", animated=True) + ax.draw_artist(line) + all_lines.append(line) canvas.blit(ax.bbox) self.clusters = clusters diff --git a/diffpy/srmise/modelevaluators/__init__.py b/diffpy/srmise/modelevaluators/__init__.py index 1d80760..5540acb 100644 --- a/diffpy/srmise/modelevaluators/__init__.py +++ b/diffpy/srmise/modelevaluators/__init__.py @@ -11,8 +11,3 @@ # See LICENSE.txt for license information. # ############################################################################## - -__all__ = ["base", "aic", "aicc"] - -from aic import AIC -from aicc import AICc diff --git a/diffpy/srmise/modelevaluators/aicc.py b/diffpy/srmise/modelevaluators/aicc.py index be1052f..e1b13f2 100644 --- a/diffpy/srmise/modelevaluators/aicc.py +++ b/diffpy/srmise/modelevaluators/aicc.py @@ -16,7 +16,6 @@ import numpy as np -import diffpy.srmise.srmiselog from diffpy.srmise.modelevaluators.base import ModelEvaluator from diffpy.srmise.srmiseerrors import SrMiseModelEvaluatorError @@ -72,7 +71,7 @@ def evaluate(self, fit, count_fixed=False, kshift=0): logger.warn("AICc.evaluate(): too few data to evaluate quality reliably.") n = self.minpoints(k) - if self.chisq == None: + if self.chisq is None: self.chisq = self.chi_squared(fit.value(), fit.y_cluster, fit.error_cluster) self.stat = self.chisq + self.parpenalty(k, n) @@ -96,10 +95,12 @@ def parpenalty(self, k, n): return (2 * k + float(2 * k * (k + 1)) / (n - k - 1)) * fudgefactor def growth_justified(self, fit, k_prime): - """Returns whether adding k_prime parameters to the given model (ModelCluster) is justified given the current quality of the fit. - The assumption is that adding k_prime parameters will result in "effectively 0" chiSquared cost, and so adding it is justified - if the cost of adding these parameters is less than the current chiSquared cost. The validity of this assumption (which - depends on an unknown chiSquared value) and the impact of the errors used should be examined more thoroughly in the future. + """Is adding k_prime parameters to ModelCluster justified given the current quality of the fit. + + The assumption is that adding k_prime parameters will result in "effectively 0" chiSquared cost, + and so adding it is justified if the cost of adding these parameters is less than the current + chiSquared cost. The validity of this assumption (which depends on an unknown chiSquared value) + and the impact of the errors used should be examined more thoroughly in the future. """ if self.chisq is None: diff --git a/diffpy/srmise/modelparts.py b/diffpy/srmise/modelparts.py index 8dd0c40..9bd3f14 100644 --- a/diffpy/srmise/modelparts.py +++ b/diffpy/srmise/modelparts.py @@ -21,20 +21,18 @@ import logging -import numpy as np -from scipy.optimize import leastsq - -from diffpy.srmise import srmiselog -from diffpy.srmise.srmiseerrors import * - -logger = logging.getLogger("diffpy.srmise") - import matplotlib.pyplot as plt +import numpy as np # Output of scipy.optimize.leastsq for a single parameter changed in scipy 0.8.0 # Before it returned a scalar, later it returned an array of length 1. import pkg_resources as pr +from scipy.optimize import leastsq +from diffpy.srmise import srmiselog +from diffpy.srmise.srmiseerrors import SrMiseFitError, SrMiseStaticOwnerError, SrMiseUndefinedCovarianceError + +logger = logging.getLogger("diffpy.srmise") __spv__ = pr.get_distribution("scipy").version __oldleastsqbehavior__ = pr.parse_version(__spv__) < pr.parse_version("0.8.0") @@ -98,7 +96,7 @@ def fit( # raise SrMiseFitError(emsg) return - if range == None: + if range is None: range = slice(None) args = (r, y, y_error, range) @@ -203,7 +201,7 @@ def fit( cov.setcovariance(self, pcov * np.sum(fvec**2) / dof) try: cov.transform(in_format="internal", out_format=cov_format) - except SrMiseUndefinedCovarianceError as e: + except SrMiseUndefinedCovarianceError: logger.warn("Covariance not defined. Fit may not have converged.") return @@ -332,7 +330,7 @@ def covariance(self, format="internal", **kwds): for k, v in kwds.items(): try: - idx = int(k[1:]) + int(k[1:]) except ValueError: emsg = "Invalid format keyword '%s'. They must be specified as 'f0', 'f1', etc." % k raise ValueError(emsg) @@ -559,7 +557,7 @@ def npars(self, count_fixed=True): if count_fixed: return self._owner.npars else: - return (self.free == True).sum() + return (self.free is True).sum() def __str__(self): """Return string representation of ModelPart parameters.""" diff --git a/diffpy/srmise/peaks/__init__.py b/diffpy/srmise/peaks/__init__.py index ea3cdfc..5540acb 100644 --- a/diffpy/srmise/peaks/__init__.py +++ b/diffpy/srmise/peaks/__init__.py @@ -11,5 +11,3 @@ # See LICENSE.txt for license information. # ############################################################################## - -__all__ = ["base", "gaussian", "gaussianoverr", "terminationripples"] From 9a2d7ccff45ce9b8f32b88d7096a86028cd85dc0 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 30 Jul 2024 20:59:09 -0400 Subject: [PATCH 18/65] peakextraction.py and init (#40) --- diffpy/srmise/__init__.py | 3 +++ diffpy/srmise/peakextraction.py | 17 ++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/diffpy/srmise/__init__.py b/diffpy/srmise/__init__.py index f746b0f..dc7bb07 100644 --- a/diffpy/srmise/__init__.py +++ b/diffpy/srmise/__init__.py @@ -16,3 +16,6 @@ from diffpy.srmise.version import __version__ + +# silence the pyflakes syntax checker +assert __version__ or True diff --git a/diffpy/srmise/peakextraction.py b/diffpy/srmise/peakextraction.py index 4693d66..7ac50bc 100644 --- a/diffpy/srmise/peakextraction.py +++ b/diffpy/srmise/peakextraction.py @@ -20,17 +20,15 @@ import matplotlib.pyplot as plt import numpy as np +from diffpy.srmise import srmiselog from diffpy.srmise.baselines import Baseline from diffpy.srmise.dataclusters import DataClusters from diffpy.srmise.modelcluster import ModelCluster, ModelCovariance -from diffpy.srmise.modelparts import ModelPart, ModelParts from diffpy.srmise.peaks import Peak, Peaks -from diffpy.srmise.srmiseerrors import * +from diffpy.srmise.srmiseerrors import SrMiseDataFormatError, SrMiseEstimationError, SrMiseFileError logger = logging.getLogger("diffpy.srmise") -from diffpy.srmise import srmiselog - class PeakExtraction(object): """Class for peak extraction. @@ -461,8 +459,8 @@ def readstr(self, datastring): # raise SrMiseDataFormatError if something goes wrong try: for line in start_data.split("\n"): - l = line.split() - if len(arrays) != len(l): + split_line = line.split() + if len(arrays) != len(split_line): emsg = "Number of value fields does not match that given by '%s'" % start_data_info for a, v in zip(arrays, line.split()): a.append(float(v)) @@ -892,7 +890,8 @@ def extract_single(self, recursion_depth=1): # left_data, right_data: indices defining the extent of the "interpeak range" for x, etc. near_peaks = np.array([], dtype=np.int) - # interpeak range goes from peak to peak of next nearest peaks, although their contributions to the data are still removed. + # interpeak range goes from peak to peak of next nearest peaks, although their contributions + # to the data are still removed. if pivot == 0: # No peaks left of border_x! left_data = full_cluster.slice.indices(len(x))[0] @@ -1035,7 +1034,8 @@ def extract_single(self, recursion_depth=1): # left_data, right_data: indices defining the extent of the "interpeak range" for x, etc. near_peaks = np.array([], dtype=np.int) - # interpeak range goes from peak to peak of next nearest peaks, although their contributions to the data are still removed. + # interpeak range goes from peak to peak of next nearest peaks, although their contributions + # to the data are still removed. if pivot == 0: # No peaks left of border_x! left_data = new_cluster.slice.indices(len(x))[0] @@ -1307,7 +1307,6 @@ def fit_single(self): from numpy.random import randn - from diffpy.srmise import srmiselog from diffpy.srmise.modelevaluators import AICc from diffpy.srmise.peaks import GaussianOverR From d237df0bf9363415e60d96a6d93408adad7f41b4 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:36:01 +0800 Subject: [PATCH 19/65] move untrack doc and requirement files (#41) * move untrack doc and requirement files * add requirement in run.txt * [pre-commit.ci] auto fixes from pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .gitattributes | 1 + doc/source/_static/.placeholder | 0 .../api/diffpy.srmise.example_package.rst | 31 ++ doc/source/api/diffpy.srmise.rst | 30 ++ doc/source/conf.py | 289 ++++++++++++++++++ doc/source/index.rst | 44 +++ doc/source/license.rst | 39 +++ doc/source/release.rst | 5 + requirements/README.txt | 11 + requirements/build.txt | 2 + requirements/docs.txt | 4 + requirements/pip.txt | 0 requirements/test.txt | 1 + 13 files changed, 457 insertions(+) create mode 100644 .gitattributes create mode 100644 doc/source/_static/.placeholder create mode 100644 doc/source/api/diffpy.srmise.example_package.rst create mode 100644 doc/source/api/diffpy.srmise.rst create mode 100644 doc/source/conf.py create mode 100644 doc/source/index.rst create mode 100644 doc/source/license.rst create mode 100644 doc/source/release.rst create mode 100644 requirements/README.txt create mode 100644 requirements/build.txt create mode 100644 requirements/docs.txt create mode 100644 requirements/pip.txt diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e7c3aea --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +diffpy.srmise/_version.py export-subst diff --git a/doc/source/_static/.placeholder b/doc/source/_static/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/doc/source/api/diffpy.srmise.example_package.rst b/doc/source/api/diffpy.srmise.example_package.rst new file mode 100644 index 0000000..8c7f6ad --- /dev/null +++ b/doc/source/api/diffpy.srmise.example_package.rst @@ -0,0 +1,31 @@ +.. _example_package documentation: + +|title| +======= + +.. |title| replace:: diffpy.srmise.example_package package + +.. automodule:: diffpy.srmise.example_package + :members: + :undoc-members: + :show-inheritance: + +|foo| +----- + +.. |foo| replace:: diffpy.srmise.example_package.foo module + +.. automodule:: diffpy.srmise.example_package.foo + :members: + :undoc-members: + :show-inheritance: + +|bar| +----- + +.. |bar| replace:: diffpy.srmise.example_package.bar module + +.. automodule:: diffpy.srmise.example_package.foo + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/diffpy.srmise.rst b/doc/source/api/diffpy.srmise.rst new file mode 100644 index 0000000..958efe0 --- /dev/null +++ b/doc/source/api/diffpy.srmise.rst @@ -0,0 +1,30 @@ +:tocdepth: -1 + +|title| +======= + +.. |title| replace:: diffpy.srmise package + +.. automodule:: diffpy.srmise + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + diffpy.srmise.example_package + +Submodules +---------- + +|module| +-------- + +.. |module| replace:: diffpy.srmise.example_submodule module + +.. automodule:: diffpy.srmise.example_submodule + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..65d3685 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# diffpy.srmise documentation build configuration file, created by +# sphinx-quickstart on Thu Jan 30 15:49:41 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import time +from importlib.metadata import version +from pathlib import Path + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use Path().resolve() to make it absolute, like shown here. +# sys.path.insert(0, str(Path(".").resolve())) +sys.path.insert(0, str(Path("../..").resolve())) +sys.path.insert(0, str(Path("../../src").resolve())) + +# abbreviations +ab_authors = "Billinge Group members and community contributors" + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx_rtd_theme", + "m2r", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = [".rst", ".md"] + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = "diffpy.srmise" +copyright = "%Y, The Trustees of Columbia University in the City of New York" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +fullversion = version(project) +# The short X.Y version. +version = "".join(fullversion.split(".post")[:1]) +# The full version, including alpha/beta/rc tags. +release = fullversion + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +today = time.strftime("%B %d, %Y", time.localtime()) +year = today.split()[-1] +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' +# substitute YEAR in the copyright string +copyright = copyright.replace("%Y", year) + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ["build"] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# A list of ignored prefixes for module index sorting. +modindex_common_prefix = ["diffpy.srmise"] + +# Display all warnings for missing links. +nitpicky = True + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +html_theme_options = { + "navigation_with_keys": "true", +} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +basename = "diffpy.srmise".replace(" ", "").replace(".", "") +htmlhelp_basename = basename + "doc" + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ("index", "diffpy.srmise.tex", "diffpy.srmise Documentation", ab_authors, "manual"), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [("index", "diffpy.srmise", "diffpy.srmise Documentation", ab_authors, 1)] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + "index", + "diffpy.srmise", + "diffpy.srmise Documentation", + ab_authors, + "diffpy.srmise", + "One line description of project.", + "Miscellaneous", + ), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +# intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..89fa460 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,44 @@ +####### +|title| +####### + +.. |title| replace:: diffpy.srmise documentation + +diffpy.srmise - Peak extraction and peak fitting tool for atomic pair distribution functions.. + +| Software version |release|. +| Last updated |today|. + +======= +Authors +======= + +diffpy.srmise is developed by Billinge Group +and its community contributors. + +For a detailed list of contributors see +https://github.com/diffpy/diffpy.srmise/graphs/contributors. + +============ +Installation +============ + +See the `README `_ +file included with the distribution. + +================= +Table of contents +================= +.. toctree:: + :titlesonly: + + license + release + Package API + +======= +Indices +======= + +* :ref:`genindex` +* :ref:`search` diff --git a/doc/source/license.rst b/doc/source/license.rst new file mode 100644 index 0000000..cfab61c --- /dev/null +++ b/doc/source/license.rst @@ -0,0 +1,39 @@ +:tocdepth: -1 + +.. index:: license + +License +####### + +OPEN SOURCE LICENSE AGREEMENT +============================= +BSD 3-Clause License + +Copyright (c) 2024, The Trustees of Columbia University in +the City of New York. +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/source/release.rst b/doc/source/release.rst new file mode 100644 index 0000000..27cd0cc --- /dev/null +++ b/doc/source/release.rst @@ -0,0 +1,5 @@ +:tocdepth: -1 + +.. index:: release notes + +.. include:: ../../CHANGELOG.rst diff --git a/requirements/README.txt b/requirements/README.txt new file mode 100644 index 0000000..dc34909 --- /dev/null +++ b/requirements/README.txt @@ -0,0 +1,11 @@ +# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! +# +# This directory is where you should place your project dependencies. +# "pip.txt" should contain all required packages not available on conda. +# All other files should contain only packages available to download from conda. +# build.txt should contain all packages required to build (not run) the project. +# run.txt should contain all packages (including optional packages) required for a user to run the program. +# test.txt should contain all packages required for the testing suite and to ensure all tests pass. +# docs.txt should contain all packages required for building the package documentation page. +# +# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! diff --git a/requirements/build.txt b/requirements/build.txt new file mode 100644 index 0000000..f72d870 --- /dev/null +++ b/requirements/build.txt @@ -0,0 +1,2 @@ +python +setuptools diff --git a/requirements/docs.txt b/requirements/docs.txt new file mode 100644 index 0000000..ab17b1c --- /dev/null +++ b/requirements/docs.txt @@ -0,0 +1,4 @@ +sphinx +sphinx_rtd_theme +doctr +m2r diff --git a/requirements/pip.txt b/requirements/pip.txt new file mode 100644 index 0000000..e69de29 diff --git a/requirements/test.txt b/requirements/test.txt index 7666f95..6f9ccf8 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -2,3 +2,4 @@ flake8 pytest codecov coverage +pytest-env From f3cdb5893297ca82bcfd691ab2456b8e4ad1e74c Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 31 Jul 2024 11:04:31 +0800 Subject: [PATCH 20/65] add pyproject.toml (#42) * add pyproject.toml * [pre-commit.ci] auto fixes from pre-commit hooks * update classifiers pyproject.toml * Delete setup.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- diffpy/srmise/modelparts.py | 2 +- pyproject.toml | 54 +++++++++++++++++++ setup.py | 105 ------------------------------------ 3 files changed, 55 insertions(+), 106 deletions(-) delete mode 100755 setup.py diff --git a/diffpy/srmise/modelparts.py b/diffpy/srmise/modelparts.py index 9bd3f14..37d6e0c 100644 --- a/diffpy/srmise/modelparts.py +++ b/diffpy/srmise/modelparts.py @@ -172,7 +172,7 @@ def fit( y, r, (y - self.value(r, range=range)) - 1.1 * (max(y) - min(y)), - *[i for sublist in [[r, p.value(r, range=range)] for p in self] for i in sublist] + *[i for sublist in [[r, p.value(r, range=range)] for p in self] for i in sublist], ) plt.draw() diff --git a/pyproject.toml b/pyproject.toml index 3239179..897eb5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,55 @@ +[build-system] +requires = ["setuptools>=62.0", "setuptools-git-versioning<2"] +build-backend = "setuptools.build_meta" + +[project] +name = "diffpy.srmise" +dynamic=['version'] +authors = [ + { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, +] +maintainers = [ + { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, +] +description = "Peak extraction and peak fitting tool for atomic pair distribution functions." +keywords = ['peak extraction fitting PDF AIC multimodeling'] +readme = "README.rst" +requires-python = ">=3.10" +classifiers = [ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'Intended Audience :: Education', + 'License :: OSI Approved :: BSD License', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Topic :: Scientific/Engineering :: Physics', + 'Topic :: Scientific/Engineering :: Chemistry', + 'Topic :: Software Development :: Libraries', +] + +[project.urls] +Homepage = "https://github.com/diffpy/diffpy.srmise/" +Issues = "https://github.com/diffpy/diffpy.srmise/issues/" + +[tool.setuptools-git-versioning] +enabled = true +template = "{tag}" +dev_template = "{tag}" +dirty_template = "{tag}" + +[tool.setuptools.packages.find] +where = ["src"] # list of folders that contain the packages (["."] by default) +include = ["*"] # package names should match these glob patterns (["*"] by default) +exclude = ["diffpy.srmise.tests*"] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) + [tool.black] line-length = 115 include = '\.pyi?$' @@ -8,6 +60,8 @@ exclude = ''' | \.mypy_cache | \.tox | \.venv + | \.rst + | \.txt | _build | buck-out | build diff --git a/setup.py b/setup.py deleted file mode 100755 index 9828c76..0000000 --- a/setup.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python - -# Installation script for diffpy.srmise - -"""diffpy.srmise - Peak extraction/fitting tool for pair distribution functions - -Packages: diffpy.srmise -""" - -import os - -from setuptools import find_packages, setup - -# versioncfgfile holds version data for git commit hash and date. -# It must reside in the same directory as version.py. -MYDIR = os.path.dirname(os.path.abspath(__file__)) -versioncfgfile = os.path.join(MYDIR, "diffpy/srmise/version.cfg") - - -def gitinfo(): - from subprocess import PIPE, Popen - - kw = dict(stdout=PIPE, cwd=MYDIR) - proc = Popen(["git", "describe", "--match=v[[:digit:]]*"], **kw) - desc = proc.stdout.read() - proc = Popen(["git", "log", "-1", "--format=%H %at %ai"], **kw) - glog = proc.stdout.read() - rv = {} - rv["version"] = "-".join(desc.strip().split("-")[:2]).lstrip("v") - rv["commit"], rv["timestamp"], rv["date"] = glog.strip().split(None, 2) - return rv - - -def getversioncfg(): - from ConfigParser import SafeConfigParser - - cp = SafeConfigParser() - cp.read(versioncfgfile) - gitdir = os.path.join(MYDIR, ".git") - if not os.path.isdir(gitdir): - return cp - try: - g = gitinfo() - except OSError: - return cp - d = cp.defaults() - if g["version"] != d.get("version") or g["commit"] != d.get("commit"): - cp.set("DEFAULT", "version", g["version"]) - cp.set("DEFAULT", "commit", g["commit"]) - cp.set("DEFAULT", "date", g["date"]) - cp.set("DEFAULT", "timestamp", g["timestamp"]) - cp.write(open(versioncfgfile, "w")) - return cp - - -versiondata = getversioncfg() - -# define distribution, but make this module importable -setup_args = dict( - name="diffpy.srmise", - version=versiondata.get("DEFAULT", "version"), - namespace_packages=["diffpy"], - packages=find_packages(), - include_package_data=True, - zip_safe=False, - # Dependencies - # numpy - # scipy - # matplotlib >= 1.1.0 - install_requires=["matplotlib >= 1.1.0", "numpy", "scipy"], - # other arguments here... - entry_points={ - "console_scripts": [ - "srmise = diffpy.srmise.applications.extract:main", - "srmiseplot = diffpy.srmise.applications.plot:main", - ] - }, - author="Luke Granlund", - author_email="luke.r.granlund@gmail.com", - description=("Peak extraction and peak fitting tool for atomic " "pair distribution functions."), - license="BSD-style license", - url="https://github.com/diffpy/diffpy.srmise/", - keywords="peak extraction fitting PDF AIC multimodeling", - classifiers=[ - # List of possible values at - # http://pypi.python.org/pypi?:action=list_classifiers - "Development Status :: 3 - Alpha", - "Environment :: Console", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: BSD License", - "Operating System :: MacOS", - "Operating System :: POSIX", - "Operating System :: Microsoft :: Windows", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Topic :: Scientific/Engineering :: Chemistry", - "Topic :: Scientific/Engineering :: Physics", - "Topic :: Software Development :: Libraries", - ], -) - -if __name__ == "__main__": - setup(**setup_args) From 9ecf5e8a970921721ca415abe248dee24f440e07 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:09:02 +0800 Subject: [PATCH 21/65] move diffpy files to src dir (#44) * move diffpy files to src dir * [pre-commit.ci] auto fixes from pre-commit hooks * add Luke to authors --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- diffpy/__init__.py | 26 - diffpy/srmise/__init__.py | 21 - diffpy/srmise/version.py | 31 - pyproject.toml | 1 + src/diffpy/__init__.py | 23 + src/diffpy/srmise/__init__.py | 24 + .../diffpy}/srmise/applications/__init__.py | 0 .../diffpy}/srmise/applications/extract.py | 0 .../diffpy}/srmise/applications/plot.py | 0 {diffpy => src/diffpy}/srmise/basefunction.py | 0 .../diffpy}/srmise/baselines/__init__.py | 0 .../diffpy}/srmise/baselines/arbitrary.py | 0 .../diffpy}/srmise/baselines/base.py | 0 .../diffpy}/srmise/baselines/fromsequence.py | 0 .../diffpy}/srmise/baselines/nanospherical.py | 0 .../diffpy}/srmise/baselines/polynomial.py | 0 {diffpy => src/diffpy}/srmise/dataclusters.py | 0 {diffpy => src/diffpy}/srmise/modelcluster.py | 0 .../srmise/modelevaluators/__init__.py | 0 .../diffpy}/srmise/modelevaluators/aic.py | 0 .../diffpy}/srmise/modelevaluators/aicc.py | 0 .../diffpy}/srmise/modelevaluators/base.py | 0 src/diffpy/srmise/modelparts.py | 610 ++++++++++++++++++ .../diffpy}/srmise/multimodelselection.py | 0 {diffpy => src/diffpy}/srmise/pdfdataset.py | 0 .../diffpy}/srmise/pdfpeakextraction.py | 0 .../diffpy}/srmise/peakextraction.py | 0 .../diffpy}/srmise/peaks/__init__.py | 0 {diffpy => src/diffpy}/srmise/peaks/base.py | 0 .../diffpy}/srmise/peaks/gaussian.py | 0 .../diffpy}/srmise/peaks/gaussianoverr.py | 0 .../srmise/peaks/terminationripples.py | 0 .../diffpy}/srmise/peakstability.py | 0 {diffpy => src/diffpy}/srmise/srmiseerrors.py | 0 {diffpy => src/diffpy}/srmise/srmiselog.py | 0 src/diffpy/srmise/tests/__init__.py | 0 src/diffpy/srmise/tests/conftest.py | 19 + src/diffpy/srmise/tests/debug.py | 35 + src/diffpy/srmise/tests/run.py | 34 + src/diffpy/srmise/version.py | 26 + 40 files changed, 772 insertions(+), 78 deletions(-) delete mode 100644 diffpy/__init__.py delete mode 100644 diffpy/srmise/__init__.py delete mode 100644 diffpy/srmise/version.py create mode 100644 src/diffpy/__init__.py create mode 100644 src/diffpy/srmise/__init__.py rename {diffpy => src/diffpy}/srmise/applications/__init__.py (100%) rename {diffpy => src/diffpy}/srmise/applications/extract.py (100%) rename {diffpy => src/diffpy}/srmise/applications/plot.py (100%) rename {diffpy => src/diffpy}/srmise/basefunction.py (100%) rename {diffpy => src/diffpy}/srmise/baselines/__init__.py (100%) rename {diffpy => src/diffpy}/srmise/baselines/arbitrary.py (100%) rename {diffpy => src/diffpy}/srmise/baselines/base.py (100%) rename {diffpy => src/diffpy}/srmise/baselines/fromsequence.py (100%) rename {diffpy => src/diffpy}/srmise/baselines/nanospherical.py (100%) rename {diffpy => src/diffpy}/srmise/baselines/polynomial.py (100%) rename {diffpy => src/diffpy}/srmise/dataclusters.py (100%) rename {diffpy => src/diffpy}/srmise/modelcluster.py (100%) rename {diffpy => src/diffpy}/srmise/modelevaluators/__init__.py (100%) rename {diffpy => src/diffpy}/srmise/modelevaluators/aic.py (100%) rename {diffpy => src/diffpy}/srmise/modelevaluators/aicc.py (100%) rename {diffpy => src/diffpy}/srmise/modelevaluators/base.py (100%) create mode 100644 src/diffpy/srmise/modelparts.py rename {diffpy => src/diffpy}/srmise/multimodelselection.py (100%) rename {diffpy => src/diffpy}/srmise/pdfdataset.py (100%) rename {diffpy => src/diffpy}/srmise/pdfpeakextraction.py (100%) rename {diffpy => src/diffpy}/srmise/peakextraction.py (100%) rename {diffpy => src/diffpy}/srmise/peaks/__init__.py (100%) rename {diffpy => src/diffpy}/srmise/peaks/base.py (100%) rename {diffpy => src/diffpy}/srmise/peaks/gaussian.py (100%) rename {diffpy => src/diffpy}/srmise/peaks/gaussianoverr.py (100%) rename {diffpy => src/diffpy}/srmise/peaks/terminationripples.py (100%) rename {diffpy => src/diffpy}/srmise/peakstability.py (100%) rename {diffpy => src/diffpy}/srmise/srmiseerrors.py (100%) rename {diffpy => src/diffpy}/srmise/srmiselog.py (100%) create mode 100644 src/diffpy/srmise/tests/__init__.py create mode 100644 src/diffpy/srmise/tests/conftest.py create mode 100644 src/diffpy/srmise/tests/debug.py create mode 100644 src/diffpy/srmise/tests/run.py create mode 100644 src/diffpy/srmise/version.py diff --git a/diffpy/__init__.py b/diffpy/__init__.py deleted file mode 100644 index 7170a04..0000000 --- a/diffpy/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -######################################################################## -# -# diffpy by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2008 The Trustees of Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Pavol Juhas -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. -# -######################################################################## - - -"""diffpy - tools for structure analysis by diffraction. - -Blank namespace package. -""" - - -__import__("pkg_resources").declare_namespace(__name__) - - -# End of file diff --git a/diffpy/srmise/__init__.py b/diffpy/srmise/__init__.py deleted file mode 100644 index dc7bb07..0000000 --- a/diffpy/srmise/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York -# All rights reserved. -# -# File coded by: Luke Granlund -# -# See LICENSE.txt for license information. -# -############################################################################## - -"""Tools for peak extraction from PDF.""" - - -from diffpy.srmise.version import __version__ - -# silence the pyflakes syntax checker -assert __version__ or True diff --git a/diffpy/srmise/version.py b/diffpy/srmise/version.py deleted file mode 100644 index 094af4a..0000000 --- a/diffpy/srmise/version.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York -# All rights reserved. -# -# File coded by: Luke Granlund -# -# See LICENSE.txt for license information. -# -############################################################################## - -"""Definition of __version__, __date__, __gitsha__. -""" - -from ConfigParser import SafeConfigParser -from pkg_resources import resource_stream - -# obtain version information from the version.cfg file -cp = SafeConfigParser() -cp.readfp(resource_stream(__name__, "version.cfg")) - -__version__ = cp.get("DEFAULT", "version") -__date__ = cp.get("DEFAULT", "date") -__gitsha__ = cp.get("DEFAULT", "commit") - -del cp - -# End of file diff --git a/pyproject.toml b/pyproject.toml index 897eb5a..3c8f23b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ name = "diffpy.srmise" dynamic=['version'] authors = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, + {name="Luke Granlund", email="granlund@pa.msu.edu"}, ] maintainers = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py new file mode 100644 index 0000000..377a0f9 --- /dev/null +++ b/src/diffpy/__init__.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Billinge Group members and community contributors. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srmise/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## + +"""Blank namespace package for module diffpy.""" + + +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) + +# End of file diff --git a/src/diffpy/srmise/__init__.py b/src/diffpy/srmise/__init__.py new file mode 100644 index 0000000..65eb42b --- /dev/null +++ b/src/diffpy/srmise/__init__.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Billinge Group members and community contributors. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srmise/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## + +"""Peak extraction and peak fitting tool for atomic pair distribution functions.""" + +# package version +from diffpy.srmise.version import __version__ + +# silence the pyflakes syntax checker +assert __version__ or True + +# End of file diff --git a/diffpy/srmise/applications/__init__.py b/src/diffpy/srmise/applications/__init__.py similarity index 100% rename from diffpy/srmise/applications/__init__.py rename to src/diffpy/srmise/applications/__init__.py diff --git a/diffpy/srmise/applications/extract.py b/src/diffpy/srmise/applications/extract.py similarity index 100% rename from diffpy/srmise/applications/extract.py rename to src/diffpy/srmise/applications/extract.py diff --git a/diffpy/srmise/applications/plot.py b/src/diffpy/srmise/applications/plot.py similarity index 100% rename from diffpy/srmise/applications/plot.py rename to src/diffpy/srmise/applications/plot.py diff --git a/diffpy/srmise/basefunction.py b/src/diffpy/srmise/basefunction.py similarity index 100% rename from diffpy/srmise/basefunction.py rename to src/diffpy/srmise/basefunction.py diff --git a/diffpy/srmise/baselines/__init__.py b/src/diffpy/srmise/baselines/__init__.py similarity index 100% rename from diffpy/srmise/baselines/__init__.py rename to src/diffpy/srmise/baselines/__init__.py diff --git a/diffpy/srmise/baselines/arbitrary.py b/src/diffpy/srmise/baselines/arbitrary.py similarity index 100% rename from diffpy/srmise/baselines/arbitrary.py rename to src/diffpy/srmise/baselines/arbitrary.py diff --git a/diffpy/srmise/baselines/base.py b/src/diffpy/srmise/baselines/base.py similarity index 100% rename from diffpy/srmise/baselines/base.py rename to src/diffpy/srmise/baselines/base.py diff --git a/diffpy/srmise/baselines/fromsequence.py b/src/diffpy/srmise/baselines/fromsequence.py similarity index 100% rename from diffpy/srmise/baselines/fromsequence.py rename to src/diffpy/srmise/baselines/fromsequence.py diff --git a/diffpy/srmise/baselines/nanospherical.py b/src/diffpy/srmise/baselines/nanospherical.py similarity index 100% rename from diffpy/srmise/baselines/nanospherical.py rename to src/diffpy/srmise/baselines/nanospherical.py diff --git a/diffpy/srmise/baselines/polynomial.py b/src/diffpy/srmise/baselines/polynomial.py similarity index 100% rename from diffpy/srmise/baselines/polynomial.py rename to src/diffpy/srmise/baselines/polynomial.py diff --git a/diffpy/srmise/dataclusters.py b/src/diffpy/srmise/dataclusters.py similarity index 100% rename from diffpy/srmise/dataclusters.py rename to src/diffpy/srmise/dataclusters.py diff --git a/diffpy/srmise/modelcluster.py b/src/diffpy/srmise/modelcluster.py similarity index 100% rename from diffpy/srmise/modelcluster.py rename to src/diffpy/srmise/modelcluster.py diff --git a/diffpy/srmise/modelevaluators/__init__.py b/src/diffpy/srmise/modelevaluators/__init__.py similarity index 100% rename from diffpy/srmise/modelevaluators/__init__.py rename to src/diffpy/srmise/modelevaluators/__init__.py diff --git a/diffpy/srmise/modelevaluators/aic.py b/src/diffpy/srmise/modelevaluators/aic.py similarity index 100% rename from diffpy/srmise/modelevaluators/aic.py rename to src/diffpy/srmise/modelevaluators/aic.py diff --git a/diffpy/srmise/modelevaluators/aicc.py b/src/diffpy/srmise/modelevaluators/aicc.py similarity index 100% rename from diffpy/srmise/modelevaluators/aicc.py rename to src/diffpy/srmise/modelevaluators/aicc.py diff --git a/diffpy/srmise/modelevaluators/base.py b/src/diffpy/srmise/modelevaluators/base.py similarity index 100% rename from diffpy/srmise/modelevaluators/base.py rename to src/diffpy/srmise/modelevaluators/base.py diff --git a/src/diffpy/srmise/modelparts.py b/src/diffpy/srmise/modelparts.py new file mode 100644 index 0000000..37d6e0c --- /dev/null +++ b/src/diffpy/srmise/modelparts.py @@ -0,0 +1,610 @@ +#!/usr/bin/env python +############################################################################## +# +# SrMise by Luke Granlund +# (c) 2014 trustees of the Michigan State University +# (c) 2024 trustees of Columia University in the City of New York +# All rights reserved. +# +# File coded by: Luke Granlund +# +# See LICENSE.txt for license information. +# +############################################################################## +"""Module for representing instances of mathematical functions. + +Classes +------- +ModelPart: Superclass of Peak and Baseline +ModelParts: Collection (list) of ModelPart instances. +""" + +import logging + +import matplotlib.pyplot as plt +import numpy as np + +# Output of scipy.optimize.leastsq for a single parameter changed in scipy 0.8.0 +# Before it returned a scalar, later it returned an array of length 1. +import pkg_resources as pr +from scipy.optimize import leastsq + +from diffpy.srmise import srmiselog +from diffpy.srmise.srmiseerrors import SrMiseFitError, SrMiseStaticOwnerError, SrMiseUndefinedCovarianceError + +logger = logging.getLogger("diffpy.srmise") +__spv__ = pr.get_distribution("scipy").version +__oldleastsqbehavior__ = pr.parse_version(__spv__) < pr.parse_version("0.8.0") + + +class ModelParts(list): + """A collection of ModelPart instances. + + Methods + ------- + copy: Return deep copy + fit: Fit to given data + npars: Return total number of parameters + pack_freepars: Update free parameters with values in given sequence + residual: Return residual of model + residual_jacobian: Return jacobian of residual of model + transform: Change format of parameters. + value: Return value of model + unpack_freepars: Return sequence containing value of all free parameters + """ + + def __init__(self, *args, **kwds): + list.__init__(self, *args, **kwds) + + def fit( + self, + r, + y, + y_error, + range=None, + ntrials=0, + cov=None, + cov_format="default_output", + ): + """Chi-square fit of all free parameters to given data. + + There must be at least as many free parameters as data points. + Fitting is performed with the MINPACK leastsq() routine exposed by scipy. + + Parameters + r - Sequence of r values over which to fit + y - Sequence of y values over which to fit + y_error - Sequence of uncertainties in y + range - Slice object specifying region of r and y over which to fit. + Fits over all the data by default. + ntrials - The maximum number of function evaluations while fitting. + cov - Optional ModelCovariance object preserves covariance information. + cov_format - Parameterization to use in cov. + """ + freepars = self.unpack_freepars() + if len(freepars) >= len(r): + emsg = ( + "Cannot fit model with " + + str(len(freepars)) + + " free parametersbut only " + + str(len(r)) + + " data points." + ) + raise SrMiseFitError(emsg) + if len(freepars) == 0: + # emsg = "Cannot fit model with no free parameters." + # raise SrMiseFitError(emsg) + return + + if range is None: + range = slice(None) + + args = (r, y, y_error, range) + + if srmiselog.liveplots: + plt.figure(1) + plt.ioff() + plt.subplot(211) + plt.cla() + plt.title("Before") + plt.plot(r, y, label="_nolabel_") + plt.plot( + r, + (y - self.value(r, range=range)) - 1.1 * (max(y) - min(y)), + label="_nolabel_", + ) + for p in self: + plt.plot(r, p.value(r, range=range), label=str(p)) + plt.ion() + + try: + f = leastsq( + self.residual, # minimize this function + freepars, # initial parameters + args=args, # arguments to residual, residual_jacobian + Dfun=self.residual_jacobian, # explicit Jacobian + col_deriv=1, # order of derivatives in Jacobian + full_output=1, + maxfev=ntrials, + ) + except NotImplementedError: + # TODO: Figure out if is worth checking for residual_jacobian + # before leastsq(). This exception will either occur almost never + # or extremely frequently, and the extra evaluations will add up. + logger.info("One or more functions do not define residual_jacobian().") + f = leastsq( + self.residual, # minimize this function + freepars, # initial parameters + args=args, # arguments to residual + col_deriv=1, # order of derivatives in Jacobian + full_output=1, + maxfev=ntrials, + ) + except Exception: + # Sadly, KeyboardInterrupt, etc. is reraised as minpack.error + # Not much I can do about that, though. + import traceback + + emsg = ( + "Unexpected error in modelparts.fit(). Original exception:\n" + + traceback.format_exc() + + "End original exception." + ) + raise SrMiseFitError(emsg) + + result = f[0] + if __oldleastsqbehavior__ and len(freepars) == 1: + # leastsq returns a scalar when there is only one parameter + result = np.array([result]) + + self.pack_freepars(result) + + if srmiselog.liveplots: + plt.draw() + plt.ioff() + plt.figure(1) + plt.subplot(212) + plt.cla() + plt.title("After") + plt.ion() + plt.plot( + r, + y, + r, + (y - self.value(r, range=range)) - 1.1 * (max(y) - min(y)), + *[i for sublist in [[r, p.value(r, range=range)] for p in self] for i in sublist], + ) + plt.draw() + + if srmiselog.wait: + print( + "Press 'Enter' to continue...", + ) + input() + + if f[4] not in (1, 2, 3, 4): + emsg = "Fit did not succeed -- " + str(f[3]) + raise SrMiseFitError(emsg) + + # clean up parameters + for p in self: + p.pars = p.owner().transform_parameters(p.pars, in_format="internal", out_format="internal") + + # Supply estimated covariance matrix if requested. + # The precise relationship between f[1] and estimated covariance matrix is a little unclear from + # the documentation of leastsq. This is the interpretation given by scipy.optimize.curve_fit, + # which is a wrapper around leastsq. + if cov is not None: + pcov = f[1] + fvec = f[2]["fvec"] + dof = len(r) - len(freepars) + cov.setcovariance(self, pcov * np.sum(fvec**2) / dof) + try: + cov.transform(in_format="internal", out_format=cov_format) + except SrMiseUndefinedCovarianceError: + logger.warn("Covariance not defined. Fit may not have converged.") + + return + + # # Notes on the fit f + # f[0] = solution + # f[1] = Uses the fjac and ipvt optional outputs to construct an estimate of the jacobian around the solution. + # None if a singular matrix encountered (indicates very flat curvature in some direction). + # This matrix must be multiplied by the residual variance to get the covariance of the parameter + # estimates - see curve fit. + # f[2] = dictionary{nfev: int, fvec: array(), fjac: array(), ipvt: array(), qtf: array()} + # nfev - The number of function calls made + # fvec - function (residual) evaluated at solution + # fjac - "a permutation of the R matrix of a QR factorization of the final Jacobian." + # ipvt - integer array defining a permutation matrix P such that fjac*P=QR + # qtf - transpose(q)*fvec + # f[3] = message about results of fit + # f[4] = integer flag. Fit was successful on 1,2,3, or 4. Otherwise unsuccessful. + + def npars(self, count_fixed=True): + """Return total number of parameters in all parts. + + Parameters + count_fixed - Boolean which determines if fixed parameters are + are included in the count. + """ + n = 0 + for p in self: + n += p.npars(count_fixed=count_fixed) + return n + + def pack_freepars(self, freepars): + """Update parameters with values from sequence of freepars.""" + if np.isnan(freepars).any(): + emsg = "Non-numeric free parameters." + raise ValueError(emsg) + freeidx = 0 + for p in self: + freeidx += p.update(freepars[freeidx:]) + + def residual(self, freepars, r, y_expected, y_error, range=None): + """Calculate residual of all parameters. + + Parameters + freepars - sequence of free parameters + r - the input domain + y_expected - sequence of expected values + y_error - sequence of uncertainties in y-variable + range - Slice object specifying region of r and y over which to fit. + All the data by default. + """ + self.pack_freepars(freepars) + total = self.value(r, range) + try: + if range is None: + range = slice(0, len(r)) + return (y_expected[range] - total[range]) / y_error[range] + except TypeError: + return (y_expected - total) / y_error + + def residual_jacobian(self, freepars, r, y_expected, y_error, range=None): + """Calculate the Jacobian of freepars. + + Parameters + freepars - sequence of free parameters + r - the input domain + y_expected - sequence of expected values + y_error - sequence of uncertainties in y-variable + range - Slice object specifying region of r and y over which to fit. + All the data by default. + """ + if len(freepars) == 0: + raise ValueError( + "Argument freepars has length 0. The Jacobian " "is only defined with >=1 free parameters." + ) + + self.pack_freepars(freepars) + tempJac = [] + for p in self: + tempJac[len(tempJac) :] = p.jacobian(r, range) + # Since the residual is (expected - calculated) the jacobian + # of the residual has a minus sign. + jac = -np.array([j for j in tempJac if j is not None]) + try: + if range is None: + range = slice(0, len(r)) + return jac[:, range] / y_error[range] + except TypeError: + return jac / y_error + + def value(self, r, range=None): + """Calculate total value of all parts over range. + + Parameters + r - the input domain + range - Slice object specifying region of r and y over which to fit. + All the data by default. + """ + total = r * 0.0 + for p in self: + total += p.value(r, range) + return total + + def unpack_freepars(self): + """Return array of all free parameters.""" + # To check: ravel() sometimes returns a reference and othertimes a copy. + # Do I need to use flatten() instead? + return np.concatenate([p.compress() for p in self]).ravel() + + def covariance(self, format="internal", **kwds): + """Return estimated covariance matrix of the model. + + The covariance matrix may be given in terms of any parameterization + defined by the formats for each individual ModelPart. + + Parameters + format - The format ("internal" by default) to use for all ModelParts. + This may be overridden for specific peaks as shown below. + + Keywords + f0 - The format of the 0th ModelPart + f1 - The format of the 1st ModelPart + etc. + """ + formats = [format for p in self] + + for k, v in kwds.items(): + try: + int(k[1:]) + except ValueError: + emsg = "Invalid format keyword '%s'. They must be specified as 'f0', 'f1', etc." % k + raise ValueError(emsg) + + formats[int(k[1:])] = v + + return + + def copy(self): + """Return deep copy of this ModelParts. + + The original and the copy are completely independent, except each + ModelPart and its copy still reference the same owner.""" + return type(self).__call__([p.copy() for p in self]) + + def __str__(self): + """Return string representation of this ModelParts.""" + return "".join([str(p) + "\n" for p in self]) + + def __getslice__(self, i, j): + """Extends list.__getslice__""" + return self.__class__(list.__getslice__(self, i, j)) + + def transform(self, in_format="internal", out_format="internal"): + """Transforms format of parameters in this modelpart. + + Parameters + in_format - The format the parameters are already in. + out_format - The format the parameters are transformed to. + """ + for p in self: + try: + p.pars = p.owner().transform_parameters(p.pars, in_format, out_format) + except ValueError: + logger.info( + "Invalid parameter transformation: Ignoring %s->%s for function of type %s." + % (in_format, out_format, p.owner().getmodule()) + ) + + +# End of class ModelParts + + +class ModelPart(object): + """Represents a single part (instance of some function) of a model. + + Members + ------- + pars - Array containing the parameters of this model part + free - Array containing boolean values defining whether the corresponding parameter + is free or not. + removable - Boolean determining whether or not this model part can be + removed during extraction. + static_owner - Boolean determines if owner can be changed with changeowner() + + Methods + ------- + changeowner - Change the owner of self + copy - Return deep copy of self + compress - Return parameters with non-free parameters removed + jacobian - Return jacobian + getfree - Return free parameter by index or keyword define by owner + npars - Return number of parameters in self + owner - Return self.owner + setfree - Set a free parameter by index or keyword defined by owner + update - Update free parameters with values in given sequence + value - Return value + writestr - Return string representation of self + """ + + def __init__(self, owner, pars, free=None, removable=True, static_owner=False): + """Set instance members. + + Parameters + owner - an instance of a BaseFunction subclass + pars - Sequence of parameters which specify the function explicitly + free - Sequence of Boolean variables. If False, the corresponding + parameter will not be changed. + removable - Boolean determines whether this part can be removed. + static_owner - Whether or not the part can be changed with + changeowner() + + Note that free and removable are not mutually exclusive. If any + pars are not free but removable=True then the part may be removed, but + the held parameters for this part will remain unchanged until then. + """ + self._owner = owner + + if len(pars) != owner.npars: + emsg = "The length of pars must equal the number of parameters " + "specified by the model part owner." + raise ValueError(emsg) + self.pars = np.array(pars[:]) # pars[:] in case pars is a ModelPart + + if free is None: + self.free = np.array([True for p in pars], dtype=bool) + else: + self.free = np.array(free, dtype=bool) + if len(self.free) != owner.npars: + emsg = ( + "The length of free must be equal to the number of " + + "parameters specified by the model part owner." + ) + raise ValueError(emsg) + + self.removable = removable + self.static_owner = static_owner + + def changeowner(self, owner): + """Change the owner of this part. + + Does not change the parameters associated with this model part. Raises + SrMiseStaticOwnerError if this peak has been declared to have a static + owner, or if the number of parameters is incompatible. + + Parameters + owner - an instance of a BaseFunction subclass + """ + if self.static_owner and self._owner is not owner: + emsg = "Cannot change owner if static_owner is True." + raise SrMiseStaticOwnerError(emsg) + if self._owner.npars != owner.npars: + emsg = "New owner specifies different number of parameters than " + "original owner." + raise SrMiseStaticOwnerError(emsg) + self._owner = owner + + def compress(self): + """Return part parameters with non-free values removed.""" + return self.pars[self.free] + + def jacobian(self, r, range=None): + """Return jacobian of this part over r. + + Parameters + r - the input domain + range - Slice object specifying region of r and y over which to fit. + All the data by default. + """ + return self._owner.jacobian(self, r, range) + + def owner(self): + """Return the BaseFunction subclass instance which owns this part.""" + return self._owner + + def update(self, freepars): + """Sequentially update free parameters from freepars. + + Parameters + freepars - sequence of new parameter values. May contain more + parameters than can actually be updated. + + Return number of parameters updated from freepars. + """ + numfree = self.npars(count_fixed=False) + if len(freepars) < numfree: + pass # raise "freepars does not have enough elements to + # update every unheld parameter." + # TODO: Check if I need to make copies here, or if references + # to parameters are safe. + self.pars[self.free] = freepars[:numfree] + return numfree + + def value(self, r, range=None): + """Return value of peak over r. + + Parameters + r - the input domain + range - Slice object specifying region of r and y over which to fit. + All the data by default. + """ + return self._owner.value(self, r, range) + + def copy(self): + """Return a deep copy of this ModelPart. + + The original and the copy are completely independent, except they both + reference the same owner.""" + return type(self).__call__(self._owner, self.pars, self.free, self.removable, self.static_owner) + + def __getitem__(self, key_or_idx): + """Return parameter of peak corresponding with key_or_idx. + + Parameters + key_or_idx - An integer index, slice, or key from owner's parameter + dictionary. + """ + if key_or_idx in self._owner.parameterdict: + return self.pars[self._owner.parameterdict[key_or_idx]] + else: + return self.pars[key_or_idx] + + def getfree(self, key_or_idx): + """Return value of free corresponding with key_or_idx. + + Parameters + key_or_idx - An integer index, slice, or key from owner's parameter + dictionary.""" + if key_or_idx in self._owner.parameterdict: + return self.free[self._owner.parameterdict[key_or_idx]] + else: + return self.free[key_or_idx] + + def setfree(self, key_or_idx, value): + """Set value of free corresponding with key_or_idx. + + Parameters + key_or_idx - An integer index, slice, or key from owner's parameter + dictionary. + value: A boolean""" + if key_or_idx in self._owner.parameterdict: + self.free[self._owner.parameterdict[key_or_idx]] = value + else: + self.free[key_or_idx] = value + + def __len__(self): + """Return number of parameters, including any fixed ones.""" + return self._owner.npars + + def npars(self, count_fixed=True): + """Return total number of parameters in all parts. + + Parameters + count_fixed - Boolean which determines if fixed parameters are + are included in the count.""" + if count_fixed: + return self._owner.npars + else: + return (self.free is True).sum() + + def __str__(self): + """Return string representation of ModelPart parameters.""" + return str(self._owner.transform_parameters(self.pars, in_format="internal", out_format="default_output")) + + def __eq__(self, other): + """ """ + if hasattr(other, "_owner"): + return ( + (self._owner is other._owner) + and np.all(self.pars == other.pars) + and np.all(self.free == other.free) + and self.removable == other.removable + ) + else: + return False + + def __ne__(self, other): + """ """ + return not self == other + + def writestr(self, ownerlist): + """Return string representation of ModelPart. + + The value of owner is determined by its index in ownerlist. + + Parameters + ownerlist - List of owner functions + """ + if self._owner not in ownerlist: + emsg = "ownerlist does not contain this ModelPart's owner." + raise ValueError(emsg) + lines = [] + lines.append("owner=%s" % repr(ownerlist.index(self._owner))) + + # Lists/numpy arrays don't give full representation of long lists + lines.append("pars=[%s]" % ", ".join([repr(p) for p in self.pars])) + lines.append("free=[%s]" % ", ".join([repr(f) for f in self.free])) + lines.append("removable=%s" % repr(self.removable)) + lines.append("static_owner=%s" % repr(self.static_owner)) + datastring = "\n".join(lines) + "\n" + return datastring + + +# End of class ModelPart + +# simple test code +if __name__ == "__main__": + + pass diff --git a/diffpy/srmise/multimodelselection.py b/src/diffpy/srmise/multimodelselection.py similarity index 100% rename from diffpy/srmise/multimodelselection.py rename to src/diffpy/srmise/multimodelselection.py diff --git a/diffpy/srmise/pdfdataset.py b/src/diffpy/srmise/pdfdataset.py similarity index 100% rename from diffpy/srmise/pdfdataset.py rename to src/diffpy/srmise/pdfdataset.py diff --git a/diffpy/srmise/pdfpeakextraction.py b/src/diffpy/srmise/pdfpeakextraction.py similarity index 100% rename from diffpy/srmise/pdfpeakextraction.py rename to src/diffpy/srmise/pdfpeakextraction.py diff --git a/diffpy/srmise/peakextraction.py b/src/diffpy/srmise/peakextraction.py similarity index 100% rename from diffpy/srmise/peakextraction.py rename to src/diffpy/srmise/peakextraction.py diff --git a/diffpy/srmise/peaks/__init__.py b/src/diffpy/srmise/peaks/__init__.py similarity index 100% rename from diffpy/srmise/peaks/__init__.py rename to src/diffpy/srmise/peaks/__init__.py diff --git a/diffpy/srmise/peaks/base.py b/src/diffpy/srmise/peaks/base.py similarity index 100% rename from diffpy/srmise/peaks/base.py rename to src/diffpy/srmise/peaks/base.py diff --git a/diffpy/srmise/peaks/gaussian.py b/src/diffpy/srmise/peaks/gaussian.py similarity index 100% rename from diffpy/srmise/peaks/gaussian.py rename to src/diffpy/srmise/peaks/gaussian.py diff --git a/diffpy/srmise/peaks/gaussianoverr.py b/src/diffpy/srmise/peaks/gaussianoverr.py similarity index 100% rename from diffpy/srmise/peaks/gaussianoverr.py rename to src/diffpy/srmise/peaks/gaussianoverr.py diff --git a/diffpy/srmise/peaks/terminationripples.py b/src/diffpy/srmise/peaks/terminationripples.py similarity index 100% rename from diffpy/srmise/peaks/terminationripples.py rename to src/diffpy/srmise/peaks/terminationripples.py diff --git a/diffpy/srmise/peakstability.py b/src/diffpy/srmise/peakstability.py similarity index 100% rename from diffpy/srmise/peakstability.py rename to src/diffpy/srmise/peakstability.py diff --git a/diffpy/srmise/srmiseerrors.py b/src/diffpy/srmise/srmiseerrors.py similarity index 100% rename from diffpy/srmise/srmiseerrors.py rename to src/diffpy/srmise/srmiseerrors.py diff --git a/diffpy/srmise/srmiselog.py b/src/diffpy/srmise/srmiselog.py similarity index 100% rename from diffpy/srmise/srmiselog.py rename to src/diffpy/srmise/srmiselog.py diff --git a/src/diffpy/srmise/tests/__init__.py b/src/diffpy/srmise/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/diffpy/srmise/tests/conftest.py b/src/diffpy/srmise/tests/conftest.py new file mode 100644 index 0000000..e3b6313 --- /dev/null +++ b/src/diffpy/srmise/tests/conftest.py @@ -0,0 +1,19 @@ +import json +from pathlib import Path + +import pytest + + +@pytest.fixture +def user_filesystem(tmp_path): + base_dir = Path(tmp_path) + home_dir = base_dir / "home_dir" + home_dir.mkdir(parents=True, exist_ok=True) + cwd_dir = base_dir / "cwd_dir" + cwd_dir.mkdir(parents=True, exist_ok=True) + + home_config_data = {"username": "home_username", "email": "home@email.com"} + with open(home_dir / "diffpyconfig.json", "w") as f: + json.dump(home_config_data, f) + + yield tmp_path diff --git a/src/diffpy/srmise/tests/debug.py b/src/diffpy/srmise/tests/debug.py new file mode 100644 index 0000000..313b120 --- /dev/null +++ b/src/diffpy/srmise/tests/debug.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Billinge Group members and community contributors. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srmise/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## + +""" +Convenience module for debugging the unit tests using + +python -m diffpy.srmise.tests.debug + +Exceptions raised by failed tests or other errors are not caught. +""" + + +if __name__ == "__main__": + import sys + + from diffpy.srmise.tests import testsuite + + pattern = sys.argv[1] if len(sys.argv) > 1 else "" + suite = testsuite(pattern) + suite.debug() + + +# End of file diff --git a/src/diffpy/srmise/tests/run.py b/src/diffpy/srmise/tests/run.py new file mode 100644 index 0000000..afe4bbf --- /dev/null +++ b/src/diffpy/srmise/tests/run.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Billinge Group members and community contributors. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srmise/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## +"""Convenience module for executing all unit tests with +python -m diffpy.srmise.tests.run +""" + +import sys + +import pytest + +if __name__ == "__main__": + # show output results from every test function + args = ["-v"] + # show the message output for skipped and expected failure tests + if len(sys.argv) > 1: + args.extend(sys.argv[1:]) + print("pytest arguments: {}".format(args)) + # call pytest and exit with the return code from pytest + exit_res = pytest.main(args) + sys.exit(exit_res) + +# End of file diff --git a/src/diffpy/srmise/version.py b/src/diffpy/srmise/version.py new file mode 100644 index 0000000..304027c --- /dev/null +++ b/src/diffpy/srmise/version.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Billinge Group members and community contributors. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srmise/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## + +"""Definition of __version__.""" + +# We do not use the other three variables, but can be added back if needed. +# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] + +# obtain version information +from importlib.metadata import version + +__version__ = version("diffpy.srmise") + +# End of file From e1a5e58f248b141a6219d1bc551a29397f8ee01c Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:13:30 +0800 Subject: [PATCH 22/65] LICENSE (#45) * add two LICENSE.rst files into cookiecutter * fix LICENSE.rst and LICENSE_PDFgui.rst with correct references and year * resolve pdfdataset.py conflict --------- Co-authored-by: Simon Billinge --- LICENSE.rst | 44 ++++++++++++++++++++++++++++++++++++ LICENSE.txt | 46 ------------------------------------- LICENSE_PDFgui.rst | 51 +++++++++++++++++++++++++++++++++++++++++ LICENSE_PDFgui.txt | 56 ---------------------------------------------- 4 files changed, 95 insertions(+), 102 deletions(-) create mode 100644 LICENSE.rst delete mode 100644 LICENSE.txt create mode 100644 LICENSE_PDFgui.rst delete mode 100644 LICENSE_PDFgui.txt diff --git a/LICENSE.rst b/LICENSE.rst new file mode 100644 index 0000000..7c8f8d0 --- /dev/null +++ b/LICENSE.rst @@ -0,0 +1,44 @@ +BSD 3-Clause License + +Copyright 2014-2015, Board of Trustees of Michigan State University + 2016-2024, The Trustees of Columbia University in the City of New York. +All rights reserved. + +If you use this program to do productive scientific research that +leads to publication, we ask that you acknowledge use of the +program by citing the following paper in your publication: + + L. Granlund, S.J.L. Billinge, P.M. Duxbury, Algorithm for + systematic peak extraction from atomic pair distribution + functions, Acta Crystallographica A 71(4), 392-409 (2015). + doi:10.1107/S2053273315005276 + +For more information please visit the diffpy web-page at + http://www.diffpy.org +or email Luke Granlund at luke.r.granlund@gmail.com, or Prof. Simon +Billinge at sb2896@columbia.edu. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 23b3331..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,46 +0,0 @@ -If you use this program to do productive scientific research that -leads to publication, we ask that you acknowledge use of the -program by citing the following paper in your publication: - - L. Granlund, S.J.L. Billinge, P.M. Duxbury, Algorithm for - systematic peak extraction from atomic pair distribution - functions, Acta Crystallographica A 71(4), 392-409 (2015). - doi:10.1107/S2053273315005276 - -Copyright 2014-2015, Board of Trustees of Michigan State University - -For more information please visit the diffpy web-page at - http://www.diffpy.org -or email Luke Granlund at luke.r.granlund@gmail.com, or Prof. Simon -Billinge at sb2896@columbia.edu. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS". COPYRIGHT -HOLDER EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES AND CONDITIONS, -EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY, TITLE, FITNESS, ADEQUACY OR -SUITABILITY FOR A PARTICULAR PURPOSE, AND ANY WARRANTIES OF FREEDOM -FROM INFRINGEMENT OF ANY DOMESTIC OR FOREIGN PATENT, COPYRIGHTS, -TRADE SECRETS OR OTHER PROPRIETARY RIGHTS OF ANY PARTY. IN NO EVENT -SHALL COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE OR RELATING TO -THIS AGREEMENT, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE_PDFgui.rst b/LICENSE_PDFgui.rst new file mode 100644 index 0000000..3ba9b69 --- /dev/null +++ b/LICENSE_PDFgui.rst @@ -0,0 +1,51 @@ +BSD 3-Clause License + +Copyright 2006-2007, Board of Trustees of Michigan State University + 2008-2024, The Trustees of Columbia University in the City of New York. +All rights reserved. + +SrMise incorporates source code from diffpy.pdfgui in the file +pdfdataset.py. The PDFgui license is reproduced in full below. + +This program is part of the DiffPy and DANSE open-source projects +and is available subject to the conditions and terms laid out +below. + +If you use this program to do productive scientific research that +leads to publication, we ask that you acknowledge use of the +program by citing the following paper in your publication: + + C. L. Farrow, P. Juhas, J. W. Liu, D. Bryndin, E. S. Bozin, + J. Bloch, Th. Proffen and S. J. L. Billinge, PDFfit2 and + PDFgui: computer programs for studying nanostructure in + crystals, J. Phys.: Condens. Matter 19, 335219 (2007) + +For more information please visit the diffpy web-page at + http://www.diffpy.org +or email Luke Granlund at luke.r.granlund@gmail.com, or Prof. Simon +Billinge at sb2896@columbia.edu. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE_PDFgui.txt b/LICENSE_PDFgui.txt deleted file mode 100644 index 4e56059..0000000 --- a/LICENSE_PDFgui.txt +++ /dev/null @@ -1,56 +0,0 @@ -SrMise incorporates source code from diffpy.pdfgui in the file -pdfdataset.py. The PDFgui license is reproduced in full below. -=================================================================== - -This program is part of the DiffPy and DANSE open-source projects -and is available subject to the conditions and terms laid out -below. - -If you use this program to do productive scientific research that -leads to publication, we ask that you acknowledge use of the -program by citing the following paper in your publication: - - C. L. Farrow, P. Juhas, J. W. Liu, D. Bryndin, E. S. Bozin, - J. Bloch, Th. Proffen and S. J. L. Billinge, PDFfit2 and - PDFgui: computer programs for studying nanostructure in - crystals, J. Phys.: Condens. Matter 19, 335219 (2007) - -Copyright 2006-2007, Board of Trustees of Michigan State -University, Copyright 2008-2009, Board of Trustees of Columbia -University in the city of New York. (Copyright holder indicated in -each source file). - -For more information please visit the project web-page: - http://www.diffpy.org/ -or email Prof. Simon Billinge at sb2896@columbia.edu - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS". COPYRIGHT -HOLDER EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES AND CONDITIONS, -EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY, TITLE, FITNESS, ADEQUACY OR -SUITABILITY FOR A PARTICULAR PURPOSE, AND ANY WARRANTIES OF FREEDOM -FROM INFRINGEMENT OF ANY DOMESTIC OR FOREIGN PATENT, COPYRIGHTS, -TRADE SECRETS OR OTHER PROPRIETARY RIGHTS OF ANY PARTY. IN NO EVENT -SHALL COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE OR RELATING TO -THIS AGREEMENT, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 0b4650795d0c38991d49ffbd3079dc4d4564c8a2 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 31 Jul 2024 22:15:20 +0800 Subject: [PATCH 23/65] add untrack files and add cookiecut.rst news (#46) * add untrack files and add cookiecut.rst news * delete README.txt --- AUTHORS.rst | 11 +++ AUTHORS.txt | 3 - CHANGELOG.rst | 5 ++ CODE_OF_CONDUCT.rst | 133 +++++++++++++++++++++++++++ doc/Makefile | 194 ++++++++++++++++++++++++++++++++++++++++ doc/make.bat | 36 ++++++++ news/TEMPLATE.rst | 23 +++++ news/cookiecut.rst | 23 +++++ requirements/README.txt | 11 --- 9 files changed, 425 insertions(+), 14 deletions(-) create mode 100644 AUTHORS.rst delete mode 100644 AUTHORS.txt create mode 100644 CHANGELOG.rst create mode 100644 CODE_OF_CONDUCT.rst create mode 100644 doc/Makefile create mode 100644 doc/make.bat create mode 100644 news/TEMPLATE.rst create mode 100644 news/cookiecut.rst delete mode 100644 requirements/README.txt diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..9cef943 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,11 @@ +Authors +======= + +Luke Granlund +Billinge Group and community contibutors. + +Contributors +------------ + +For a list of contributors, visit +https://github.com/diffpy/diffpy.srmise/graphs/contributors diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index be8310a..0000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,3 +0,0 @@ -SrMise (diffpy.srmise) authors: - -Luke Granlund diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..2669451 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,5 @@ +============= +Release Notes +============= + +.. current developments diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst new file mode 100644 index 0000000..ff9c356 --- /dev/null +++ b/CODE_OF_CONDUCT.rst @@ -0,0 +1,133 @@ +===================================== + Contributor Covenant Code of Conduct +===================================== + +Our Pledge +---------- + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +Our Standards +------------- + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +---------------------------- + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +Scope +----- + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +sb2896@columbia.edu. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +---------------------- + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction +**************** + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +************* + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +3. Temporary Ban +****************** + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +****************** + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant `_. + +Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder `_. + +For answers to common questions about this code of conduct, see the `FAQ `_. `Translations are available `_ diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..798f52b --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,194 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build +BASENAME = $(subst .,,$(subst $() $(),,diffpy.srmise)) + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/$(BASENAME).qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/$(BASENAME).qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/$(BASENAME)" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/$(BASENAME)" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +# Manual publishing to the gh-pages branch + +GITREPOPATH = $(shell cd $(CURDIR) && git rev-parse --git-dir) +GITREMOTE = origin +GITREMOTEURL = $(shell git config --get remote.$(GITREMOTE).url) +GITLASTCOMMIT = $(shell git rev-parse --short HEAD) + +publish: + @test -d build/html || \ + ( echo >&2 "Run 'make html' first!"; false ) + git show-ref --verify --quiet refs/heads/gh-pages || \ + git branch --track gh-pages $(GITREMOTE)/gh-pages + test -d build/gh-pages || \ + git clone -s -b gh-pages $(GITREPOPATH) build/gh-pages + cd build/gh-pages && \ + git pull $(GITREMOTEURL) gh-pages + rsync -acv --delete --exclude=.git --exclude=.rsync-exclude \ + --exclude-from=build/gh-pages/.rsync-exclude \ + --link-dest=$(CURDIR)/build/html build/html/ build/gh-pages/ + cd build/gh-pages && \ + git add --all . && \ + git diff --cached --quiet || \ + git commit -m "Sync with the source at $(GITLASTCOMMIT)." + cd build/gh-pages && \ + git push origin gh-pages diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..2be8306 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=PackagingScientificPython + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/news/TEMPLATE.rst b/news/TEMPLATE.rst new file mode 100644 index 0000000..790d30b --- /dev/null +++ b/news/TEMPLATE.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/news/cookiecut.rst b/news/cookiecut.rst new file mode 100644 index 0000000..25b8176 --- /dev/null +++ b/news/cookiecut.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Cookiecuttered diffpy.srmise to new Billingegroup standard. + +**Security:** + +* diff --git a/requirements/README.txt b/requirements/README.txt deleted file mode 100644 index dc34909..0000000 --- a/requirements/README.txt +++ /dev/null @@ -1,11 +0,0 @@ -# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! -# -# This directory is where you should place your project dependencies. -# "pip.txt" should contain all required packages not available on conda. -# All other files should contain only packages available to download from conda. -# build.txt should contain all packages required to build (not run) the project. -# run.txt should contain all packages (including optional packages) required for a user to run the program. -# test.txt should contain all packages required for the testing suite and to ensure all tests pass. -# docs.txt should contain all packages required for building the package documentation page. -# -# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! From c1886a1b454c238e5d13b2567b0f60c152c36630 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 2 Aug 2024 21:21:33 +0800 Subject: [PATCH 24/65] fix py2 -> py3, fix broken import, remove deprecation warning (#47) * fix py2 -> py3, move deprecation warning * fix search & split in binary files --- devutils/makesdist | 5 +++-- src/diffpy/srmise/applications/extract.py | 12 ++++++------ src/diffpy/srmise/applications/plot.py | 10 +++++----- src/diffpy/srmise/baselines/arbitrary.py | 4 ++-- src/diffpy/srmise/baselines/base.py | 5 +++-- src/diffpy/srmise/baselines/fromsequence.py | 8 ++++---- src/diffpy/srmise/modelevaluators/aic.py | 4 ++-- src/diffpy/srmise/modelevaluators/aicc.py | 4 ++-- 8 files changed, 27 insertions(+), 25 deletions(-) diff --git a/devutils/makesdist b/devutils/makesdist index a7e2676..380f9e1 100644 --- a/devutils/makesdist +++ b/devutils/makesdist @@ -1,8 +1,8 @@ #!/usr/bin/env python -'''Create source distribution tar.gz archive, where each file belongs +"""Create source distribution tar.gz archive, where each file belongs to a root user and modification time is set to the git commit time. -''' +""" import sys import os @@ -28,6 +28,7 @@ tarname = max(glob.glob(BASEDIR + '/dist/*.tar'), key=os.path.getmtime) tfin = tarfile.open(tarname) tfout = tarfile.open(tarname + '.gz', 'w:gz') + def fixtarinfo(tinfo): tinfo.uid = tinfo.gid = 0 tinfo.uname = tinfo.gname = 'root' diff --git a/src/diffpy/srmise/applications/extract.py b/src/diffpy/srmise/applications/extract.py index c070704..9d08e9f 100755 --- a/src/diffpy/srmise/applications/extract.py +++ b/src/diffpy/srmise/applications/extract.py @@ -434,7 +434,7 @@ def main(): return if options.bcrystal is not None: - from diffpy.srmise.baselines import Polynomial + from diffpy.srmise.baselines.polynomial import Polynomial bl = Polynomial(degree=1) options.baseline = parsepars(bl, [options.bcrystal, "0c"]) @@ -445,27 +445,27 @@ def main(): blext.read(options.bsrmise) options.baseline = blext.extracted.baseline elif options.bpoly0 is not None: - from diffpy.srmise.baselines import Polynomial + from diffpy.srmise.baselines.polynomial import Polynomial bl = Polynomial(degree=0) options.baseline = parsepars(bl, [options.bpoly0]) elif options.bpoly1 is not None: - from diffpy.srmise.baselines import Polynomial + from diffpy.srmise.baselines.polynomial import Polynomial bl = Polynomial(degree=1) options.baseline = parsepars(bl, options.bpoly1) elif options.bpoly2 is not None: - from diffpy.srmise.baselines import Polynomial + from diffpy.srmise.baselines.polynomial import Polynomial bl = Polynomial(degree=2) options.baseline = parsepars(bl, options.bpoly2) elif options.bseq is not None: - from diffpy.srmise.baselines import FromSequence + from diffpy.srmise.baselines.fromsequence import FromSequence bl = FromSequence(options.bseq) options.baseline = bl.actualize([], "internal") elif options.bspherical is not None: - from diffpy.srmise.baselines import NanoSpherical + from diffpy.srmise.baselines.nanospherical import NanoSpherical bl = NanoSpherical() options.baseline = parsepars(bl, options.bspherical) diff --git a/src/diffpy/srmise/applications/plot.py b/src/diffpy/srmise/applications/plot.py index 5dc59e1..2663158 100755 --- a/src/diffpy/srmise/applications/plot.py +++ b/src/diffpy/srmise/applications/plot.py @@ -22,8 +22,8 @@ from matplotlib.ticker import MultipleLocator from mpl_toolkits.axes_grid1.inset_locator import inset_axes -from diffpy.srmise import PDFPeakExtraction, PeakStability -from diffpy.srmise.pdfpeakextraction import resample +from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction, resample +from diffpy.srmise.peakstability import PeakStability # For a given figure, returns a label of interest labeldict = {} @@ -92,14 +92,14 @@ def comparepositions(ppe, ip=None, **kwds): ep = [p for p in ep if p >= pmin and p <= pmax] if ip is not None: - xi = np.NaN + np.zeros(3 * len(ip)) + xi = np.nan + np.zeros(3 * len(ip)) xi[0::3] = ip xi[1::3] = ip yi = np.zeros_like(xi) + base yi[1::3] += yideal plt.plot(xi, yi, "b", lw=1.5, **ip_style) - xe = np.NaN + np.zeros(3 * len(ep)) + xe = np.nan + np.zeros(3 * len(ep)) xe[0::3] = ep xe[1::3] = ep ye = np.zeros_like(xe) + base @@ -185,7 +185,7 @@ def makeplot(ppe_or_stability, ip=None, **kwds): if ppe.extracted is None: # Makeplot requires a ModelCluster, so whip one up. - from diffpy.srmise import ModelCluster + from diffpy.srmise.modelcluster import ModelCluster ppe.defaultvars() # Make sure everything has some setting. This # shouldn't have harmful side effects. diff --git a/src/diffpy/srmise/baselines/arbitrary.py b/src/diffpy/srmise/baselines/arbitrary.py index bda2494..6e54d73 100644 --- a/src/diffpy/srmise/baselines/arbitrary.py +++ b/src/diffpy/srmise/baselines/arbitrary.py @@ -16,8 +16,8 @@ import numpy as np -from diffpy.srmise.baselines import Polynomial from diffpy.srmise.baselines.base import BaselineFunction +from diffpy.srmise.baselines.polynomial import Polynomial from diffpy.srmise.srmiseerrors import SrMiseEstimationError logger = logging.getLogger("diffpy.srmise") @@ -65,7 +65,7 @@ def __init__(self, npars, valuef, jacobianf=None, estimatef=None, Cache=None): # Define parameterdict # e.g. {"a_0":0, "a_1":1, "a_2":2, "a_3":3} if npars is 4. parameterdict = {} - for d in range(self.testnpars + 1): + for d in range(testnpars + 1): parameterdict["a_" + str(d)] = d formats = ["internal"] default_formats = {"default_input": "internal", "default_output": "internal"} diff --git a/src/diffpy/srmise/baselines/base.py b/src/diffpy/srmise/baselines/base.py index ca58155..4d1d42f 100644 --- a/src/diffpy/srmise/baselines/base.py +++ b/src/diffpy/srmise/baselines/base.py @@ -170,8 +170,9 @@ def factory(baselinestr, ownerlist): from numpy.random import randn - from diffpy.srmise.modelevaluators import AICc - from diffpy.srmise.peaks import GaussianOverR, Peaks + from diffpy.srmise.modelevaluators.aicc import AICc + from diffpy.srmise.peaks.base import Peaks + from diffpy.srmise.peaks.gaussianoverr import GaussianOverR res = 0.01 r = np.arange(2, 4, res) diff --git a/src/diffpy/srmise/baselines/fromsequence.py b/src/diffpy/srmise/baselines/fromsequence.py index 4d4c553..c0a2d95 100644 --- a/src/diffpy/srmise/baselines/fromsequence.py +++ b/src/diffpy/srmise/baselines/fromsequence.py @@ -145,14 +145,14 @@ def _valueraw(self, pars, r): raise ValueError(emsg) try: if r[0] < self.minx or r[-1] > self.maxx: - logger.warn( + logger.warning( "Warning: Evaluating interpolating function over %s, outside safe range of %s.", [r[0], r[-1]], [self.minx, self.maxx], ) except (IndexError, TypeError): if r < self.minx or r > self.maxx: - logger.warn( + logger.warning( "Warning: Evaluating interpolating function at %s, outside safe range of %s.", r, [self.minx, self.maxx], @@ -178,7 +178,7 @@ def readxy(self, filename): import re - res = re.search(r"^[^#]", datastring, re.M) + res = re.search(rb"^[^#]", datastring, re.M) if res: datastring = datastring[res.end() :].strip() @@ -186,7 +186,7 @@ def readxy(self, filename): y = [] try: - for line in datastring.split("\n"): + for line in datastring.split(b"\n"): v = line.split() x.append(float(v[0])) y.append(float(v[1])) diff --git a/src/diffpy/srmise/modelevaluators/aic.py b/src/diffpy/srmise/modelevaluators/aic.py index e3ee9e4..8841111 100644 --- a/src/diffpy/srmise/modelevaluators/aic.py +++ b/src/diffpy/srmise/modelevaluators/aic.py @@ -68,7 +68,7 @@ def evaluate(self, fit, count_fixed=False, kshift=0): n = fit.size if n < self.minpoints(k): - logger.warn("AIC.evaluate(): too few data to evaluate quality reliably.") + logger.warning("AIC.evaluate(): too few data to evaluate quality reliably.") n = self.minpoints(k) if self.chisq is None: @@ -115,7 +115,7 @@ def growth_justified(self, fit, k_prime): # assert n >= self.minPoints(kActual) #check that AIC is defined for the actual fit if n < self.minpoints(k_actual): - logger.warn("AIC.growth_justified(): too few data to evaluate quality reliably.") + logger.warning("AIC.growth_justified(): too few data to evaluate quality reliably.") n = self.minpoints(k_actual) penalty = self.parpenalty(k_test, n) - self.parpenalty(k_actual, n) diff --git a/src/diffpy/srmise/modelevaluators/aicc.py b/src/diffpy/srmise/modelevaluators/aicc.py index e1b13f2..da0a083 100644 --- a/src/diffpy/srmise/modelevaluators/aicc.py +++ b/src/diffpy/srmise/modelevaluators/aicc.py @@ -68,7 +68,7 @@ def evaluate(self, fit, count_fixed=False, kshift=0): n = fit.size if n < self.minpoints(k): - logger.warn("AICc.evaluate(): too few data to evaluate quality reliably.") + logger.warning("AICc.evaluate(): too few data to evaluate quality reliably.") n = self.minpoints(k) if self.chisq is None: @@ -117,7 +117,7 @@ def growth_justified(self, fit, k_prime): # assert n >= self.minPoints(kActual) #check that AICc is defined for the actual fit if n < self.minpoints(k_actual): - logger.warn("AICc.growth_justified(): too few data to evaluate quality reliably.") + logger.warning("AICc.growth_justified(): too few data to evaluate quality reliably.") n = self.minpoints(k_actual) penalty = self.parpenalty(k_test, n) - self.parpenalty(k_actual, n) From f31d3d426f2fce08f5f6b2e8d4076b5459084ef9 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 2 Aug 2024 21:24:06 +0800 Subject: [PATCH 25/65] fix broken import, remove deprecated pkg_resource (#50) --- src/diffpy/srmise/basefunction.py | 5 +++-- src/diffpy/srmise/modelcluster.py | 4 ++-- src/diffpy/srmise/modelparts.py | 19 ++++++++++--------- src/diffpy/srmise/pdfdataset.py | 8 ++++---- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/diffpy/srmise/basefunction.py b/src/diffpy/srmise/basefunction.py index d5dd220..de69527 100644 --- a/src/diffpy/srmise/basefunction.py +++ b/src/diffpy/srmise/basefunction.py @@ -369,7 +369,7 @@ def factory(functionstr, baselist): # "key=value"->{"key":"value"} data = re.split(r"(?:[\r\n]+|\A)(\S+)=", data) ddict = {} - for i in range(len(data) / 2): + for i in range(len(data) // 2): ddict[data[2 * i + 1]] = data[2 * i + 2] # dictionary of parameters @@ -442,7 +442,8 @@ def safefunction(f, fsafe): if __name__ == "__main__": - from diffpy.srmise.peaks import GaussianOverR, TerminationRipples + from diffpy.srmise.peaks.gaussianoverr import GaussianOverR + from diffpy.srmise.peaks.terminationripples import TerminationRipples p = GaussianOverR(0.8) outstr = p.writestr([]) diff --git a/src/diffpy/srmise/modelcluster.py b/src/diffpy/srmise/modelcluster.py index cf952b2..cc509c4 100644 --- a/src/diffpy/srmise/modelcluster.py +++ b/src/diffpy/srmise/modelcluster.py @@ -26,9 +26,9 @@ import numpy as np from diffpy.srmise import srmiselog -from diffpy.srmise.baselines import Baseline +from diffpy.srmise.baselines.base import Baseline from diffpy.srmise.modelparts import ModelParts -from diffpy.srmise.peaks import Peak, Peaks +from diffpy.srmise.peaks.base import Peak, Peaks from diffpy.srmise.srmiseerrors import ( SrMiseDataFormatError, SrMiseEstimationError, diff --git a/src/diffpy/srmise/modelparts.py b/src/diffpy/srmise/modelparts.py index 37d6e0c..1c6f2cf 100644 --- a/src/diffpy/srmise/modelparts.py +++ b/src/diffpy/srmise/modelparts.py @@ -20,21 +20,22 @@ """ import logging +from importlib.metadata import version import matplotlib.pyplot as plt import numpy as np # Output of scipy.optimize.leastsq for a single parameter changed in scipy 0.8.0 # Before it returned a scalar, later it returned an array of length 1. -import pkg_resources as pr +from packaging.version import parse from scipy.optimize import leastsq from diffpy.srmise import srmiselog from diffpy.srmise.srmiseerrors import SrMiseFitError, SrMiseStaticOwnerError, SrMiseUndefinedCovarianceError logger = logging.getLogger("diffpy.srmise") -__spv__ = pr.get_distribution("scipy").version -__oldleastsqbehavior__ = pr.parse_version(__spv__) < pr.parse_version("0.8.0") +__spv__ = version("scipy") +__oldleastsqbehavior__ = parse(__spv__) < parse("0.8.0") class ModelParts(list): @@ -123,8 +124,8 @@ def fit( freepars, # initial parameters args=args, # arguments to residual, residual_jacobian Dfun=self.residual_jacobian, # explicit Jacobian - col_deriv=1, # order of derivatives in Jacobian - full_output=1, + col_deriv=True, # order of derivatives in Jacobian + full_output=True, maxfev=ntrials, ) except NotImplementedError: @@ -136,8 +137,8 @@ def fit( self.residual, # minimize this function freepars, # initial parameters args=args, # arguments to residual - col_deriv=1, # order of derivatives in Jacobian - full_output=1, + col_deriv=True, # order of derivatives in Jacobian + full_output=True, maxfev=ntrials, ) except Exception: @@ -202,7 +203,7 @@ def fit( try: cov.transform(in_format="internal", out_format=cov_format) except SrMiseUndefinedCovarianceError: - logger.warn("Covariance not defined. Fit may not have converged.") + logger.warning("Covariance not defined. Fit may not have converged.") return @@ -352,7 +353,7 @@ def __str__(self): def __getslice__(self, i, j): """Extends list.__getslice__""" - return self.__class__(list.__getslice__(self, i, j)) + return self.__class__(list.__getitem__(self, i, j)) def transform(self, in_format="internal", out_format="internal"): """Transforms format of parameters in this modelpart. diff --git a/src/diffpy/srmise/pdfdataset.py b/src/diffpy/srmise/pdfdataset.py index 8671e28..dc1f47b 100644 --- a/src/diffpy/srmise/pdfdataset.py +++ b/src/diffpy/srmise/pdfdataset.py @@ -201,7 +201,7 @@ def readStr(self, datastring): # find where the metadata starts metadata = "" - res = re.search(r"^#+\ +metadata\b\n", header, re.M) + res = re.search(r"^#+ +metadata\b\n", header, re.M) if res: metadata = header[res.end() :] header = header[: res.start()] @@ -343,7 +343,7 @@ def writeStr(self): # metadata if len(self.metadata) > 0: lines.append("# metadata") - for k, v in self.metadata.iteritems(): + for k, v in self.metadata.items(): lines.append("%s=%s" % (k, v)) # write data: lines.append("##### start data") @@ -408,10 +408,10 @@ class PDFDataFormatError(Exception): dataset = PDFDataSet("test") dataset.read(filename) print("== metadata ==") - for k, v in dataset.metadata.iteritems(): + for k, v in dataset.metadata.items(): print(k, "=", v) print("== data members ==") - for k, v in dataset.__dict__.iteritems(): + for k, v in dataset.__dict__.items(): if k in ("metadata", "robs", "Gobs", "drobs", "dGobs") or k[0] == "_": continue print(k, "=", v) From ee8f1b26cb78da58060ea376c81aaf8476d1648f Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 2 Aug 2024 21:24:32 +0800 Subject: [PATCH 26/65] change import path to make it work. (#48) --- src/diffpy/srmise/peaks/base.py | 4 ++-- src/diffpy/srmise/peaks/gaussian.py | 4 ++-- src/diffpy/srmise/peaks/gaussianoverr.py | 4 ++-- src/diffpy/srmise/peaks/terminationripples.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/diffpy/srmise/peaks/base.py b/src/diffpy/srmise/peaks/base.py index 653a0e3..aa7849c 100644 --- a/src/diffpy/srmise/peaks/base.py +++ b/src/diffpy/srmise/peaks/base.py @@ -298,8 +298,8 @@ def factory(peakstr, ownerlist): from numpy.random import randn from diffpy.srmise.modelcluster import ModelCluster - from diffpy.srmise.modelevaluators import AICc - from diffpy.srmise.peaks import GaussianOverR + from diffpy.srmise.modelevaluators.aicc import AICc + from diffpy.srmise.peaks.gaussianoverr import GaussianOverR res = 0.01 r = np.arange(2, 4, res) diff --git a/src/diffpy/srmise/peaks/gaussian.py b/src/diffpy/srmise/peaks/gaussian.py index ad2530b..126e3b6 100644 --- a/src/diffpy/srmise/peaks/gaussian.py +++ b/src/diffpy/srmise/peaks/gaussian.py @@ -344,8 +344,8 @@ def max(self, pars): from numpy.random import randn from diffpy.srmise.modelcluster import ModelCluster - from diffpy.srmise.modelevaluators import AICc - from diffpy.srmise.peaks import Peaks + from diffpy.srmise.modelevaluators.aicc import AICc + from diffpy.srmise.peaks.base import Peaks res = 0.01 r = np.arange(2, 4, res) diff --git a/src/diffpy/srmise/peaks/gaussianoverr.py b/src/diffpy/srmise/peaks/gaussianoverr.py index a5f9794..9252135 100644 --- a/src/diffpy/srmise/peaks/gaussianoverr.py +++ b/src/diffpy/srmise/peaks/gaussianoverr.py @@ -417,8 +417,8 @@ def max(self, pars): from numpy.random import randn from diffpy.srmise.modelcluster import ModelCluster - from diffpy.srmise.modelevaluators import AICc - from diffpy.srmise.peaks import Peaks + from diffpy.srmise.modelevaluators.aicc import AICc + from diffpy.srmise.peaks.base import Peaks res = 0.01 r = np.arange(2, 4, res) diff --git a/src/diffpy/srmise/peaks/terminationripples.py b/src/diffpy/srmise/peaks/terminationripples.py index e814820..1ac5d81 100644 --- a/src/diffpy/srmise/peaks/terminationripples.py +++ b/src/diffpy/srmise/peaks/terminationripples.py @@ -278,8 +278,8 @@ def extend_grid(self, r, dr): from numpy.random import randn from diffpy.srmise.modelcluster import ModelCluster - from diffpy.srmise.modelevaluators import AICc - from diffpy.srmise.peaks import Peaks + from diffpy.srmise.modelevaluators.aicc import AICc + from diffpy.srmise.peaks.base import Peaks from diffpy.srmise.peaks.gaussianoverr import GaussianOverR res = 0.01 From 8f6c37659300bdba31f361075fbe61e1079ac894 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 2 Aug 2024 21:25:18 +0800 Subject: [PATCH 27/65] fix import modules, py2->py3 (#49) --- src/diffpy/srmise/peakextraction.py | 16 ++++++++-------- src/diffpy/srmise/peakstability.py | 3 ++- src/diffpy/srmise/srmiselog.py | 1 - 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/diffpy/srmise/peakextraction.py b/src/diffpy/srmise/peakextraction.py index 7ac50bc..c62102b 100644 --- a/src/diffpy/srmise/peakextraction.py +++ b/src/diffpy/srmise/peakextraction.py @@ -21,10 +21,10 @@ import numpy as np from diffpy.srmise import srmiselog -from diffpy.srmise.baselines import Baseline +from diffpy.srmise.baselines.base import Baseline from diffpy.srmise.dataclusters import DataClusters from diffpy.srmise.modelcluster import ModelCluster, ModelCovariance -from diffpy.srmise.peaks import Peak, Peaks +from diffpy.srmise.peaks.base import Peak, Peaks from diffpy.srmise.srmiseerrors import SrMiseDataFormatError, SrMiseEstimationError, SrMiseFileError logger = logging.getLogger("diffpy.srmise") @@ -131,7 +131,7 @@ def setvars(self, quiet=False, **kwds): initial_peaks: Peaks instance. These peaks are present at the start of extraction. rng: Sequence specifying the least and greatest x-values over which to extract peaks. """ - for k, v in kwds.iteritems(): + for k, v in kwds.items(): if k in self.extractvars: if quiet: logger.debug("Setting variable %s=%s", k, v) @@ -183,7 +183,7 @@ def defaultvars(self, *args): self.effective_dy = self.effective_dy * np.ones(len(self.x)) if self.pf is None or "pf" in args: - from diffpy.srmise.peaks import GaussianOverR + from diffpy.srmise.peaks.gaussianoverr import GaussianOverR # TODO: Make a more useful default. self.pf = [GaussianOverR(self.x[-1] - self.x[0])] @@ -208,13 +208,13 @@ def defaultvars(self, *args): self.baseline = None if self.baseline is None or "baseline" in args: - from diffpy.srmise.baselines import Polynomial + from diffpy.srmise.baselines.polynomial import Polynomial bl = Polynomial(degree=-1) self.baseline = bl.actualize(np.array([]), "internal") if self.error_method is None or "error_method" in args: - from diffpy.srmise.modelevaluators import AIC + from diffpy.srmise.modelevaluators.aic import AIC self.error_method = AIC @@ -1307,8 +1307,8 @@ def fit_single(self): from numpy.random import randn - from diffpy.srmise.modelevaluators import AICc - from diffpy.srmise.peaks import GaussianOverR + from diffpy.srmise.modelevaluators.aicc import AICc + from diffpy.srmise.peaks.gaussianoverr import GaussianOverR srmiselog.setlevel("info") srmiselog.liveplotting(False) diff --git a/src/diffpy/srmise/peakstability.py b/src/diffpy/srmise/peakstability.py index f53eed2..83d2897 100644 --- a/src/diffpy/srmise/peakstability.py +++ b/src/diffpy/srmise/peakstability.py @@ -15,7 +15,8 @@ import matplotlib.pyplot as plt import numpy as np -from diffpy.srmise import ModelCluster, PDFPeakExtraction +from diffpy.srmise.modelcluster import ModelCluster +from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction # This is a total hack-job right now, and isn't suitable for diff --git a/src/diffpy/srmise/srmiselog.py b/src/diffpy/srmise/srmiselog.py index 361ea4b..d7b15cd 100644 --- a/src/diffpy/srmise/srmiselog.py +++ b/src/diffpy/srmise/srmiselog.py @@ -247,7 +247,6 @@ def read(self, filename): err, ) raise SrMiseFileError(emsg) - return None def readstr(self, datastring): """Read tracer ModelCluster from string. From 9f81b5fb2cad6e6a8c135f9c767faf7ef7a0d959 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 7 Aug 2024 00:36:46 +0800 Subject: [PATCH 28/65] fix broken import in doc, change README to rst file. (#51) * fix broken import in doc, change README to rst file. * fix os getcwd method --- devutils/prep.py | 2 +- doc/examples/{README => README.rst} | 0 doc/examples/extract_single_peak.py | 2 +- doc/examples/fit_initial.py | 7 ++++--- doc/examples/multimodel_known_dG1.py | 3 ++- doc/examples/multimodel_known_dG2.py | 2 +- doc/examples/multimodel_unknown_dG1.py | 5 +++-- doc/examples/multimodel_unknown_dG2.py | 2 +- doc/examples/parameter_summary.py | 6 +++--- doc/examples/query_results.py | 3 ++- src/diffpy/srmise/multimodelselection.py | 4 ++-- src/diffpy/srmise/peaks/base.py | 2 +- 12 files changed, 21 insertions(+), 17 deletions(-) rename doc/examples/{README => README.rst} (100%) diff --git a/devutils/prep.py b/devutils/prep.py index af3b446..9eee5e8 100644 --- a/devutils/prep.py +++ b/devutils/prep.py @@ -6,7 +6,7 @@ import re import sys -__basedir__ = os.getcwdu() +__basedir__ = os.getcwd() from numpy.compat import unicode diff --git a/doc/examples/README b/doc/examples/README.rst similarity index 100% rename from doc/examples/README rename to doc/examples/README.rst diff --git a/doc/examples/extract_single_peak.py b/doc/examples/extract_single_peak.py index b32b0be..5004172 100644 --- a/doc/examples/extract_single_peak.py +++ b/doc/examples/extract_single_peak.py @@ -28,7 +28,7 @@ import matplotlib.pyplot as plt from diffpy.srmise.applications.plot import makeplot -from diffpy.srmise.baselines import Polynomial +from diffpy.srmise.baselines.polynomial import Polynomial from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction diff --git a/doc/examples/fit_initial.py b/doc/examples/fit_initial.py index 71979e8..fa9961c 100644 --- a/doc/examples/fit_initial.py +++ b/doc/examples/fit_initial.py @@ -21,10 +21,11 @@ import matplotlib.pyplot as plt -from diffpy.srmise import PDFPeakExtraction from diffpy.srmise.applications.plot import makeplot -from diffpy.srmise.baselines import FromSequence -from diffpy.srmise.peaks import Peaks, TerminationRipples +from diffpy.srmise.baselines.fromsequence import FromSequence +from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction +from diffpy.srmise.peaks.base import Peaks +from diffpy.srmise.peaks.terminationripples import TerminationRipples def run(plot=True): diff --git a/doc/examples/multimodel_known_dG1.py b/doc/examples/multimodel_known_dG1.py index f0ceb1b..f1fe508 100644 --- a/doc/examples/multimodel_known_dG1.py +++ b/doc/examples/multimodel_known_dG1.py @@ -37,7 +37,8 @@ import numpy as np import diffpy.srmise.srmiselog as sml -from diffpy.srmise import MultimodelSelection, PDFPeakExtraction +from diffpy.srmise.multimodelselection import MultimodelSelection +from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction def run(plot=True): diff --git a/doc/examples/multimodel_known_dG2.py b/doc/examples/multimodel_known_dG2.py index e72db46..6e6fdb3 100644 --- a/doc/examples/multimodel_known_dG2.py +++ b/doc/examples/multimodel_known_dG2.py @@ -35,8 +35,8 @@ import numpy as np import diffpy.srmise.srmiselog as sml -from diffpy.srmise import MultimodelSelection from diffpy.srmise.applications.plot import makeplot +from diffpy.srmise.multimodelselection import MultimodelSelection # distances from ideal Ag (refined to PDF) dcif = np.array( diff --git a/doc/examples/multimodel_unknown_dG1.py b/doc/examples/multimodel_unknown_dG1.py index 7a6a2b8..4570f78 100644 --- a/doc/examples/multimodel_unknown_dG1.py +++ b/doc/examples/multimodel_unknown_dG1.py @@ -36,8 +36,9 @@ import numpy as np import diffpy.srmise.srmiselog as sml -from diffpy.srmise import MultimodelSelection, PDFPeakExtraction -from diffpy.srmise.baselines import FromSequence +from diffpy.srmise.baselines.fromsequence import FromSequence +from diffpy.srmise.multimodelselection import MultimodelSelection +from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction def run(plot=True): diff --git a/doc/examples/multimodel_unknown_dG2.py b/doc/examples/multimodel_unknown_dG2.py index 9a4b24a..c4bbef4 100644 --- a/doc/examples/multimodel_unknown_dG2.py +++ b/doc/examples/multimodel_unknown_dG2.py @@ -42,8 +42,8 @@ import numpy as np import diffpy.srmise.srmiselog as sml -from diffpy.srmise import MultimodelSelection from diffpy.srmise.applications.plot import makeplot +from diffpy.srmise.multimodelselection import MultimodelSelection # distances from ideal (unrefined) C60 dcif = np.array( diff --git a/doc/examples/parameter_summary.py b/doc/examples/parameter_summary.py index 1b8a195..1d4095c 100644 --- a/doc/examples/parameter_summary.py +++ b/doc/examples/parameter_summary.py @@ -29,10 +29,10 @@ import matplotlib.pyplot as plt -from diffpy.srmise import PDFPeakExtraction from diffpy.srmise.applications.plot import makeplot -from diffpy.srmise.baselines import Polynomial -from diffpy.srmise.peaks import GaussianOverR +from diffpy.srmise.baselines.polynomial import Polynomial +from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction +from diffpy.srmise.peaks.gaussianoverr import GaussianOverR def run(plot=True): diff --git a/doc/examples/query_results.py b/doc/examples/query_results.py index 8035921..c012567 100644 --- a/doc/examples/query_results.py +++ b/doc/examples/query_results.py @@ -29,7 +29,8 @@ import matplotlib.pyplot as plt import numpy as np -from diffpy.srmise import ModelCovariance, PDFPeakExtraction +from diffpy.srmise.modelcluster import ModelCovariance +from diffpy.srmise.pdfpeakextraction import PDFPeakExtraction def run(plot=True): diff --git a/src/diffpy/srmise/multimodelselection.py b/src/diffpy/srmise/multimodelselection.py index 3fc4f5b..a10dba2 100644 --- a/src/diffpy/srmise/multimodelselection.py +++ b/src/diffpy/srmise/multimodelselection.py @@ -507,8 +507,8 @@ def plot3dclassprobs(self, **kwds): probfilter = kwds.pop("probfilter", [0.0, 1.0]) class_size = kwds.pop("class_size", "number") norm = kwds.pop("norm", "auto") - cmap = kwds.pop("cmap", cm.jet) - highlight_cmap = kwds.pop("highlight_cmap", cm.gray) + cmap = kwds.pop("cmap", cm.get_cmap("jet")) + highlight_cmap = kwds.pop("highlight_cmap", cm.get_cmap("gray")) title = kwds.pop("title", True) p_alpha = kwds.pop("p_alpha", 0.7) scale = kwds.pop("scale", 1.0) diff --git a/src/diffpy/srmise/peaks/base.py b/src/diffpy/srmise/peaks/base.py index aa7849c..44c7963 100644 --- a/src/diffpy/srmise/peaks/base.py +++ b/src/diffpy/srmise/peaks/base.py @@ -189,7 +189,7 @@ def match_at(self, x, y): return False return any_scaled - def sort(self, key="position"): + def sort(self, reverse=False, key="position"): """Sort peaks in order specified by key.""" keypars = np.array([p[key] for p in self]) order = keypars.argsort() From 8270200219da8d0cc89bc6ca3c05dcf5a28307a4 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 7 Aug 2024 00:37:32 +0800 Subject: [PATCH 29/65] fix p2 to p3 (#52) --- src/diffpy/srmise/basefunction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/srmise/basefunction.py b/src/diffpy/srmise/basefunction.py index de69527..ebeee5e 100644 --- a/src/diffpy/srmise/basefunction.py +++ b/src/diffpy/srmise/basefunction.py @@ -346,7 +346,7 @@ def writestr(self, baselist): else: lines.append("base=%s" % repr(None)) # Write all other metadata - for k, (v, f) in self.metadict.iteritems(): + for k, (v, f) in self.metadict.items(): lines.append("%s=%s" % (k, f(v))) datastring = "\n".join(lines) + "\n" return datastring From 89c8c0becaf945df20bb792732609551fc4d1323 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Sun, 11 Aug 2024 00:25:44 +0800 Subject: [PATCH 30/65] add test for dataclusters (#54) * add test for dataclusters * define eq method in dataclusters.py * change parametrization form * add one more case and change reference name to actual * delete comment * add two more tests for DataClusters class function. * change in docstring for clearer explanation for clear method, remove duplicated case for testing behavior, remove other tests. * change clear method docstring into numpydoc format. Delete dtype for numpy array. * remove block * Make edition to condition on res, refactor for setdata to make behavior of the test passed. * change condition on res * add condition on x and res are incompatible, update test. * revert change in setdata method. --- src/diffpy/srmise/dataclusters.py | 57 +++++++++++++++----- src/diffpy/srmise/pdfpeakextraction.py | 2 +- src/diffpy/srmise/tests/test_dataclusters.py | 12 +++++ 3 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 src/diffpy/srmise/tests/test_dataclusters.py diff --git a/src/diffpy/srmise/dataclusters.py b/src/diffpy/srmise/dataclusters.py index 1eabb26..4cb46be 100644 --- a/src/diffpy/srmise/dataclusters.py +++ b/src/diffpy/srmise/dataclusters.py @@ -68,12 +68,36 @@ def __init__(self, x, y, res): def __iter__(self): return self + def __eq__(self, other): + if not isinstance(other, DataClusters): + return False + return ( + np.array_equal(self.x, other.x) + and np.array_equal(self.y, other.y) + and np.array_equal(self.data_order, other.data_order) + and np.array_equal(self.clusters, other.clusters) + and self.res == other.res + and self.current_idx == other.current_idx + and self.lastcluster_idx == other.lastcluster_idx + and self.lastpoint_idx == other.lastpoint_idx + and self.status == other.status + ) + def clear(self): - """Clear all members, including user data.""" + """ + Clear all data and reset the cluster object to a transient initial state. + + The purpose of this method is to provide a clean state before creating new clustering operations. + The object is updated in-place and no new instance is returned. + + Returns + ------- + None + """ self.x = np.array([]) self.y = np.array([]) - self.data_order = np.array([], dtype=np.int32) - self.clusters = np.array([[]], dtype=np.int32) + self.data_order = np.array([]) + self.clusters = np.array([[]]) self.res = 0 self.current_idx = 0 self.lastcluster_idx = None @@ -106,21 +130,26 @@ def setdata(self, x, y, res): # 3) r isn't sorted? if len(x) != len(y): raise ValueError("Sequences x and y must have the same length.") - if res <= 0: - raise ValueError("Resolution res must be greater than 0.") + if res < 0: + raise ValueError("Resolution res must be non-negative.") # Test for sorting? - self.x = x self.y = y self.res = res - - self.data_order = self.y.argsort() # Defines order of clustering - self.clusters = np.array([[self.data_order[-1], self.data_order[-1]]]) - self.current_idx = len(self.data_order) - 1 - self.lastcluster_idx = 0 - self.lastpoint_idx = self.data_order[-1] - - self.status = self.READY + # If x sequence size is empty, set the object into Initialized state. + if x.size == 0 and res == 0: + self.data_order = np.array([]) + self.clusters = np.array([[]]) + self.current_idx = 0 + self.lastpoint_idx = None + self.status = self.INIT + else: + self.data_order = self.y.argsort() # Defines order of clustering + self.clusters = np.array([[self.data_order[-1], self.data_order[-1]]]) + self.current_idx = len(self.data_order) - 1 + self.lastpoint_idx = self.data_order[-1] + self.status = self.READY + self.lastcluster_idx = None return def next(self): diff --git a/src/diffpy/srmise/pdfpeakextraction.py b/src/diffpy/srmise/pdfpeakextraction.py index ab5f03d..58b4132 100644 --- a/src/diffpy/srmise/pdfpeakextraction.py +++ b/src/diffpy/srmise/pdfpeakextraction.py @@ -119,7 +119,7 @@ def setvars(self, quiet=False, **kwds): quiet: [False] Log changes quietly. Keywords - cres: The clustering resolution, must be > 0. + cres: The clustering resolution, must be >= 0. effective_dy: The uncertainties actually used during extraction dg: Alias for effective_dy pf: Sequence of PeakFunctionBase subclass instances. diff --git a/src/diffpy/srmise/tests/test_dataclusters.py b/src/diffpy/srmise/tests/test_dataclusters.py new file mode 100644 index 0000000..ecd243e --- /dev/null +++ b/src/diffpy/srmise/tests/test_dataclusters.py @@ -0,0 +1,12 @@ +import numpy as np + +from diffpy.srmise.dataclusters import DataClusters + + +def test_clear(): + # Initialize DataClusters with input parameters + actual = DataClusters(x=np.array([1, 2, 3]), y=np.array([3, 2, 1]), res=4) + expected = DataClusters(x=np.array([]), y=np.array([]), res=0) + # Perform the clear operation + actual.clear() + assert actual == expected From 5aad8bd43fb50e27f41b1d275bdfb304cb40ca5c Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 10 Aug 2024 17:10:14 -0400 Subject: [PATCH 31/65] Eq tests (#59) * remove diffpy/srmise tree * test for eq --- diffpy/srmise/modelparts.py | 610 ------------------- src/diffpy/srmise/tests/test_dataclusters.py | 22 + 2 files changed, 22 insertions(+), 610 deletions(-) delete mode 100644 diffpy/srmise/modelparts.py diff --git a/diffpy/srmise/modelparts.py b/diffpy/srmise/modelparts.py deleted file mode 100644 index 37d6e0c..0000000 --- a/diffpy/srmise/modelparts.py +++ /dev/null @@ -1,610 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# SrMise by Luke Granlund -# (c) 2014 trustees of the Michigan State University -# (c) 2024 trustees of Columia University in the City of New York -# All rights reserved. -# -# File coded by: Luke Granlund -# -# See LICENSE.txt for license information. -# -############################################################################## -"""Module for representing instances of mathematical functions. - -Classes -------- -ModelPart: Superclass of Peak and Baseline -ModelParts: Collection (list) of ModelPart instances. -""" - -import logging - -import matplotlib.pyplot as plt -import numpy as np - -# Output of scipy.optimize.leastsq for a single parameter changed in scipy 0.8.0 -# Before it returned a scalar, later it returned an array of length 1. -import pkg_resources as pr -from scipy.optimize import leastsq - -from diffpy.srmise import srmiselog -from diffpy.srmise.srmiseerrors import SrMiseFitError, SrMiseStaticOwnerError, SrMiseUndefinedCovarianceError - -logger = logging.getLogger("diffpy.srmise") -__spv__ = pr.get_distribution("scipy").version -__oldleastsqbehavior__ = pr.parse_version(__spv__) < pr.parse_version("0.8.0") - - -class ModelParts(list): - """A collection of ModelPart instances. - - Methods - ------- - copy: Return deep copy - fit: Fit to given data - npars: Return total number of parameters - pack_freepars: Update free parameters with values in given sequence - residual: Return residual of model - residual_jacobian: Return jacobian of residual of model - transform: Change format of parameters. - value: Return value of model - unpack_freepars: Return sequence containing value of all free parameters - """ - - def __init__(self, *args, **kwds): - list.__init__(self, *args, **kwds) - - def fit( - self, - r, - y, - y_error, - range=None, - ntrials=0, - cov=None, - cov_format="default_output", - ): - """Chi-square fit of all free parameters to given data. - - There must be at least as many free parameters as data points. - Fitting is performed with the MINPACK leastsq() routine exposed by scipy. - - Parameters - r - Sequence of r values over which to fit - y - Sequence of y values over which to fit - y_error - Sequence of uncertainties in y - range - Slice object specifying region of r and y over which to fit. - Fits over all the data by default. - ntrials - The maximum number of function evaluations while fitting. - cov - Optional ModelCovariance object preserves covariance information. - cov_format - Parameterization to use in cov. - """ - freepars = self.unpack_freepars() - if len(freepars) >= len(r): - emsg = ( - "Cannot fit model with " - + str(len(freepars)) - + " free parametersbut only " - + str(len(r)) - + " data points." - ) - raise SrMiseFitError(emsg) - if len(freepars) == 0: - # emsg = "Cannot fit model with no free parameters." - # raise SrMiseFitError(emsg) - return - - if range is None: - range = slice(None) - - args = (r, y, y_error, range) - - if srmiselog.liveplots: - plt.figure(1) - plt.ioff() - plt.subplot(211) - plt.cla() - plt.title("Before") - plt.plot(r, y, label="_nolabel_") - plt.plot( - r, - (y - self.value(r, range=range)) - 1.1 * (max(y) - min(y)), - label="_nolabel_", - ) - for p in self: - plt.plot(r, p.value(r, range=range), label=str(p)) - plt.ion() - - try: - f = leastsq( - self.residual, # minimize this function - freepars, # initial parameters - args=args, # arguments to residual, residual_jacobian - Dfun=self.residual_jacobian, # explicit Jacobian - col_deriv=1, # order of derivatives in Jacobian - full_output=1, - maxfev=ntrials, - ) - except NotImplementedError: - # TODO: Figure out if is worth checking for residual_jacobian - # before leastsq(). This exception will either occur almost never - # or extremely frequently, and the extra evaluations will add up. - logger.info("One or more functions do not define residual_jacobian().") - f = leastsq( - self.residual, # minimize this function - freepars, # initial parameters - args=args, # arguments to residual - col_deriv=1, # order of derivatives in Jacobian - full_output=1, - maxfev=ntrials, - ) - except Exception: - # Sadly, KeyboardInterrupt, etc. is reraised as minpack.error - # Not much I can do about that, though. - import traceback - - emsg = ( - "Unexpected error in modelparts.fit(). Original exception:\n" - + traceback.format_exc() - + "End original exception." - ) - raise SrMiseFitError(emsg) - - result = f[0] - if __oldleastsqbehavior__ and len(freepars) == 1: - # leastsq returns a scalar when there is only one parameter - result = np.array([result]) - - self.pack_freepars(result) - - if srmiselog.liveplots: - plt.draw() - plt.ioff() - plt.figure(1) - plt.subplot(212) - plt.cla() - plt.title("After") - plt.ion() - plt.plot( - r, - y, - r, - (y - self.value(r, range=range)) - 1.1 * (max(y) - min(y)), - *[i for sublist in [[r, p.value(r, range=range)] for p in self] for i in sublist], - ) - plt.draw() - - if srmiselog.wait: - print( - "Press 'Enter' to continue...", - ) - input() - - if f[4] not in (1, 2, 3, 4): - emsg = "Fit did not succeed -- " + str(f[3]) - raise SrMiseFitError(emsg) - - # clean up parameters - for p in self: - p.pars = p.owner().transform_parameters(p.pars, in_format="internal", out_format="internal") - - # Supply estimated covariance matrix if requested. - # The precise relationship between f[1] and estimated covariance matrix is a little unclear from - # the documentation of leastsq. This is the interpretation given by scipy.optimize.curve_fit, - # which is a wrapper around leastsq. - if cov is not None: - pcov = f[1] - fvec = f[2]["fvec"] - dof = len(r) - len(freepars) - cov.setcovariance(self, pcov * np.sum(fvec**2) / dof) - try: - cov.transform(in_format="internal", out_format=cov_format) - except SrMiseUndefinedCovarianceError: - logger.warn("Covariance not defined. Fit may not have converged.") - - return - - # # Notes on the fit f - # f[0] = solution - # f[1] = Uses the fjac and ipvt optional outputs to construct an estimate of the jacobian around the solution. - # None if a singular matrix encountered (indicates very flat curvature in some direction). - # This matrix must be multiplied by the residual variance to get the covariance of the parameter - # estimates - see curve fit. - # f[2] = dictionary{nfev: int, fvec: array(), fjac: array(), ipvt: array(), qtf: array()} - # nfev - The number of function calls made - # fvec - function (residual) evaluated at solution - # fjac - "a permutation of the R matrix of a QR factorization of the final Jacobian." - # ipvt - integer array defining a permutation matrix P such that fjac*P=QR - # qtf - transpose(q)*fvec - # f[3] = message about results of fit - # f[4] = integer flag. Fit was successful on 1,2,3, or 4. Otherwise unsuccessful. - - def npars(self, count_fixed=True): - """Return total number of parameters in all parts. - - Parameters - count_fixed - Boolean which determines if fixed parameters are - are included in the count. - """ - n = 0 - for p in self: - n += p.npars(count_fixed=count_fixed) - return n - - def pack_freepars(self, freepars): - """Update parameters with values from sequence of freepars.""" - if np.isnan(freepars).any(): - emsg = "Non-numeric free parameters." - raise ValueError(emsg) - freeidx = 0 - for p in self: - freeidx += p.update(freepars[freeidx:]) - - def residual(self, freepars, r, y_expected, y_error, range=None): - """Calculate residual of all parameters. - - Parameters - freepars - sequence of free parameters - r - the input domain - y_expected - sequence of expected values - y_error - sequence of uncertainties in y-variable - range - Slice object specifying region of r and y over which to fit. - All the data by default. - """ - self.pack_freepars(freepars) - total = self.value(r, range) - try: - if range is None: - range = slice(0, len(r)) - return (y_expected[range] - total[range]) / y_error[range] - except TypeError: - return (y_expected - total) / y_error - - def residual_jacobian(self, freepars, r, y_expected, y_error, range=None): - """Calculate the Jacobian of freepars. - - Parameters - freepars - sequence of free parameters - r - the input domain - y_expected - sequence of expected values - y_error - sequence of uncertainties in y-variable - range - Slice object specifying region of r and y over which to fit. - All the data by default. - """ - if len(freepars) == 0: - raise ValueError( - "Argument freepars has length 0. The Jacobian " "is only defined with >=1 free parameters." - ) - - self.pack_freepars(freepars) - tempJac = [] - for p in self: - tempJac[len(tempJac) :] = p.jacobian(r, range) - # Since the residual is (expected - calculated) the jacobian - # of the residual has a minus sign. - jac = -np.array([j for j in tempJac if j is not None]) - try: - if range is None: - range = slice(0, len(r)) - return jac[:, range] / y_error[range] - except TypeError: - return jac / y_error - - def value(self, r, range=None): - """Calculate total value of all parts over range. - - Parameters - r - the input domain - range - Slice object specifying region of r and y over which to fit. - All the data by default. - """ - total = r * 0.0 - for p in self: - total += p.value(r, range) - return total - - def unpack_freepars(self): - """Return array of all free parameters.""" - # To check: ravel() sometimes returns a reference and othertimes a copy. - # Do I need to use flatten() instead? - return np.concatenate([p.compress() for p in self]).ravel() - - def covariance(self, format="internal", **kwds): - """Return estimated covariance matrix of the model. - - The covariance matrix may be given in terms of any parameterization - defined by the formats for each individual ModelPart. - - Parameters - format - The format ("internal" by default) to use for all ModelParts. - This may be overridden for specific peaks as shown below. - - Keywords - f0 - The format of the 0th ModelPart - f1 - The format of the 1st ModelPart - etc. - """ - formats = [format for p in self] - - for k, v in kwds.items(): - try: - int(k[1:]) - except ValueError: - emsg = "Invalid format keyword '%s'. They must be specified as 'f0', 'f1', etc." % k - raise ValueError(emsg) - - formats[int(k[1:])] = v - - return - - def copy(self): - """Return deep copy of this ModelParts. - - The original and the copy are completely independent, except each - ModelPart and its copy still reference the same owner.""" - return type(self).__call__([p.copy() for p in self]) - - def __str__(self): - """Return string representation of this ModelParts.""" - return "".join([str(p) + "\n" for p in self]) - - def __getslice__(self, i, j): - """Extends list.__getslice__""" - return self.__class__(list.__getslice__(self, i, j)) - - def transform(self, in_format="internal", out_format="internal"): - """Transforms format of parameters in this modelpart. - - Parameters - in_format - The format the parameters are already in. - out_format - The format the parameters are transformed to. - """ - for p in self: - try: - p.pars = p.owner().transform_parameters(p.pars, in_format, out_format) - except ValueError: - logger.info( - "Invalid parameter transformation: Ignoring %s->%s for function of type %s." - % (in_format, out_format, p.owner().getmodule()) - ) - - -# End of class ModelParts - - -class ModelPart(object): - """Represents a single part (instance of some function) of a model. - - Members - ------- - pars - Array containing the parameters of this model part - free - Array containing boolean values defining whether the corresponding parameter - is free or not. - removable - Boolean determining whether or not this model part can be - removed during extraction. - static_owner - Boolean determines if owner can be changed with changeowner() - - Methods - ------- - changeowner - Change the owner of self - copy - Return deep copy of self - compress - Return parameters with non-free parameters removed - jacobian - Return jacobian - getfree - Return free parameter by index or keyword define by owner - npars - Return number of parameters in self - owner - Return self.owner - setfree - Set a free parameter by index or keyword defined by owner - update - Update free parameters with values in given sequence - value - Return value - writestr - Return string representation of self - """ - - def __init__(self, owner, pars, free=None, removable=True, static_owner=False): - """Set instance members. - - Parameters - owner - an instance of a BaseFunction subclass - pars - Sequence of parameters which specify the function explicitly - free - Sequence of Boolean variables. If False, the corresponding - parameter will not be changed. - removable - Boolean determines whether this part can be removed. - static_owner - Whether or not the part can be changed with - changeowner() - - Note that free and removable are not mutually exclusive. If any - pars are not free but removable=True then the part may be removed, but - the held parameters for this part will remain unchanged until then. - """ - self._owner = owner - - if len(pars) != owner.npars: - emsg = "The length of pars must equal the number of parameters " + "specified by the model part owner." - raise ValueError(emsg) - self.pars = np.array(pars[:]) # pars[:] in case pars is a ModelPart - - if free is None: - self.free = np.array([True for p in pars], dtype=bool) - else: - self.free = np.array(free, dtype=bool) - if len(self.free) != owner.npars: - emsg = ( - "The length of free must be equal to the number of " - + "parameters specified by the model part owner." - ) - raise ValueError(emsg) - - self.removable = removable - self.static_owner = static_owner - - def changeowner(self, owner): - """Change the owner of this part. - - Does not change the parameters associated with this model part. Raises - SrMiseStaticOwnerError if this peak has been declared to have a static - owner, or if the number of parameters is incompatible. - - Parameters - owner - an instance of a BaseFunction subclass - """ - if self.static_owner and self._owner is not owner: - emsg = "Cannot change owner if static_owner is True." - raise SrMiseStaticOwnerError(emsg) - if self._owner.npars != owner.npars: - emsg = "New owner specifies different number of parameters than " + "original owner." - raise SrMiseStaticOwnerError(emsg) - self._owner = owner - - def compress(self): - """Return part parameters with non-free values removed.""" - return self.pars[self.free] - - def jacobian(self, r, range=None): - """Return jacobian of this part over r. - - Parameters - r - the input domain - range - Slice object specifying region of r and y over which to fit. - All the data by default. - """ - return self._owner.jacobian(self, r, range) - - def owner(self): - """Return the BaseFunction subclass instance which owns this part.""" - return self._owner - - def update(self, freepars): - """Sequentially update free parameters from freepars. - - Parameters - freepars - sequence of new parameter values. May contain more - parameters than can actually be updated. - - Return number of parameters updated from freepars. - """ - numfree = self.npars(count_fixed=False) - if len(freepars) < numfree: - pass # raise "freepars does not have enough elements to - # update every unheld parameter." - # TODO: Check if I need to make copies here, or if references - # to parameters are safe. - self.pars[self.free] = freepars[:numfree] - return numfree - - def value(self, r, range=None): - """Return value of peak over r. - - Parameters - r - the input domain - range - Slice object specifying region of r and y over which to fit. - All the data by default. - """ - return self._owner.value(self, r, range) - - def copy(self): - """Return a deep copy of this ModelPart. - - The original and the copy are completely independent, except they both - reference the same owner.""" - return type(self).__call__(self._owner, self.pars, self.free, self.removable, self.static_owner) - - def __getitem__(self, key_or_idx): - """Return parameter of peak corresponding with key_or_idx. - - Parameters - key_or_idx - An integer index, slice, or key from owner's parameter - dictionary. - """ - if key_or_idx in self._owner.parameterdict: - return self.pars[self._owner.parameterdict[key_or_idx]] - else: - return self.pars[key_or_idx] - - def getfree(self, key_or_idx): - """Return value of free corresponding with key_or_idx. - - Parameters - key_or_idx - An integer index, slice, or key from owner's parameter - dictionary.""" - if key_or_idx in self._owner.parameterdict: - return self.free[self._owner.parameterdict[key_or_idx]] - else: - return self.free[key_or_idx] - - def setfree(self, key_or_idx, value): - """Set value of free corresponding with key_or_idx. - - Parameters - key_or_idx - An integer index, slice, or key from owner's parameter - dictionary. - value: A boolean""" - if key_or_idx in self._owner.parameterdict: - self.free[self._owner.parameterdict[key_or_idx]] = value - else: - self.free[key_or_idx] = value - - def __len__(self): - """Return number of parameters, including any fixed ones.""" - return self._owner.npars - - def npars(self, count_fixed=True): - """Return total number of parameters in all parts. - - Parameters - count_fixed - Boolean which determines if fixed parameters are - are included in the count.""" - if count_fixed: - return self._owner.npars - else: - return (self.free is True).sum() - - def __str__(self): - """Return string representation of ModelPart parameters.""" - return str(self._owner.transform_parameters(self.pars, in_format="internal", out_format="default_output")) - - def __eq__(self, other): - """ """ - if hasattr(other, "_owner"): - return ( - (self._owner is other._owner) - and np.all(self.pars == other.pars) - and np.all(self.free == other.free) - and self.removable == other.removable - ) - else: - return False - - def __ne__(self, other): - """ """ - return not self == other - - def writestr(self, ownerlist): - """Return string representation of ModelPart. - - The value of owner is determined by its index in ownerlist. - - Parameters - ownerlist - List of owner functions - """ - if self._owner not in ownerlist: - emsg = "ownerlist does not contain this ModelPart's owner." - raise ValueError(emsg) - lines = [] - lines.append("owner=%s" % repr(ownerlist.index(self._owner))) - - # Lists/numpy arrays don't give full representation of long lists - lines.append("pars=[%s]" % ", ".join([repr(p) for p in self.pars])) - lines.append("free=[%s]" % ", ".join([repr(f) for f in self.free])) - lines.append("removable=%s" % repr(self.removable)) - lines.append("static_owner=%s" % repr(self.static_owner)) - datastring = "\n".join(lines) + "\n" - return datastring - - -# End of class ModelPart - -# simple test code -if __name__ == "__main__": - - pass diff --git a/src/diffpy/srmise/tests/test_dataclusters.py b/src/diffpy/srmise/tests/test_dataclusters.py index ecd243e..0ea6b42 100644 --- a/src/diffpy/srmise/tests/test_dataclusters.py +++ b/src/diffpy/srmise/tests/test_dataclusters.py @@ -1,3 +1,5 @@ +from copy import copy + import numpy as np from diffpy.srmise.dataclusters import DataClusters @@ -10,3 +12,23 @@ def test_clear(): # Perform the clear operation actual.clear() assert actual == expected + + +def test___eq__(): + actual = DataClusters(np.array([1, 2, 3]), np.array([3, 2, 1]), 0) + expected = DataClusters(np.array([1, 2, 3]), np.array([3, 2, 1]), 0) + assert expected == actual + attributes = vars(actual) + for attr_key, attr_val in attributes.items(): + reset = copy(attr_val) + assert expected == actual + if attr_val is not None: + attributes.update({attr_key: attr_val + 1}) + else: + attributes.update({attr_key: 1}) + try: + assert not expected == actual + except AssertionError: + print(f"not-equal test failed on {attr_key}") + assert not expected == actual + attributes.update({attr_key: reset}) From 5be09d5a100add0985abf14bee05eba3ec9249e6 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Sun, 11 Aug 2024 12:49:18 +0800 Subject: [PATCH 32/65] add attributes in eq method (#60) --- src/diffpy/srmise/dataclusters.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/diffpy/srmise/dataclusters.py b/src/diffpy/srmise/dataclusters.py index 4cb46be..e5a14e6 100644 --- a/src/diffpy/srmise/dataclusters.py +++ b/src/diffpy/srmise/dataclusters.py @@ -81,6 +81,10 @@ def __eq__(self, other): and self.lastcluster_idx == other.lastcluster_idx and self.lastpoint_idx == other.lastpoint_idx and self.status == other.status + and self.INIT == other.INIT + and self.READY == other.READY + and self.CLUSTERING == other.CLUSTERING + and self.DONE == other.DONE ) def clear(self): From d1bf3d4af3cca6dc684b75f1355ec70f9f20deec Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 14 Aug 2024 00:00:27 +0800 Subject: [PATCH 33/65] Add set data test cases (#61) * add test cases to test files and make edition to make sure the behavior of the test pass. * [pre-commit.ci] auto fixes from pre-commit hooks * change case in test__eq__ to be compatible with the behavior of setdata * delete text and redundant tests * tweaking error message in DataClusters * [pre-commit.ci] auto fixes from pre-commit hooks * update test for checking implicit attributes for setdata function * [pre-commit.ci] auto fixes from pre-commit hooks * update test for setdata function * update setdata test to right format. * update to constructor test & make setdata clear function private * final tweaks to tests by Simon * fix actual_attribute typo * final refactor of actual_attr --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Simon Billinge --- src/diffpy/srmise/dataclusters.py | 80 ++++++++++++-------- src/diffpy/srmise/tests/test_dataclusters.py | 77 ++++++++++++++++--- 2 files changed, 114 insertions(+), 43 deletions(-) diff --git a/src/diffpy/srmise/dataclusters.py b/src/diffpy/srmise/dataclusters.py index e5a14e6..b881f17 100644 --- a/src/diffpy/srmise/dataclusters.py +++ b/src/diffpy/srmise/dataclusters.py @@ -22,43 +22,57 @@ class DataClusters: - """Find clusters corresponding to peaks in numerical x-, y-value arrays. + """Find clusters corresponding to peaks in the PDF (y-array) - DataClusters determines which points, given a pair of x- and y-value - sequences, roughly correspond to which visible peaks in that data. This - division is contiguous, with borders between clusters near relative + DataClusters determines which points in inter-atomic distane, r, + correspond to peaks in the PDF. The division between clusters + is contiguous, with borders between clusters likely near relative minima in the data. Clusters are iteratively formed around points with the largest - y-coordinates. New clusters are added only when the unclustered data + PDF values. New clusters are added only when the unclustered data point under consideration is greater than a given distance (the 'resolution') from the nearest existing cluster. Data members - x - sequence of x coordinates. - y - sequence of y values - res - clustering 'resolution' - data_order - array of x, y indices ordered by decreasing y - clusters - array of cluster ranges - current_idx - index of data_order currently considered + ------------ + x : array + The array of r values. + y : sequence of y values + The array of PDF values, G(r) + res : int + The clustering resolution, i.e., the number of points another point has to + be away from the center of an existing cluster to before a new cluster is + formed. A value of zero allows every point to be cluster. + data_order : array + The array of x, y indices ordered by decreasing y + clusters : + The array of cluster ranges + current_idx - int + The index of data_order currently considered """ def __init__(self, x, y, res): - """Initializes the data to be clustered, and the 'resolution' to use. + """Constructor Parameters - x - numeric sequence of x-value sorted in ascending order - y - corresponding sequence of y-values - res - clustering 'resolution' + ---------- + x : array + The array of r values. + y : sequence of y values + The array of PDF values, G(r) + res : int + The clustering resolution, i.e., the number of points another point has to + be away from the center of an existing cluster to before a new cluster is + formed. A value of zero allows every point to be cluster. """ # Track internal state of clustering. self.INIT = 0 self.READY = 1 self.CLUSTERING = 2 self.DONE = 3 - - self.clear() - self.setdata(x, y, res) + self._clear() + self._setdata(x, y, res) return @@ -87,7 +101,7 @@ def __eq__(self, other): and self.DONE == other.DONE ) - def clear(self): + def _clear(self): """ Clear all data and reset the cluster object to a transient initial state. @@ -120,35 +134,37 @@ def reset_clusters(self): self.status = self.READY return - def setdata(self, x, y, res): + def _setdata(self, x, y, res): """Assign data members for x- and y-coordinates, and resolution. Parameters - x - numeric sequence of x-value sorted in ascending order - y - corresponding sequence of y-values - res - clustering 'resolution' + ---------- + x : array + The array of r values. + y : sequence of y values + The array of PDF values, G(r) + res : int + The clustering resolution, i.e., the number of points another point has to + be away from the center of an existing cluster to before a new cluster is + formed. A value of zero allows every point to be cluster. """ - # Test for error conditions - # 1) Length mismatch - # 2) Bound errors for res - # 3) r isn't sorted? if len(x) != len(y): raise ValueError("Sequences x and y must have the same length.") if res < 0: - raise ValueError("Resolution res must be non-negative.") - # Test for sorting? + raise ValueError( + "Value of resolution parameter is less than zero. Please rerun specifying a non-negative res" + ) self.x = x self.y = y self.res = res - # If x sequence size is empty, set the object into Initialized state. - if x.size == 0 and res == 0: + if x.size == 0: self.data_order = np.array([]) self.clusters = np.array([[]]) self.current_idx = 0 self.lastpoint_idx = None self.status = self.INIT else: - self.data_order = self.y.argsort() # Defines order of clustering + self.data_order = self.y.argsort() self.clusters = np.array([[self.data_order[-1], self.data_order[-1]]]) self.current_idx = len(self.data_order) - 1 self.lastpoint_idx = self.data_order[-1] diff --git a/src/diffpy/srmise/tests/test_dataclusters.py b/src/diffpy/srmise/tests/test_dataclusters.py index 0ea6b42..c9fa8a7 100644 --- a/src/diffpy/srmise/tests/test_dataclusters.py +++ b/src/diffpy/srmise/tests/test_dataclusters.py @@ -1,22 +1,14 @@ from copy import copy import numpy as np +import pytest from diffpy.srmise.dataclusters import DataClusters -def test_clear(): - # Initialize DataClusters with input parameters - actual = DataClusters(x=np.array([1, 2, 3]), y=np.array([3, 2, 1]), res=4) - expected = DataClusters(x=np.array([]), y=np.array([]), res=0) - # Perform the clear operation - actual.clear() - assert actual == expected - - def test___eq__(): - actual = DataClusters(np.array([1, 2, 3]), np.array([3, 2, 1]), 0) - expected = DataClusters(np.array([1, 2, 3]), np.array([3, 2, 1]), 0) + actual = DataClusters(np.array([1, 2, 3]), np.array([3, 2, 1]), 1) + expected = DataClusters(np.array([1, 2, 3]), np.array([3, 2, 1]), 1) assert expected == actual attributes = vars(actual) for attr_key, attr_val in attributes.items(): @@ -32,3 +24,66 @@ def test___eq__(): print(f"not-equal test failed on {attr_key}") assert not expected == actual attributes.update({attr_key: reset}) + + +@pytest.mark.parametrize( + "inputs, expected", + [ + ( + { + "x": np.array([1, 2, 3]), + "y": np.array([3, 2, 1]), + "res": 4, + }, + { + "x": np.array([1, 2, 3]), + "y": np.array([3, 2, 1]), + "res": 4, + "data_order": [2, 1, 0], + "clusters": np.array([[0, 0]]), + "current_idx": 2, + "lastpoint_idx": 0, + "INIT": 0, + "READY": 1, + "CLUSTERING": 2, + "DONE": 3, + "lastcluster_idx": None, + "status": 1, + }, + ), + ], +) +def test_DataClusters_constructor(inputs, expected): + actual = DataClusters(x=inputs["x"], y=inputs["y"], res=inputs["res"]) + actual_attributes = vars(actual) + for attr_key, actual_attr_val in actual_attributes.items(): + if isinstance(actual_attr_val, np.ndarray): + assert np.array_equal(actual_attr_val, expected[attr_key]) + else: + assert actual_attr_val == expected[attr_key] + + +@pytest.mark.parametrize( + "inputs, msg", + [ + ( + { + "x": np.array([1]), + "y": np.array([3, 2]), + "res": 4, + }, + "Sequences x and y must have the same length.", + ), + ( + { + "x": np.array([1]), + "y": np.array([3]), + "res": -1, + }, + "Value of resolution parameter is less than zero. Please rerun specifying a non-negative res", + ), + ], +) +def test_set_data_order_bad(inputs, msg): + with pytest.raises(ValueError, match=msg): + DataClusters(x=inputs["x"], y=inputs["y"], res=inputs["res"]) From 48c5d64d30a7790c3ca4ef83c6e2c7a13b107cf6 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:26:09 +0800 Subject: [PATCH 34/65] fix arbitrary.py to numpydoc format (#68) * fix arbitrary.py to numpydoc format * pre-commit fix * change start sentence to 'The' --- src/diffpy/srmise/baselines/arbitrary.py | 106 ++++++++++++++++------- 1 file changed, 75 insertions(+), 31 deletions(-) diff --git a/src/diffpy/srmise/baselines/arbitrary.py b/src/diffpy/srmise/baselines/arbitrary.py index 6e54d73..80c1055 100644 --- a/src/diffpy/srmise/baselines/arbitrary.py +++ b/src/diffpy/srmise/baselines/arbitrary.py @@ -43,15 +43,18 @@ def __init__(self, npars, valuef, jacobianf=None, estimatef=None, Cache=None): """Initialize an arbitrary baseline. Parameters - npars: Number of parameters which define the function - valuef: Function which calculates the value of the baseline - at x. - jacobianf: (None) Function which calculates the Jacobian of the + ---------- + npars : int + The number of parameters which define the function + valuef : array-like or int + The function which calculates the value of the baseline at x. + jacobianf : array-like or None + The function which calculates the Jacobian of the baseline function with respect to free pars. - estimatef: (None) Function which estimates function parameters given the - data x and y. - Cache: (None) A class (not instance) which implements caching of - BaseFunction evaluations. + estimatef : array-like or None + The function which estimates function parameters given the data x and y. + Cache : None or callable + The class (not instance) which implements caching of BaseFunction evaluations. """ # Guarantee valid number of parameters try: @@ -103,11 +106,17 @@ def estimate_parameters(self, r, y): """Estimate parameters for data baseline. Parameters - r: (Numpy array) Data along r from which to estimate - y: (Numpy array) Data along y from which to estimate + ---------- + r : array-like + The data along r from which to estimate + y : array-like + The data along y from which to estimate - Returns Numpy array of parameters in the default internal format. - Raises NotImplementedError if no estimation routine is defined, and + Returns + ------- + The numpy array of parameters in the default internal format. + + we raise NotImplementedError if no estimation routine is defined, and SrMiseEstimationError if parameters cannot be estimated for any other.""" if self.estimatef is None: emsg = "No estimation routine provided to Arbitrary." @@ -124,13 +133,23 @@ def _jacobianraw(self, pars, r, free): """Return the Jacobian of a polynomial. Parameters - pars: Sequence of parameters - pars[0] = a_0 - pars[1] = a_1 - ... - r: sequence or scalar over which pars is evaluated - free: sequence of booleans which determines which derivatives are - needed. True for evaluation, False for no evaluation.""" + ---------- + pars : array-like + The sequence of parameters + pars[0] = a_0 + pars[1] = a_1 + ... + r : array-like or int + The sequence or scalar over which pars is evaluated + free : array-like of bools + The sequence of booleans which determines which derivatives are needed. + True for evaluation, False for no evaluation. + + Returns + ------- + numpy.ndarray + The Jacobian of polynomial with respect to free pars. + """ nfree = None if self.jacobianf is None: nfree = (pars is True).sum() @@ -159,12 +178,23 @@ def _transform_parametersraw(self, pars, in_format, out_format): """Convert parameter values from in_format to out_format. Parameters - pars: Sequence of parameters - in_format: A format defined for this class - out_format: A format defined for this class - - Defined Formats - internal: [a_0, a_1, ...]""" + ---------- + pars : array-like + The sequence of parameters + in_format : internal + The format defined for this class + out_format: internal + The format defined for this class + + Defined Format + -------------- + internal: [a_0, a_1, ...] + + Returns + ------- + numpy.ndarray + The standard output of transformed parameters + """ temp = np.array(pars) # Convert to intermediate format "internal" @@ -181,14 +211,28 @@ def _transform_parametersraw(self, pars, in_format, out_format): return temp def _valueraw(self, pars, r): - """Return value of polynomial for the given parameters and r values. + """Compute the value of the polynomial given a set of parameters and evaluation points. + + This method ensures that the input parameters conform to the expected count + and then delegates the computation to an internal method `valuef`. Parameters - pars: Sequence of parameters - pars[0] = a_0 - pars[1] = a_1 - ... - r: sequence or scalar over which pars is evaluated""" + ---------- + pars : array_like + The sequence of coefficients for the polynomial where each element corresponds to: + - pars[0] = a_0, the constant term + - pars[1] = a_1, the coefficient of the first degree term, and so on. + The length of `pars` must match the expected number of parameters defined in the class. + + r : array_like or float + The sequence of points or a single point at which the polynomial is to be evaluated. + If a scalar is provided, it will be treated as a single point for evaluation. + + Returns + ------- + ndarray or float + The computed values of the polynomial for each point in `r`. + """ if len(pars) != self.npars: emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) From 20b4d01bfb43896edf60e80719597f7be4f116c5 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Thu, 15 Aug 2024 00:57:35 +0800 Subject: [PATCH 35/65] print things correctly (#71) * print things correctly * change to f string * reduce print to one line --- src/diffpy/srmise/baselines/nanospherical.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/diffpy/srmise/baselines/nanospherical.py b/src/diffpy/srmise/baselines/nanospherical.py index b45113b..8c5ae12 100644 --- a/src/diffpy/srmise/baselines/nanospherical.py +++ b/src/diffpy/srmise/baselines/nanospherical.py @@ -88,7 +88,7 @@ def _jacobianraw(self, pars, r, free): emsg = "Argument free must have " + str(self.npars) + " elements." raise ValueError(emsg) jacobian = [None for p in range(self.npars)] - if (free is False).sum() == self.npars: + if np.sum(np.logical_not(free)) == self.npars: return jacobian if np.isscalar(r): @@ -247,7 +247,6 @@ def getmodule(self): for tup in zip(r, val, *outjac): for t in tup: if t is None: - print("%s" % None).ljust(10), + print(f"{None}".ljust(10)) else: - print("% .3g" % t).ljust(10), - print + print(f"{t:.3g}".ljust(10)) From 6e62077a6bd75ec3f27665bf49d6ccef45a76252 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Thu, 15 Aug 2024 19:57:06 +0800 Subject: [PATCH 36/65] change createpeak to actualize function (#72) --- src/diffpy/srmise/baselines/base.py | 2 +- src/diffpy/srmise/peaks/gaussian.py | 4 ++-- src/diffpy/srmise/peaks/gaussianoverr.py | 4 ++-- src/diffpy/srmise/peaks/terminationripples.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/diffpy/srmise/baselines/base.py b/src/diffpy/srmise/baselines/base.py index 4d1d42f..23e242d 100644 --- a/src/diffpy/srmise/baselines/base.py +++ b/src/diffpy/srmise/baselines/base.py @@ -181,5 +181,5 @@ def factory(baselinestr, ownerlist): evaluator = AICc() pars = [[3, 0.2, 10], [3.5, 0.2, 10]] - ideal_peaks = Peaks([pf.createpeak(p, "pwa") for p in pars]) + ideal_peaks = Peaks([pf.actualize(p, "pwa") for p in pars]) y = ideal_peaks.value(r) + 0.1 * randn(len(r)) diff --git a/src/diffpy/srmise/peaks/gaussian.py b/src/diffpy/srmise/peaks/gaussian.py index 126e3b6..433af3a 100644 --- a/src/diffpy/srmise/peaks/gaussian.py +++ b/src/diffpy/srmise/peaks/gaussian.py @@ -354,11 +354,11 @@ def max(self, pars): evaluator = AICc() pars = [[3, 0.2, 10], [3.5, 0.2, 10]] - ideal_peaks = Peaks([pf.createpeak(p, "pwa") for p in pars]) + ideal_peaks = Peaks([pf.actualize(p, "pwa") for p in pars]) y = ideal_peaks.value(r) + 0.1 * randn(len(r)) guesspars = [[2.7, 0.15, 5], [3.7, 0.3, 5]] - guess_peaks = Peaks([pf.createpeak(p, "pwa") for p in guesspars]) + guess_peaks = Peaks([pf.actualize(p, "pwa") for p in guesspars]) cluster = ModelCluster(guess_peaks, r, y, err, None, AICc, [pf]) qual1 = cluster.quality() diff --git a/src/diffpy/srmise/peaks/gaussianoverr.py b/src/diffpy/srmise/peaks/gaussianoverr.py index 9252135..8b125a9 100644 --- a/src/diffpy/srmise/peaks/gaussianoverr.py +++ b/src/diffpy/srmise/peaks/gaussianoverr.py @@ -427,11 +427,11 @@ def max(self, pars): evaluator = AICc() pars = [[3, 0.2, 10], [3.5, 0.2, 10]] - ideal_peaks = Peaks([pf.createpeak(p, "pwa") for p in pars]) + ideal_peaks = Peaks([pf.actualize(p, "pwa") for p in pars]) y = ideal_peaks.value(r) + 0.1 * randn(len(r)) guesspars = [[2.7, 0.15, 5], [3.7, 0.3, 5]] - guess_peaks = Peaks([pf.createpeak(p, "pwa") for p in guesspars]) + guess_peaks = Peaks([pf.actualize(p, "pwa") for p in guesspars]) cluster = ModelCluster(guess_peaks, r, y, err, None, AICc, [pf]) qual1 = cluster.quality() diff --git a/src/diffpy/srmise/peaks/terminationripples.py b/src/diffpy/srmise/peaks/terminationripples.py index 1ac5d81..4a66b6e 100644 --- a/src/diffpy/srmise/peaks/terminationripples.py +++ b/src/diffpy/srmise/peaks/terminationripples.py @@ -290,13 +290,13 @@ def extend_grid(self, r, dr): evaluator = AICc() pars = [[3, 0.2, 10], [3.5, 0.2, 10]] - ideal_peaks = Peaks([pf1.createpeak(p, "pwa") for p in pars]) - ripple_peaks = Peaks([pf2.createpeak(p, "pwa") for p in pars]) + ideal_peaks = Peaks([pf1.actualize(p, "pwa") for p in pars]) + ripple_peaks = Peaks([pf2.actualize(p, "pwa") for p in pars]) y_ideal = ideal_peaks.value(r) y_ripple = ripple_peaks.value(r) + 0.1 * randn(len(r)) guesspars = [[2.7, 0.15, 5], [3.7, 0.3, 5]] - guess_peaks = Peaks([pf2.createpeak(p, "pwa") for p in guesspars]) + guess_peaks = Peaks([pf2.actualize(p, "pwa") for p in guesspars]) cluster = ModelCluster(guess_peaks, r, y_ripple, err, None, AICc, [pf2]) qual1 = cluster.quality() From 578a972a3ada4206dcc92680cf9598ba7f6a4557 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 16 Aug 2024 21:27:09 +0800 Subject: [PATCH 37/65] fix import and counting to make it work (#74) --- src/diffpy/srmise/modelcluster.py | 4 ++-- src/diffpy/srmise/modelparts.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/srmise/modelcluster.py b/src/diffpy/srmise/modelcluster.py index cc509c4..668612e 100644 --- a/src/diffpy/srmise/modelcluster.py +++ b/src/diffpy/srmise/modelcluster.py @@ -1408,8 +1408,8 @@ def prune(self): if __name__ == "__main__": from numpy.random import randn - from diffpy.srmise.modelevaluators import AICc - from diffpy.srmise.peaks import GaussianOverR + from diffpy.srmise.modelevaluators.aicc import AICc + from diffpy.srmise.peaks.gaussianoverr import GaussianOverR pf = GaussianOverR(0.7) res = 0.01 diff --git a/src/diffpy/srmise/modelparts.py b/src/diffpy/srmise/modelparts.py index 1c6f2cf..f8f0fbc 100644 --- a/src/diffpy/srmise/modelparts.py +++ b/src/diffpy/srmise/modelparts.py @@ -558,7 +558,7 @@ def npars(self, count_fixed=True): if count_fixed: return self._owner.npars else: - return (self.free is True).sum() + return np.sum(self.free) def __str__(self): """Return string representation of ModelPart parameters.""" From 3773bcfe843d66ea5f14cd10595c7318722f194b Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 16 Aug 2024 23:07:52 +0800 Subject: [PATCH 38/65] refactor makeclusters to make it work (#73) --- src/diffpy/srmise/dataclusters.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/diffpy/srmise/dataclusters.py b/src/diffpy/srmise/dataclusters.py index b881f17..3d68004 100644 --- a/src/diffpy/srmise/dataclusters.py +++ b/src/diffpy/srmise/dataclusters.py @@ -172,7 +172,7 @@ def _setdata(self, x, y, res): self.lastcluster_idx = None return - def next(self): + def cluster_next_point(self): """Cluster point with largest y-coordinate left, returning self. next() always adds at least one additional point to the existing @@ -217,14 +217,13 @@ def next(self): else: # insert right of nearest cluster self.lastcluster_idx = nearest_cluster[0] + 1 - self.clusters = np.insert(self.clusters, self.lastcluster_idx, [test_idx, test_idx], 0) + self.clusters = np.insert(self.clusters, int(self.lastcluster_idx), [test_idx, test_idx], 0) return self def makeclusters(self): """Cluster all remaining data.""" - for i in self: - pass - return + while self.current_idx > 0: + self.cluster_next_point() def find_nearest_cluster2(self, x): """Return [cluster index, distance] for cluster nearest to x. @@ -286,8 +285,8 @@ def find_nearest_cluster(self, idx): # Calculate which of the two nearest clusters is closer distances = np.array( [ - self.x[idx] - self.x[self.clusters[near_idx - 1, 1]], - self.x[idx] - self.x[self.clusters[near_idx, 0]], + self.x[idx] - self.x[self.clusters[int(near_idx) - 1, 1]], + self.x[idx] - self.x[self.clusters[int(near_idx), 0]], ] ) if distances[0] < np.abs(distances[1]): From abfdb5b1bf824b584af59fd31aee5ebbb9015198 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 16 Aug 2024 23:15:08 +0800 Subject: [PATCH 39/65] deprecation remove (#78) * deprecation remove * fix to right behavior --- src/diffpy/srmise/basefunction.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/diffpy/srmise/basefunction.py b/src/diffpy/srmise/basefunction.py index ebeee5e..b2acc2c 100644 --- a/src/diffpy/srmise/basefunction.py +++ b/src/diffpy/srmise/basefunction.py @@ -18,7 +18,6 @@ import sys import numpy as np -from numpy.compat import unicode from diffpy.srmise.srmiseerrors import SrMiseDataFormatError @@ -100,7 +99,7 @@ def __init__( # arbitrary types, parameters are indexed by these keys as well as # integer indices. Restricting keys to strings keeps things sane. for p in self.parameterdict.keys(): - if type(p) not in (str, unicode): + if not isinstance(p, str): emsg = "Argument parameterdict's keys must be strings." raise ValueError(emsg) vals = self.parameterdict.values() From e58e0c7019451af4cf5c2cbac8ab8c955cdc091d Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Sat, 17 Aug 2024 02:23:42 +0800 Subject: [PATCH 40/65] Revert "refactor makeclusters to make it work (#73)" (#79) This reverts commit 3773bcfe843d66ea5f14cd10595c7318722f194b. --- src/diffpy/srmise/dataclusters.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/diffpy/srmise/dataclusters.py b/src/diffpy/srmise/dataclusters.py index 3d68004..b881f17 100644 --- a/src/diffpy/srmise/dataclusters.py +++ b/src/diffpy/srmise/dataclusters.py @@ -172,7 +172,7 @@ def _setdata(self, x, y, res): self.lastcluster_idx = None return - def cluster_next_point(self): + def next(self): """Cluster point with largest y-coordinate left, returning self. next() always adds at least one additional point to the existing @@ -217,13 +217,14 @@ def cluster_next_point(self): else: # insert right of nearest cluster self.lastcluster_idx = nearest_cluster[0] + 1 - self.clusters = np.insert(self.clusters, int(self.lastcluster_idx), [test_idx, test_idx], 0) + self.clusters = np.insert(self.clusters, self.lastcluster_idx, [test_idx, test_idx], 0) return self def makeclusters(self): """Cluster all remaining data.""" - while self.current_idx > 0: - self.cluster_next_point() + for i in self: + pass + return def find_nearest_cluster2(self, x): """Return [cluster index, distance] for cluster nearest to x. @@ -285,8 +286,8 @@ def find_nearest_cluster(self, idx): # Calculate which of the two nearest clusters is closer distances = np.array( [ - self.x[idx] - self.x[self.clusters[int(near_idx) - 1, 1]], - self.x[idx] - self.x[self.clusters[int(near_idx), 0]], + self.x[idx] - self.x[self.clusters[near_idx - 1, 1]], + self.x[idx] - self.x[self.clusters[near_idx, 0]], ] ) if distances[0] < np.abs(distances[1]): From ee41be5275994ebd1e76229511f8cd11d25e0290 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Sat, 17 Aug 2024 19:03:29 +0800 Subject: [PATCH 41/65] try out py2 before py3 refactor to make sure correct workflow (#75) --- src/diffpy/srmise/dataclusters.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/diffpy/srmise/dataclusters.py b/src/diffpy/srmise/dataclusters.py index b881f17..af0c4e5 100644 --- a/src/diffpy/srmise/dataclusters.py +++ b/src/diffpy/srmise/dataclusters.py @@ -172,7 +172,7 @@ def _setdata(self, x, y, res): self.lastcluster_idx = None return - def next(self): + def __next__(self): """Cluster point with largest y-coordinate left, returning self. next() always adds at least one additional point to the existing @@ -217,7 +217,7 @@ def next(self): else: # insert right of nearest cluster self.lastcluster_idx = nearest_cluster[0] + 1 - self.clusters = np.insert(self.clusters, self.lastcluster_idx, [test_idx, test_idx], 0) + self.clusters = np.insert(self.clusters, int(self.lastcluster_idx), [test_idx, test_idx], 0) return self def makeclusters(self): @@ -286,8 +286,8 @@ def find_nearest_cluster(self, idx): # Calculate which of the two nearest clusters is closer distances = np.array( [ - self.x[idx] - self.x[self.clusters[near_idx - 1, 1]], - self.x[idx] - self.x[self.clusters[near_idx, 0]], + self.x[idx] - self.x[self.clusters[int(near_idx) - 1, 1]], + self.x[idx] - self.x[self.clusters[int(near_idx), 0]], ] ) if distances[0] < np.abs(distances[1]): From abe0c2ac435d1cf1df9d24f9ab0c0b380f58cfd3 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Mon, 19 Aug 2024 22:11:04 +0800 Subject: [PATCH 42/65] fix false counting and numpy to int (#80) * fix false counting and numpy to int * [pre-commit.ci] auto fixes from pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/diffpy/srmise/baselines/polynomial.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/srmise/baselines/polynomial.py b/src/diffpy/srmise/baselines/polynomial.py index 70498be..bae1b6f 100644 --- a/src/diffpy/srmise/baselines/polynomial.py +++ b/src/diffpy/srmise/baselines/polynomial.py @@ -90,7 +90,7 @@ def estimate_parameters(self, r, y): # TODO: Make this more sophisticated. try: cut = np.max([len(y) / 10, 1]) - cut_idx = y.argsort()[:cut] + cut_idx = y.argsort()[: int(cut)] import numpy.linalg as la @@ -121,7 +121,7 @@ def _jacobianraw(self, pars, r, free): emsg = "Argument free must have " + str(self.npars) + " elements." raise ValueError(emsg) jacobian = [None for p in range(self.npars)] - if (free is False).sum() == self.npars: + if np.sum(np.logical_not(free)) == self.npars: return jacobian # The partial derivative with respect to the nth coefficient of a From 8716a9da4e2323f5428f13acd88d416d5a120e90 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Mon, 19 Aug 2024 22:19:01 +0800 Subject: [PATCH 43/65] numpydoc edition (#81) * change peakextraction function to numpydoc * pre-commit run * remove unused import --- src/diffpy/srmise/peakextraction.py | 78 +++++++++++++++++++---------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/src/diffpy/srmise/peakextraction.py b/src/diffpy/srmise/peakextraction.py index c62102b..8d019b0 100644 --- a/src/diffpy/srmise/peakextraction.py +++ b/src/diffpy/srmise/peakextraction.py @@ -33,29 +33,44 @@ class PeakExtraction(object): """Class for peak extraction. - Data members - x: x coordinates of the data - y: y coordinates of the data - dx: uncertainties in the x coordinates (not used) - dy: uncertainties in the y coordinates - effective_dy: uncertainties in the y coordinates actually used during extraction - rng: [xmin, xmax] Range of x coordinates over which to extract peaks - pf: Sequence of peak functions that can be extracted - initial_peaks: Peaks present at start of extraction - baseline: Baseline for data - cres: Resolution of clustering - error_method: ErrorEvaluator class used to compare models + Parameters + ---------- + x : array-like + The x coordinates of the data + y : array-like + The y coordinates of the data + dx : array-like + The uncertainties in the x coordinates (not used) + dy : array-like + The uncertainties in the y coordinates + effective_dy : array-like + The uncertainties in the y coordinates actually used during extraction + rng : list + The [xmin, xmax] Range of x coordinates over which to extract peaks + pf : array-like + The sequence of peak functions that can be extracted + initial_peaks: Peaks object + The peaks present at start of extraction + baseline : Baseline object + The baseline for data + cres : float + The resolution of clustering + error_method : ErrorEvaluator class + The Evaluation class used to compare models Calculated members - extracted: ModelCluster after extraction - extraction_type: Type of extraction + ------------------ + extracted : ModelCluster object + The ModelCluster object after extraction + extraction_type : Type of extraction """ def __init__(self, newvars=[]): """Initialize PeakExtraction object. Parameters - newvars: Sequence of strings that represent additional extraction parameters.""" + newvars : array-like + Sequence of strings that represent additional extraction parameters.""" self.clear() self.extractvars = dict.fromkeys( ( @@ -77,7 +92,9 @@ def __init__(self, newvars=[]): return def clear(self): - """Clear all members.""" + """Clear all members. + + The purpose of the method is to ensure the object is in initialized state.""" self.x = None self.y = None self.dx = None @@ -119,17 +136,24 @@ def setdata(self, x, y, dx=None, dy=None): def setvars(self, quiet=False, **kwds): """Set one or more extraction variables. - Variables - quiet: [False] Log changes quietly. - - Keywords - cres: The clustering resolution, must be > 0. - effective_dy: The uncertainties actually used during extraction - pf: Sequence of PeakFunctionBase subclass instances. - baseline: Baseline instance or BaselineFunction instance (use built-in estimation) - error_method: ErrorEvaluator subclass instance used to compare models (default AIC) - initial_peaks: Peaks instance. These peaks are present at the start of extraction. - rng: Sequence specifying the least and greatest x-values over which to extract peaks. + Parameters + ---------- + quiet : bool + The log changes quietly. Default is False. + cres : float + The clustering resolution, must be > 0. + effective_dy : array-like + The uncertainties actually used during extraction + pf : list + The sequence of PeakFunctionBase subclass instances. + baseline : Baseline instance or BaselineFunction instance + The Baseline instance or BaselineFunction instance that use built-in estimation + error_method : ErrorEvaluator subclass instance + The ErrorEvaluator subclass instance used to compare models. Default is AIC. + initial_peaks : Peaks instance + These peaks are present at the start of extraction. + rng : array-like + The sequence specifying the least and greatest x-values over which to extract peaks. """ for k, v in kwds.items(): if k in self.extractvars: From e87962f6d51bde77183c6eb29559af2baa689339 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Mon, 19 Aug 2024 22:20:58 +0800 Subject: [PATCH 44/65] numpydoc build (#82) --- src/diffpy/srmise/peakextraction.py | 89 ++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/src/diffpy/srmise/peakextraction.py b/src/diffpy/srmise/peakextraction.py index 8d019b0..d323ece 100644 --- a/src/diffpy/srmise/peakextraction.py +++ b/src/diffpy/srmise/peakextraction.py @@ -171,24 +171,27 @@ def defaultvars(self, *args): """Set unset(=None) extraction variables to default values. Certain variables may be partially set for convenience, and are transformed - appropriately. See 'Default values' below. + appropriately. See 'Default values assigned' below. Parameters - Any number of strings corresponding to extraction variables. These - variables are reset to their default values even if another value - already exists. - - Default values: - cres -> 4 times the average spacing between elements in x - effective_dy -> The data dy if all elements > 0, otherwise 5% of max(y)-min(y). - If effective_dy is a positive scalar, then an array of that - value of appropriate length. - pf -> [GaussianOverR(maxwidth=x[-1]-x[0])] - baseline -> Flat baseline located at y=0. - error_method -> AIC (Aikake Information Criterion) - initial_peaks -> No initial peaks - rng -> [x[0], x[-1]]. Partially set ranges like [None, 100.] replace None with - the appropriate limit in the data. + ---------- + *args : str + The variable argument list where each string corresponds to an extraction + variable name. + + Default values assigned: + - `cres` : 4 times the average spacing between elements in `x`. + - `effective_dy` : If all elements in `y` are positive, it's set to the data `dy`; + otherwise, it's 5% of the range (`max(y)` - `min(y)`). If `effective_dy` + is a positive scalar, an array of that value with a length matching `y` is used. + - `pf` : A list containing a single Gaussian overlap function with the maximum width + spanning the entire `x` range (`x[-1] - x[0]`). + - `baseline` : A flat baseline at `y=0`, indicating no background signal. + - `error_method` : Uses the AIC (Akaike Information Criterion) for evaluating model fits. + - `initial_peaks` : Assumes no initial peak guesses, implying peaks will be detected from scratch. + - `rng` : The default range is set to span the entire `x` dataset, i.e., `[x[0], x[-1]]`. + If a range is partially defined, e.g., `[None, 100.]`, the `None` value is replaced + with the respective boundary of the `x` data. Note that the default values of very important parameters like the uncertainty and clustering resolution are crude guesses at best. @@ -264,7 +267,13 @@ def plot(self, **kwds): Uses initial peaks instead if no peaks have been extracted. - Takes same keywords as ModelCluster.plottable()""" + Takes same keywords as ModelCluster.plottable() + + Parameters + ---------- + **kwds :args + The keyword arguments to pass to matplotlib. + """ plt.clf() if self.extracted is not None: plt.plot(*self.extracted.plottable(kwds)) @@ -292,9 +301,14 @@ def plot(self, **kwds): def read(self, filename): """load PeakExtraction object from file - filename -- file from which to read + Parameters + ---------- + filename : str + The file from which to read - returns self + Returns + ------- + self """ try: self.readstr(open(filename, "rb").read()) @@ -312,7 +326,10 @@ def readstr(self, datastring): """Initialize members from string. Parameters - datastring: The raw data to read""" + ---------- + datastring : array-like + The raw data to read + """ from diffpy.srmise.basefunction import BaseFunction self.clear() @@ -525,7 +542,10 @@ def write(self, filename): """Write string representation of PeakExtraction instance to file. Parameters - filename: the name of the file to write""" + ---------- + filename : str + The name of the file to write + """ bytes = self.writestr() f = open(filename, "w") f.write(bytes) @@ -533,7 +553,12 @@ def write(self, filename): return def writestr(self): - """Return string representation of PeakExtraction object.""" + """Return string representation of PeakExtraction object. + + Returns + ------- + The str representation of PeakExtraction object + """ import time from getpass import getuser @@ -702,11 +727,16 @@ def estimate_peak(self, x, add=True): Peaks already extracted, if any, are taken into account. If none exist, use those specified by initial_peaks instead. - Parameters: - x: Coordinate of the point of interest - add: (True) Automatically add peak to extracted peaks or initial_peaks. - - Return a Peak object, or None if estimation fails. + Parameters + ---------- + x : array-like + The oordinate of the point of interest + add : bool + Automatically add peak to extracted peaks or initial_peaks. Default is True. + + Returns + ------- + The Peak object, or None if estimation fails. """ # Make sure all required extraction variables have some value self.defaultvars() @@ -758,7 +788,10 @@ def add_peaks(self, peaks): """Add peaks to extracted peaks, or initial_peaks if no extracted peaks exist. Parameters - peaks: A Peaks instance""" + ---------- + peaks: Peaks object + The peaks instance + """ if self.extracted is not None: self.extracted.replacepeaks(peaks) else: From 6458a3ea464c62bcbf6508d66fbf341e549e4ebf Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Mon, 19 Aug 2024 22:30:37 +0800 Subject: [PATCH 45/65] numpydoc build on peakstability (#83) --- src/diffpy/srmise/peakstability.py | 35 ++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/diffpy/srmise/peakstability.py b/src/diffpy/srmise/peakstability.py index 83d2897..28c9517 100644 --- a/src/diffpy/srmise/peakstability.py +++ b/src/diffpy/srmise/peakstability.py @@ -125,7 +125,17 @@ def plot(self, **kwds): ) def setcurrent(self, idx): - """Make the idxth model the active one.""" + """Make the idxth model the active one. + + Parameters + ---------- + idx : int + The index of the model to be tested. + + Returns + ------- + None + """ self.current = idx if idx is not None: result = self.results[idx] @@ -140,11 +150,15 @@ def setcurrent(self, idx): def animate(self, results=None, step=False, **kwds): """Show animation of extracted peaks from first to last. - Parameters: - step - Require keypress to show next plot - results - The indices of results to show + Keywords passed to pyplot.plot() - Keywords passed to pyplot.plot()""" + Parameters + ---------- + step : bool + Require keypress to show next plot + results array-like + The indices of results to show + """ if results is None: results = range(len(self.results)) @@ -165,9 +179,16 @@ def animate(self, results=None, step=False, **kwds): self.setcurrent(oldcurrent) def run(self, err, savecovs=False): - """err is sequence of uncertainties to run at. + """Running the uncertainty for the results. + + Parameters + ---------- + err : array-like + The sequence of uncertainties to run at. + savecovs : bool + boolean to determine to save covariance matrix. Default is False. + If savecovs is True, return the covariance matrix for each final fit.""" - If savecovs is True, return the covariance matrix for each final fit.""" self.results = [] covs = [] for i, e in enumerate(err): From 35a2ef4e28e7d5e98b25426d7e70bdfaeccd6b13 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Tue, 20 Aug 2024 20:50:43 +0800 Subject: [PATCH 46/65] numpydoc build for ModelCluster (#85) --- src/diffpy/srmise/modelcluster.py | 99 +++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 26 deletions(-) diff --git a/src/diffpy/srmise/modelcluster.py b/src/diffpy/srmise/modelcluster.py index 668612e..26816e6 100644 --- a/src/diffpy/srmise/modelcluster.py +++ b/src/diffpy/srmise/modelcluster.py @@ -369,18 +369,26 @@ class ModelCluster(object): def __init__(self, model, *args, **kwds): """Intialize explicitly, or from existing ModelCluster. - Parameters [Explicit creation] - model - Peaks object, or None->empty model - baseline - Baseline object, or None->0 - r_data - Numpy array of r coordinates - y_data - Numpy array of y values - y_error - Numpy array of uncertainties in y - cluster_slice - slice object defining the range of cluster. None->all data - error_method - an ErrorEvaluator subclass - peak_funcs - a sequence of PeakFunction instances - - Parameters [Creation from existing ModelCluster] - model - ModelCluster instance, or sequence of ModelCluster instances + Parameters + ---------- + model : (lists of) ModelCluster instance + The ModelCluster instances to be clustered. + If it is None, then a ModelCluster object is created. + baseline : Baseline object + The Baseline object, if it is None, set to 0. + r_data : array-like + The numpy array of r coordinates + y_data : array-like + The numpy array of y values + y_error : array-like + The numpy array of uncertainties in y + cluster_slice : slice object + The slice object defining the range of cluster. If the input is None, + then it will take the entire range. + error_method : ErrorEvaluator subclass + The error evaluator to use to calculate quality of model to data. + peak_funcs : a sequence of PeakFunction instances + The peak instances to use to calculate the cluster of data. """ self.last_fit_size = 0 self.slice = None @@ -437,7 +445,13 @@ def addexternalpeaks(self, peaks): """Add peaks (and their value) to self. Parameters - peaks - A Peaks object + ---------- + peaks : A Peaks object + The peaks to be added + + Returns + ------- + None """ self.replacepeaks(peaks) self.y_data += peaks.value(self.r_data) @@ -447,8 +461,9 @@ def writestr(self, **kwds): """Return partial string representation. Keywords - pfbaselist - List of peak function bases. Otherwise define list from self. - blfbaselist - List of baseline function bases. Otherwise define list from self. + -------- + pfbaselist - List of peak function bases. Otherwise, define list from self. + blfbaselist - List of baseline function bases.Otherwise, define list from self. """ from diffpy.srmise.basefunction import BaseFunction @@ -524,8 +539,9 @@ def factory(mcstr, **kwds): """Create ModelCluster from string. Keywords - pfbaselist - List of peak function bases - blfbaselist - List of baseline function bases + -------- + pfbaselist : List of peak function bases + blfbaselist : List of baseline function bases """ from diffpy.srmise.basefunction import BaseFunction @@ -696,8 +712,16 @@ def join_adjacent(m1, m2): unchanged. Parameters - m1 - A ModelCluster - m2 - A ModelCluster + ---------- + m1 : ModelCluster instance + The first ModelCluster instance. + m2 : ModelCluster instance + The second ModelCluster instance. + + Returns + ------- + ModelCluster instance + The new ModelCluster instance between m1 and m2. """ # Check for members that must be shared. if not (m1.r_data is m2.r_data): @@ -778,7 +802,17 @@ def join_adjacent(m1, m2): ) def change_slice(self, new_slice): - """Change the slice which represents the extent of a cluster.""" + """Change the slice which represents the extent of a cluster. + + Parameters + ---------- + new_slice : slice object + The new slice to change. + + Returns + ------- + None + """ old_slice = self.slice self.slice = new_slice self.r_cluster = self.r_data[new_slice] @@ -815,10 +849,16 @@ def npars(self, count_baseline=True, count_fixed=True): """Return number of parameters in model and baseline. Parameters - count_baseline - [True] Boolean determines whether or not to count - parameters from baseline. - count_fixed - [True] Boolean determines whether or not to include - non-free parameters. + ---------- + count_baseline : bool + The boolean determines whether to count parameters from baseline. Default is True. + count_fixed : bool + The boolean determines whether to include non-free parameters. Default is True. + + Returns + ------- + n : int + The number of parameters in model and baseline. """ n = self.model.npars(count_fixed=count_fixed) if count_baseline and self.baseline is not None: @@ -829,8 +869,15 @@ def replacepeaks(self, newpeaks, delslice=slice(0, 0)): """Replace peaks given by delslice by those in newpeaks. Parameters - newpeaks - Add each Peak in this Peaks to cluster. - delslice - Existing peaks given by slice object are deleted. + ---------- + newpeaks : Peak instance + The peak that id added to each existing peak to cluster. + delslice : Peak instance + The existing peaks given by slice object are deleted. + + Returns + ------- + None """ for p in self.model[delslice]: if not p.removable: From df43be66e408177c9a635d071c2cb267d68cc23a Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 21 Aug 2024 02:59:38 +0800 Subject: [PATCH 47/65] numpydoc build for multimodelselection.py (#87) --- src/diffpy/srmise/multimodelselection.py | 273 +++++++++++++++++++---- 1 file changed, 227 insertions(+), 46 deletions(-) diff --git a/src/diffpy/srmise/multimodelselection.py b/src/diffpy/srmise/multimodelselection.py index a10dba2..c4c7b74 100644 --- a/src/diffpy/srmise/multimodelselection.py +++ b/src/diffpy/srmise/multimodelselection.py @@ -62,12 +62,20 @@ def __init__(self): def makeaics(self, dgs, dr, filename=None): """Test quality of each model for all possible uncertainties. - Parameters: - dgs - Array of uncertainties over which to test each model. - dr - The sampling rate to use. This determines the actual data to use - for testing, since sometimes the actual result is different than the - nominal value. - filename - Optional file to save pickled results + Parameters + ---------- + dgs : array-like + The array of uncertainties over which to test each model. + dr : float + The sampling rate to use. This determines the actual data to use + for testing, since sometimes the actual result is different than the + nominal value. + filename : str + Optional file to save pickled results + + Returns + ------- + None """ aics_out = {} # Version of self.aics that holds only the statistic, not the AIC object. self.dgs = np.array(dgs) @@ -122,7 +130,17 @@ def makeaics(self, dgs, dr, filename=None): return def loadaics(self, filename): - """Load file containing results of the testall method.""" + """Load file containing results of the testall method. + + Parameters + ---------- + filename : str + Filename to load. + + Returns + ------- + None + """ try: import cPickle as pickle except ImportError: @@ -153,6 +171,7 @@ def loadaics(self, filename): return def makeaicweights(self): + """Make weights for the aic Modelevaluators.""" self.aicweights = {} em = self.ppe.error_method @@ -160,6 +179,7 @@ def makeaicweights(self): self.aicweights[dg] = em.akaikeweights(self.aics[dg]) def makeaicprobs(self): + """Make probabilities for the sequence of AICs.""" self.aicprobs = {} em = self.ppe.error_method @@ -167,6 +187,7 @@ def makeaicprobs(self): self.aicprobs[dg] = em.akaikeprobs(self.aics[dg]) def makesortedprobs(self): + """Make probabilities for the sequence of AICs in a sorted order.""" self.sortedprobs = {} for dg in self.dgs: @@ -175,9 +196,12 @@ def makesortedprobs(self): def animate_probs(self, step=False, duration=0.0, **kwds): """Show animation of extracted peaks from first to last. - Parameters: - step - Require keypress to show next plot - duration - Minimum time in seconds to complete animation. Default 0. + Parameters + ---------- + step : bool + Require keypress to show next plot, default is False. + duration : float + Minimum time in seconds to complete animation. Default is 0. Keywords passed to pyplot.plot()""" if duration > 0: @@ -217,9 +241,12 @@ def animate_probs(self, step=False, duration=0.0, **kwds): def animate_classprobs(self, step=False, duration=0.0, **kwds): """Show animation of extracted peaks from first to last. - Parameters: - step - Require keypress to show next plot - duration - Minimum time in seconds to complete animation. Default 0. + Parameters + ---------- + step : bool + Require keypress to show next plot, default is False. + duration : float + Minimum time in seconds to complete animation. Default is 0. Keywords passed to pyplot.plot()""" if duration > 0: @@ -294,10 +321,16 @@ def classify(self, r, tolerance=0.05): 2) The exemplar (first model) of each class isn't the best representative 3) The parameters vary so smoothly there aren't actually definite classes - Parameters: - r - The r values over which to evaluate the models - tolerance - The fraction below which models are considered the same + Parameters + ---------- + r : array-like + The r values over which to evaluate the models + tolerance : float + The fraction below which models are considered the same + Returns + ------- + None """ self.classes = [] self.classes_idx = {} @@ -394,6 +427,7 @@ def makesortedclasses(self): self.sortedclasses[dg] = bestinclass def makeclassweights(self): + """Make weights for all classes.""" self.classweights = {} em = self.ppe.error_method @@ -402,6 +436,7 @@ def makeclassweights(self): self.classweights[dg] = em.akaikeweights([self.aics[dg][b] for b in bestinclass]) def makeclassprobs(self): + """Make probabilities for all classes.""" self.classprobs = {} em = self.ppe.error_method @@ -410,17 +445,38 @@ def makeclassprobs(self): self.classprobs[dg] = em.akaikeprobs([self.aics[dg][b] for b in bestinclass]) def makesortedclassprobs(self): + """Make probabilities for all classes in sorted order.""" self.sortedclassprobs = {} for dg in self.dgs: self.sortedclassprobs[dg] = np.argsort(self.classprobs[dg]).tolist() def dg_key(self, dg_in): - """Return the dg value usable as a key nearest to dg_in.""" + """Return the dg value usable as a key nearest to dg_in. + + Parameters + ---------- + dg_in : The uncertainties of the model + + Returns + ------- + float + The dg value usable as a key nearest to dg_in.""" idx = (np.abs(self.dgs - dg_in)).argmin() return self.dgs[idx] def bestclasses(self, dgs=None): + """Return the best classes for all models. + + Parameters + ---------- + dgs : array-like, optional + The uncertainties of the models, by default None + + Returns + ------- + array-like + The best classes for all models.""" if dgs is None: dgs = self.dgs best = [] @@ -429,6 +485,18 @@ def bestclasses(self, dgs=None): return np.unique(best) def bestmodels(self, dgs=None): + """Return the best models for all models. + + Parameters + ---------- + dgs : array-like, optional + The uncertainties of the models, by default None + + Returns + ------- + array-like + Sequence of best model + """ if dgs is None: dgs = self.dgs best = [] @@ -438,6 +506,19 @@ def bestmodels(self, dgs=None): return np.unique(best) def classbestdgs(self, cls, dgs=None): + """Return the best uncertainties for the models. + + Parameters + ---------- + cls : ModelEvaluator Class + Override corder with a specific class index, or None to ignore classes entirely. + dgs : array-like, optional + The uncertainties of the models, by default None + + Returns + ------- + array-like + Sequence of best uncertainties for the models.""" if dgs is None: dgs = self.dgs bestdgs = [] @@ -447,7 +528,20 @@ def classbestdgs(self, cls, dgs=None): return bestdgs def modelbestdgs(self, model, dgs=None): - """Return uncertainties where given model has greatest Akaike probability.""" + """Return uncertainties where given model has greatest Akaike probability. + + Parameters + ---------- + model : ModelEvaluator Class + The model evaluator class to use + dgs : array-like, optional + The uncertainties of the models, by default None + + Returns + ------- + array-like + The uncertainties where given model has greatest Akaike probability + """ if dgs is None: dgs = self.dgs bestdgs = [] @@ -461,7 +555,8 @@ def modelbestdgs(self, model, dgs=None): def plot3dclassprobs(self, **kwds): """Return 3D plot of class probabilities. - Keywords: + Keywords + -------- dGs - Sequence of dG values to plot. Default is all values. highlight - Sequence of dG values to highlight on plot. Default is []. classes - Sequence of indices of classes to plot. Default is all classes. @@ -486,7 +581,9 @@ def plot3dclassprobs(self, **kwds): All other keywords are passed to the colorbar. - Returns a dictionary containing the following figure elements: + Returns + ------- + a dictionary containing the following figure elements: "fig" - The figure "axis" - The image axis "cbaxis" - The colorbar axis, if it exists. @@ -633,15 +730,22 @@ def plot3dclassprobs(self, **kwds): def get_model(self, dG, **kwds): """Return index of best model of best class at given dG. - Parameters: - dG - The uncertainty used to calculate probabilities - + Parameters + ---------- + dG : array-like + The uncertainty used to calculate probabilities - Keywords: + Keywords + -------- corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). morder - Which model to get based on AIC. Ordered from best to worst from 0 (the default). Returns a model from a class, or from the collection of all models if classes are ignored. cls - Override corder with a specific class index, or None to ignore classes entirely. + + Returns + ------- + int + Index of best model of best class at given dG. """ corder = kwds.pop("corder", 0) morder = kwds.pop("morder", 0) @@ -658,12 +762,19 @@ def get_model(self, dG, **kwds): def get_class(self, dG, **kwds): """Return index of best class at given dG. - Parameters: - dG - The uncertainty used to calculate probabilities - + Parameters + ---------- + dG : array-like + The uncertainty used to calculate probabilities - Keywords: + Keywords + -------- corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). + + Returns + ------- + int + Index of best model of best class at given dG. """ corder = kwds.pop("corder", 0) return self.sortedclassprobs[dG][-1 - corder] # index of corderth best class @@ -671,15 +782,22 @@ def get_class(self, dG, **kwds): def get_prob(self, dG, **kwds): """Return Akaike probability of best model of best class at given dG. - Parameters: - dG - The uncertainty used to calculate probabilities - + Parameters + ---------- + dG : array-like + The uncertainty used to calculate probabilities - Keywords: + Keywords + -------- corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). morder - Which model to get based on AIC. Ordered from best to worst from 0 (the default). Returns a model from a class, or from the collection of all models if classes are ignored. cls - Override corder with a specific class index, or None to ignore classes entirely. + + Returns + ------- + array-like + The sequence of Akaike probability of best model of best class at given dG. """ idx = self.get_model(dG, **kwds) if "cls" in kwds and kwds["cls"] is None: @@ -691,15 +809,22 @@ def get_prob(self, dG, **kwds): def get_nfree(self, dG, **kwds): """Return number of free parameters of best model of best class at given dG. - Parameters: - dG - The uncertainty used to calculate probabilities - + Parameters + ---------- + dG : array-like + The uncertainty used to calculate probabilities - Keywords: + Keywords + -------- corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). morder - Which model to get based on AIC. Ordered from best to worst from 0 (the default). Returns a model from a class, or from the collection of all models if classes are ignored. cls - Override corder with a specific class index, or None to ignore classes entirely. + + Returns + ------- + int + Number of free parameters of best model of best class at given dG. """ idx = self.get_model(dG, **kwds) model = self.results[idx][1] @@ -709,15 +834,22 @@ def get_nfree(self, dG, **kwds): def get_aic(self, dG, **kwds): """Return number of free parameters of best model of best class at given dG. - Parameters: - dG - The uncertainty used to calculate probabilities - + Parameters + ---------- + dG : array-like + The uncertainty used to calculate probabilities - Keywords: + Keywords + -------- corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). morder - Which model to get based on AIC. Ordered from best to worst from 0 (the default). Returns a model from a class, or from the collection of all models if classes are ignored. cls - Override corder with a specific class index, or None to ignore classes entirely. + + Returns + ------- + int + Number of free parameters of best model of best class at given dG. """ idx = self.get_model(dG, **kwds) return self.aics[dG][idx].stat @@ -725,17 +857,25 @@ def get_aic(self, dG, **kwds): def get(self, dG, *args, **kwds): """Return tuple of values corresponding to string arguments for best model of best class at given dG. - Parameters: - dG - The uncertainty used to calculate probabilities + Parameters + ---------- + dG : array-like + The uncertainty used to calculate probabilities Permissible arguments: "aic", "class", "dG", "model", "nfree", "prob" ("dG" simply returns the provided dG value) - Keywords: + Keywords + -------- corder - Which class to get based on AIC. Ordered from best to worst from 0 (the default). morder - Which model to get based on AIC. Ordered from best to worst from 0 (the default). Returns a model from a class, or from the collection of all models if classes are ignored. cls - Override corder with a specific class index, or None to ignore classes entirely. + + Returns + ------- + tuple + The values corresponding to string arguments for best model of best class at given dG. """ fdict = { "aic": self.get_aic, @@ -754,6 +894,16 @@ def maxprobdG_byclass(self, model): """Return the post-hoc dG for which the given model's Akaike probability is maximized. Each model is mapped to its class' best member. + + Parameters + ---------- + model : array-like + The model to get the post-hoc dG. + + Returns + ------- + array-like + The post-hoc dG for the given model where the given model's Akaike probability is maximized. """ cls = self.classes_idx[model] probs = [self.classprobs[dg][cls] for dg in self.dgs] @@ -762,7 +912,18 @@ def maxprobdG_byclass(self, model): def maxprobdG_bymodel(self, model): """Return the post-hoc dG for which the given model's Akaike probability is maximized. - Classes are not considered.""" + Classes are not considered. + + Parameters + ---------- + model : array-like + The model to get the post-hoc dG. + + Returns + ------- + array-like + The post-hoc dG by the given model's Akaike probability is maximize + """ probs = [self.aicprobs[dg][model] for dg in self.dgs] prob_idx = np.argmax(probs) return self.dgs[prob_idx] @@ -770,13 +931,33 @@ def maxprobdG_bymodel(self, model): def maxprobmodel_byclass(self, dG): """Calculate the model which maximizes probability at given dG. - The best class is mapped to its best model.""" + The best class is mapped to its best model. + + Parameters + ---------- + dG : array-like + The uncertainty used to calculate probabilities + + Returns + ------- + float + The model mapped by class which maximizes probability at given dG.""" cls = self.sortedclassprobs[dG][-1] m = self.sortedclasses[dG][cls][-1] return m def maxprobmodel_bymodel(self, dG): """Return the model which maximizes probability at given dG. - Classes are not considered.""" + Classes are not considered. + + Parameters + ---------- + dG : array-like + The uncertainty used to calculate probabilities + + Returns + ------- + model : array-like + The model which maximizes probability at given dG.""" # Note that if there are identical models this returns the one of greatest dg. return self.sortedprobs[dG][-1] From 4d3d8fff01cb444323b484247c21e5da4f527162 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 21 Aug 2024 04:25:48 +0800 Subject: [PATCH 48/65] numpydoc documentation build for ModelCluster class (#86) --- src/diffpy/srmise/modelcluster.py | 127 ++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 22 deletions(-) diff --git a/src/diffpy/srmise/modelcluster.py b/src/diffpy/srmise/modelcluster.py index 26816e6..a65ffb1 100644 --- a/src/diffpy/srmise/modelcluster.py +++ b/src/diffpy/srmise/modelcluster.py @@ -887,11 +887,27 @@ def replacepeaks(self, newpeaks, delslice=slice(0, 0)): return def deletepeak(self, idx): - """Delete the peak at the given index.""" + """Delete the peak at the given index. + + Parameters + ---------- + idx : int + Index of peak to delete. + + Returns + ------- + None + """ self.replacepeaks([], slice(idx, idx + 1)) def estimatepeak(self): - """Attempt to add single peak to empty cluster. Return True if successful.""" + """Attempt to add single peak to empty cluster. Return True if successful. + + Returns + ------- + bool + True if successful, False otherwise. + """ # STUB!!! ### # Currently only a single peak function is supported. Dynamic # selection from multiple types may require additional support @@ -926,16 +942,26 @@ def fit( """Perform a chi-square fit of the model to data in cluster. Parameters - justify - Revert to initial model (if one exists) if new model - has only a single peak and the quality of the fit suggests - additional peaks are present. - ntrials - The maximum number of function evaluations. - '0' indicates the fitting algorithm's default. - fitbaseline - Whether to fit baseline along with peaks - estimate - Estimate a single peak from data if model is empty. - cov - Optional ModelCovariance object preserves covariance information. - cov_format - Parameterization to use in cov. + ---------- + justify : bool + Revert to initial model (if one exists) if new model + has only a single peak and the quality of the fit suggests + additional peaks are present. Default is False. + ntrials : int + The maximum number of function evaluations. + '0' indicates the fitting algorithm's default. + fitbaseline : bool + Whether to fit baseline along with peaks. Default is False. + estimate : bool + Estimate a single peak from data if model is empty. Default is True. + cov : ModelCovariance or None + Optional ModelCovariance object preserves covariance information. + cov_format : str + Parameterization to use in cov. + Returns + ------- + ModelEvaluator or None If fitting changes a model, return ModelEvaluator instance. Otherwise return None. """ @@ -1040,10 +1066,16 @@ def contingent_fit(self, minpoints, growth_threshold): """Fit cluster if it has grown sufficiently large since its last fit. Parameters - minpoints - The minimum number of points an empty cluster requires to fit. - growth_threshold - Fit non-empty model if (currentsize/oldsize) >= this value. + ---------- + minpoints : int + The minimum number of points an empty cluster requires to fit. + growth_threshold : float + Fit non-empty model if (currentsize/oldsize) >= this value. - Return ModelEvaluator instance if fit changed, otherwise None. + Returns + ------- + ModelEvaluator or None + Return ModelEvaluator instance if fit changed, otherwise None. """ if self.never_fit: return None @@ -1115,10 +1147,16 @@ def reduce_to(self, x, y): a maximum very close to x may prevent optimal results. Parameters - x - Position at which to match - y - Height to match. + ---------- + x : array-like + The position at which to match + y : array-like + The height to match. - Return ModelEvaluator instance if fit changed, otherwise None.""" + Returns + ------- + ModelEvaluator or None + Return ModelEvaluator instance if fit changed, otherwise None.""" # No reduction neccessary if self.model.value(x) < y: logger.debug("reduce_to: No reduction necessary.") @@ -1142,7 +1180,19 @@ def reduce_to(self, x, y): return quality def value(self, r=None): - """Return value of baseline+model over cluster.""" + """Return value of baseline+model over cluster. + + Parameters + ---------- + r : array-like, optional + value(s) over which to calculate the baseline's value. + The default is over the entire cluster. + + Returns + ------- + float + The value of baseline+model over cluster. + """ if len(self.model) == 0: return self.valuebl(r) else: @@ -1157,8 +1207,14 @@ def valuebl(self, r=None): If no baseline exists its value is 0 everywhere. Parameters + ---------- r - value(s) over which to calculate the baseline's value. The default is over the entire cluster. + + Returns + ------- + float + The value of baseline's value. """ if self.baseline is None: if r is None: @@ -1183,10 +1239,18 @@ def quality(self, evaluator=None, **kwds): details see ModelEvaluator documentation. Parameters - evaluator - A ModelEvaluator class (not instance) to use instead of default. + ---------- + evaluator : ModelEvaluator class or None + The ModelEvaluator class to use. Default is None. Keywords + -------- kwds - Keyword arguments passed the the ModelEvaluator's evaluate() method. + + Returns + ------- + ModelEvaluator instance + The ModelEvaluator instance with quality calculated """ if evaluator is None: evaluator_inst = self.error_method() @@ -1199,7 +1263,14 @@ def plottable(self, joined=False): """Return sequence suitable for plotting cluster model+baseline with matplotlib. Parameters - joined - Return sum of all peaks, or each one individually. + ---------- + joined : bool + Return sum of all peaks if joined is True, or each one individually if False. + + Returns + ------- + array-like + A sequence of plottable objects. """ if joined: return [self.r_cluster, self.y_cluster, self.r_cluster, self.value()] @@ -1212,14 +1283,26 @@ def plottable(self, joined=False): return toreturn def plottable_residual(self): - """Return sequence suitable for plotting cluster residual with matplotlib.""" + """Return sequence suitable for plotting cluster residual with matplotlib. + + Returns + ------- + array-like + A sequence of plottable clusters and residuals. + """ return [self.r_cluster, self.residual()] def augment(self, source): """Add peaks from another ModelCluster that improve this one's quality. Parameters - source - A ModelCluster instance + ---------- + source : ModelCluster instance + The ModelCluster instance to augment the model's quality. + + Returns + ------- + None """ best_model = self.model.copy() best_qual = self.quality() From def4a2d9a2ceda1a090f51424a4402df62f55f1d Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:23:30 +0800 Subject: [PATCH 49/65] numpydoc build for pdfdataset (#88) --- src/diffpy/srmise/pdfdataset.py | 154 ++++++++++++++++++++++---------- 1 file changed, 107 insertions(+), 47 deletions(-) diff --git a/src/diffpy/srmise/pdfdataset.py b/src/diffpy/srmise/pdfdataset.py index dc1f47b..49fd168 100644 --- a/src/diffpy/srmise/pdfdataset.py +++ b/src/diffpy/srmise/pdfdataset.py @@ -32,16 +32,22 @@ class PDFComponent(object): """Common base class.""" def __init__(self, name): - """initialize + """initialize the object - name -- object name + Parameter + --------- + name : str + object name """ self.name = name def close(self, force=False): """close myself - force -- if forcibly (no wait) + Parameter + --------- + force : bool + Force to close if True, default is False. """ pass @@ -49,31 +55,44 @@ def close(self, force=False): class PDFDataSet(PDFComponent): """PDFDataSet is a class for experimental PDF data. - Data members: - robs -- list of observed r points - Gobs -- list of observed G values - drobs -- list of standard deviations of robs - dGobs -- list of standard deviations of Gobs - stype -- scattering type, 'X' or 'N' - qmax -- maximum value of Q in inverse Angstroms. Termination - ripples are neglected for qmax=0. - qdamp -- specifies width of Gaussian damping factor in pdf_obs due - to imperfect Q resolution - qbroad -- quadratic peak broadening factor related to dataset - spdiameter -- particle diameter for shape damping function - Note: This attribute was moved to PDFStructure. - It is kept for backward compatibility when reading - PDFgui project files. - dscale -- scale factor of this dataset - rmin -- same as robs[0] - rmax -- same as robs[-1] - filename -- set to absolute path after reading from file - metadata -- dictionary for other experimental conditions, such as - temperature or doping - - Global member: - persistentItems -- list of attributes saved in project file - refinableVars -- set (dict) of refinable variable names. + Attributes + ---------- + robs : list + The list of observed r points. + Gobs : list + The list of observed G values. + drobs : list + The list of standard deviations of `robs`. + dGobs : list + The list of standard deviations of `Gobs`. + stype : str + The scattering type, either 'X' or 'N'. + qmax : float + The maximum value of Q in inverse Angstroms. Termination ripples are neglected for qmax=0. + qdamp : float + Specifies width of Gaussian damping factor in pdf_obs due to imperfect Q resolution. + qbroad : float + The quadratic peak broadening factor related to the dataset. + spdiameter : float + The particle diameter for shape damping function. Note: This attribute was moved to PDFStructure. + It is retained here for backward compatibility when reading PDFgui project files. + dscale : float + The scale factor of this dataset. + rmin : float + The same as `robs[0]`. + rmax : float + The same as `robs[-1]`. + filename : str + Set to the absolute path after reading from a file. + metadata : dict + The dictionary for other experimental conditions, such as temperature or doping. + + Class Members + ------------- + persistentItems : list + The list of attributes saved in the project file. + refinableVars : set + The set (or dict-like) of refinable variable names. """ persistentItems = [ @@ -95,14 +114,17 @@ class PDFDataSet(PDFComponent): def __init__(self, name): """Initialize. - name -- name of the data set. It must be a unique identifier. + name : str + The name of the data set. It must be a unique identifier. """ PDFComponent.__init__(self, name) self.clear() return def clear(self): - """reset all data members to initial empty values""" + """reset all data members to initial empty values + + The purpose of this method is to set the PDF dataset to initial empty values.""" self.robs = [] self.Gobs = [] self.drobs = [] @@ -121,12 +143,21 @@ def clear(self): return def setvar(self, var, value): - """Assign data member using PdfFit-style variable. - Used by applyParameters(). + """Assign a data member using PdfFit-style variable notation. + This method is typically utilized by the `applyParameters()` function. + + Parameters + ---------- + var : str + String representation of the dataset PdfFit variable. + Possible values include: 'qdamp', 'qbroad', 'dscale'. + + value : float + The new value to which the variable `var` will be set. - var -- string representation of dataset PdfFit variable. - Possible values: qdamp, qbroad, dscale - value -- new value of the variable + Returns + ------- + None """ barevar = var.strip() fvalue = float(value) @@ -141,10 +172,16 @@ def getvar(self, var): """Obtain value corresponding to PdfFit dataset variable. Used by findParameters(). - var -- string representation of dataset PdfFit variable. - Possible values: qdamp, qbroad, dscale + Parameters + ---------- + var : str + string representation of dataset PdfFit variable. + Possible values: qdamp, qbroad, dscale - returns value of var + Returns + ------- + float + value of var """ barevar = var.strip() if barevar in PDFDataSet.refinableVars: @@ -157,9 +194,12 @@ def getvar(self, var): def read(self, filename): """load data from PDFGetX2 or PDFGetN gr file - filename -- file to read from + filename : str + file to read from - returns self + Returns + ------- + self """ try: self.readStr(open(filename, "rb").read()) @@ -176,9 +216,13 @@ def read(self, filename): def readStr(self, datastring): """read experimental PDF data from a string - datastring -- string of raw data + Parameter + --------- + datastring : str + string of raw data - returns self + Returns + self """ self.clear() # useful regex patterns: @@ -299,9 +343,14 @@ def readStr(self, datastring): def write(self, filename): """Write experimental PDF data to a file. - filename -- name of file to write to + Parameters + ---------- + filename : str + name of file to write to - No return value. + Returns + ------- + None """ bytes = self.writeStr() f = open(filename, "w") @@ -312,7 +361,11 @@ def write(self, filename): def writeStr(self): """String representation of experimental PDF data. - Return data string. + + Returns + ------- + str + The PDF data string. """ lines = [] # write metadata @@ -357,8 +410,15 @@ def writeStr(self): def copy(self, other=None): """copy self to other. if other is None, create new instance - other -- ref to other object - returns reference to copied object + Parameters + ---------- + other : PDFDataSet instance + ref to other object + + Returns + ------- + PDFDataSet instance + reference to copied object """ if other is None: other = PDFDataSet(self.name) From 4cb462e5a57c50d433b3d2561a958f9422b5af55 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Thu, 22 Aug 2024 00:52:58 +0800 Subject: [PATCH 50/65] numpydoc build for pdfpeakextraction.py (#89) --- src/diffpy/srmise/pdfpeakextraction.py | 284 ++++++++++++++++++------- 1 file changed, 207 insertions(+), 77 deletions(-) diff --git a/src/diffpy/srmise/pdfpeakextraction.py b/src/diffpy/srmise/pdfpeakextraction.py index 58b4132..8303843 100644 --- a/src/diffpy/srmise/pdfpeakextraction.py +++ b/src/diffpy/srmise/pdfpeakextraction.py @@ -36,40 +36,36 @@ class PDFPeakExtraction(PeakExtraction): - """Class for peak extraction of peaks from the PDF. - - Data members in addition to those from PeakExtraction - filename: Source PDF file - nyquist: Whether or not to fit final model at Nyquist sampling rate - qmax: qmax to use during extraction. Use 0 for infinity. - qmax_reportedbypdf: The qmax read from a file containing a PDF - qmax_fromdata: The qmax determined directly from the PDF data - scale: Whether or not to use increased uncertainties when supersampling. - This can speed extraction by reducing the number of very small - peaks found while supersampled, but also means small features - are more likely to be missed. This option puts the chi-square error - of a fit on roughly the same scale before and after resampling. - This option has no effect when Nyquist is False, and defaults - to False when Nyquist is True. - supersample: Make sure data is supersampled by at least this factor - above Nyquist sampling before starting extraction. - - Note that resampling the PDF does not properly propagate the corresponding - uncertainties, which are merely interpolated (and possibly scaled, see above). - Further, all uncertainties are treated as statistically independent, but above - the Nyquist rate the uncertainties of nearby points are highly correlated. - The most trustworthy results are therefore obtained by providing data sampled - at the Nyquist rate with correctly propagated uncertainties. - - In some cases the number of free parameters of the best model found may - exceed the number of independent points in the PDF. This is frequently - true when the PDF is oversampled and/or the reported uncertainties in the - PDF are very small. If this prevents resampling at the Nyquist rate (when - this is desired) the degree of oversampling is reported. + """PDFPeakExtraction extends the PeakExtraction class to specialize in extracting + peaks from PDF (Probability Density Function) data. + + Parameters + ---------- + filename : str + The source PDF file path. + nyquist : bool, optional + Specifies whether to fit the final model at the Nyquist sampling rate. + Defaults to False. + qmax : float, optional + The maximum q value to use during peak extraction. Use 0 to denote infinity. + Defaults to 0. + qmax_reportedbypdf : float + The qmax value read directly from the PDF file. + qmax_fromdata : float + The qmax value determined directly from the PDF data. + scale : bool, optional + Determines whether to use increased uncertainties during supersampling. + This can expedite extraction by minimizing the detection of minuscule peaks, + albeit risking the overlook of minor features. When `Nyquist` is True, + uncertainties are scaled to maintain a similar chi-square error pre- + and post-resampling. Defaults to False if `Nyquist` is True. + supersample : int, optional + Ensures the data is supersampled by at least this factor above the + Nyquist rate before initiating peak extraction. Defaults to 1. """ def __init__(self): - """Initialize.""" + """Initialize the PDFPeakExtraction class.""" newvars = ["qmax", "supersample", "nyquist", "scale"] PeakExtraction.__init__(self, newvars) return @@ -78,7 +74,9 @@ def loadpdf(self, pdf): """Load dataset. Parameters - pdf: A PDFDataSet object, or the name of a file readable by one. + ---------- + pdf: PDFDataSet instance or str + The PDFDataSet instance or a PDF file name. """ self.clear() @@ -93,7 +91,14 @@ def loadpdf(self, pdf): return def setdata(self, x, y, dx=None, dy=None): - """Set data.""" + """Set data. + + Parameters + ---------- + x : array-like + The x-coordinates of the data. + y : array-like + The y-coordinates of the data.""" PeakExtraction.setdata(self, x, y, dx, dy) try: self.qmax_fromdata = find_qmax(self.x, self.y)[0] @@ -101,7 +106,9 @@ def setdata(self, x, y, dx=None, dy=None): logger.info("Could not determine qmax from the data.") def clear(self): - """Clear all members.""" + """Clear all members. + + The purpose of the method is to ensure the object is in a clean state.""" # TODO: Clear additional members self.filename = None self.nyquist = None @@ -115,22 +122,48 @@ def clear(self): def setvars(self, quiet=False, **kwds): """Set one or more extraction variables. - Variables - quiet: [False] Log changes quietly. - - Keywords - cres: The clustering resolution, must be >= 0. - effective_dy: The uncertainties actually used during extraction - dg: Alias for effective_dy - pf: Sequence of PeakFunctionBase subclass instances. - baseline: Baseline instance or BaselineFunction instance (use built-in estimation) - error_method: ErrorEvaluator subclass instance used to compare models (default AIC) - initial_peaks: Peaks instance. These peaks are present at the start of extraction. - rng: Sequence specifying the least and greatest x-values over which to extract peaks. - qmax: The qmax value for the pdf. Using "automatic" will estimate it from data. - nyquist: Use nyquist sampling or not (boolean) - supersample: Degree of supersampling above Nyquist rate to use. - scale: Scale uncertainties on recursion when nyquist is True (boolean).""" + Parameters + ---------- + quiet : bool, optional + Log changes quietly. Default is False. + + **kwds : keyword arguments + Additional variables to set. Possible keywords include: + + - cres : float + The clustering resolution, must be greater than or equal to 0. + + - effective_dy : float + The uncertainties actually used during extraction. Aliased as 'dg'. + + - pf : list of PeakFunctionBase subclasses instances + Sequence of peak function base subclass instances. + + - baseline : Baseline or BaselineFunction instance + Baseline instance or BaselineFunction instance for built-in estimation. + + - error_method : ErrorEvaluator subclass instance + Error evaluator subclass instance used to compare models. Default is AIC. + + - initial_peaks : Peaks instance + Peaks instance representing the peaks present at the start of extraction. + + - rng : tuple of (float, float) + Specifies the least and greatest x-values over which to extract peaks. + + - qmax : float or "automatic" + The qmax value for the probability density function (pdf). + If set to "automatic", it will be estimated from the data. + + - nyquist : bool + Whether to use nyquist sampling or not. + + - supersample : int + Degree of supersampling above the Nyquist rate to use. + + - scale : bool + Scale uncertainties on recursion when nyquist is True. + """ # Treat "dg" as alias for "effective_dy" if "dg" in kwds: if "effective_dy" not in kwds: @@ -152,7 +185,12 @@ def setvars(self, quiet=False, **kwds): PeakExtraction.setvars(self, quiet, **kwds) def defaultvars(self, *args): - """Set default values.""" + """Set default values. + + Parameters + ---------- + *args : argparse.Namespace + Arguments passed to PeakExtraction.setdata().""" nargs = list(args) # qmax preference: reported, then fromdata, then 0. @@ -198,7 +236,7 @@ def defaultvars(self, *args): nargs.remove("cres") if self.pf is None or "pf" in args: - from diffpy.srmise.peaks import GaussianOverR + from diffpy.srmise.peaks.gaussianoverr import GaussianOverR self.pf = [GaussianOverR(0.7)] if "pf" in args: @@ -224,10 +262,21 @@ def resampledata(self, dr, **kwds): new grid. Parameters - dr: The sampling interval + ---------- + dr : float + The sampling interval for resampling the data. - Keywords - eps: [10^-6] Suppress information lost warning when dr-dr_nyquist < eps""" + **kwds : dict, optional + Additional keyword arguments. + + - eps : float, default=1e-6 + Suppresses the information lost warning when the difference between `dr` + and the Nyquist interval `dr_nyquist` is less than `eps`. + + Returns + ------- + tuple of ndarray + A tuple containing the resampled (x, y, error in x, effective error in y).""" self.defaultvars() # Find correct range if necessary. eps = kwds.get("eps", 10**-6) @@ -262,7 +311,14 @@ def errorscale(self, dr): is enabled, and scale is True. Parameters - dr: The sampling interval""" + ---------- + dr: float + The sampling interval + + Returns + ------- + float + The uncertainties scaled.""" if self.qmax > 0 and self.nyquist and self.scale: dr_nyquist = np.pi / self.qmax return np.max([np.sqrt(dr_nyquist / dr), 1.0]) @@ -270,7 +326,20 @@ def errorscale(self, dr): return 1.0 def extract(self, **kwds): - """Extract peaks from the PDF. Returns ModelCovariance instance summarizing results.""" + """Extract peaks from the PDF. Returns ModelCovariance instance summarizing results. + + Parameters + ---------- + **kwds : dict + Additional keyword arguments that might influence the extraction process. + These could include parameters like `qmax`, `supersample`, `nyquist`, etc., which + affect resampling and model refinement strategies. + + Returns + ------- + ModelCovariance + An instance of ModelCovariance summarizing the covariance of the extracted model parameters. + """ # TODO: The sanest way forward is to create a PeakExtraction object that does # the calculations for resampled data. All the relevant extraction variables # can be carefully controlled this way as well. Furthermore, it continues to @@ -298,7 +367,7 @@ def extract(self, **kwds): if dr_raw > dr_nyquist: # Technically I should yell for dr_raw >= dr_nyquist, since information # loss may occur at equality. - logger.warn( + logger.warning( "The input PDF appears to be missing information: The " "sampling interval of the input PDF (%s) is larger than " "the Nyquist interval (%s) defined by qmax=%s. This information " @@ -362,7 +431,7 @@ def extract(self, **kwds): logger.info("\n".join(msg), ext) - from diffpy.srmise.peaks import TerminationRipples + from diffpy.srmise.peaks.terminationripples import TerminationRipples owners = list(set([p.owner() for p in ext.model])) tfuncs = {} @@ -459,7 +528,7 @@ def extract(self, **kwds): logger.info(str(cov)) # logger.info("Correlations > .8:\n%s", "\n".join(str(c) for c in cov.correlationwarning(.8))) except SrMiseUndefinedCovarianceError: - logger.warn("Covariance not defined for final model. Fit may not have converged.") + logger.warning("Covariance not defined for final model. Fit may not have converged.") logger.info(str(ext)) # Update calculated instance variables @@ -471,7 +540,19 @@ def extract(self, **kwds): return cov def fit(self, **kwds): - """Fit peaks in the PDF. Returns ModelCovariance instance summarizing results.""" + """Fit peaks in the PDF. Returns ModelCovariance instance summarizing results. + + Parameters + ---------- + **kwds : dict + Keyword arguments passed to ModelCovariance instance. Different keywords could have + different strategies to fit. See ModelCovariance class for more information. + + Returns + ------- + ModelCovariance instance + The fitted ModelCovariance instance. + """ self.clearcalc() @@ -488,7 +569,7 @@ def fit(self, **kwds): if dr_raw > dr_nyquist: # Technically I should yell for dr_raw >= dr_nyquist, since information # loss may occur at equality. - logger.warn( + logger.warning( "The input PDF appears to be missing information: The " "sampling interval of the input PDF (%s) is larger than " "the Nyquist interval (%s) defined by qmax=%s. This information " @@ -532,7 +613,7 @@ def fit(self, **kwds): try: logger.info(str(cov)) except SrMiseUndefinedCovarianceError: - logger.warn("Covariance not defined for final model. Fit may not have converged.") + logger.warning("Covariance not defined for final model. Fit may not have converged.") # Update calculated instance variables self.extraction_type = "fit" @@ -555,7 +636,17 @@ def writemetadata(self): return datastring def readmetadata(self, metastr): - """Read metadata from string.""" + """Read metadata from string. + + Parameters + ---------- + metastr : str + Metadata string to read. + + Returns + ------- + None + """ # filename res = re.search(r"^filename=(.*)$", metastr, re.M) @@ -617,7 +708,17 @@ def writepwa(self, filename, comments="n/a"): """Write string summarizing extracted peaks to file. Parameters - filename: the name of the file to write""" + ---------- + filename : str + The name of the file to write + + comments : str + The comments to write + + Returns + ------- + None + """ bytes = self.writepwastr(comments) f = open(filename, "w") f.write(bytes) @@ -631,7 +732,13 @@ def writepwastr(self, comments): this file. Parameters - comments: String added to header containing notes about the output. + ---------- + comments : str + The string added to header containing notes about the output. + + Returns + ------- + None """ if self.extracted is None: @@ -813,11 +920,18 @@ def resample(orig_r, orig_y, new_r): """Resample sequence with Whittaker-Shannon interpolation formula. Parameters - orig_r: (Numpy array) The r grid of the original sample. - orig_y: (Numpy array) The data to resample. - new_r: (Numpy array) The resampled r grid. - - Returns sequence of same type as new_r with the resampled values. + ---------- + orig_r : array-like + The r grid of the original sample. + orig_y : array-like + The data to resample. + new_r : array-like + The resampled r grid. + + Returns + ------- + new_y : array-like + The sequence of same type as new_r with the resampled values. """ n = len(orig_r) dr = (orig_r[-1] - orig_r[0]) / (n - 1) @@ -842,8 +956,19 @@ def find_qmax(r, y, showgraphs=False): """Determine approximate qmax from PDF. Parameters - r: The r values of the PDF. - y: The corresponding y values of the PDF.""" + ---------- + r : array-like + The r values of the PDF. + y : array-like + The corresponding y values of the PDF. + showgraphs : bool + If True, the graphs are shown. + + Returns + ------- + tuple + The qmax of the PDF and its corresponding uncertainties. + """ if len(r) != len(y): emsg = "Argument arrays must have the same length." raise ValueError(emsg) @@ -914,10 +1039,15 @@ def stdratio(data): deviation. Parameters - data: Sequence of data values - - Returns an array of length floor(len(data)/2)-1. The ith element is - equivalent to std(data[:i+2])/std(data[i+2:2i+4]).""" + ---------- + data : array-like + The sequence of data values + + Returns + ------- + array-like + an array of length floor(len(data)/2)-1. The ith element is + equivalent to std(data[:i+2])/std(data[i+2:2i+4]).""" limit = int(np.floor(len(data) / 2)) std_left = np.zeros(limit) From 03b18c16d20e9af512f22a7a9f133585a8ffc75c Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:42:52 +0800 Subject: [PATCH 51/65] numpydoc build for gaussianoverr.py (#91) * numpydoc build for gaussianoverr.py * [pre-commit.ci] auto fixes from pre-commit hooks * fix pre-commit --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/diffpy/srmise/peaks/gaussianoverr.py | 146 +++++++++++++++++------ 1 file changed, 107 insertions(+), 39 deletions(-) diff --git a/src/diffpy/srmise/peaks/gaussianoverr.py b/src/diffpy/srmise/peaks/gaussianoverr.py index 8b125a9..2cf9d65 100644 --- a/src/diffpy/srmise/peaks/gaussianoverr.py +++ b/src/diffpy/srmise/peaks/gaussianoverr.py @@ -78,12 +78,19 @@ def estimate_parameters(self, r, y): """Estimate parameters for single peak from data provided. Parameters - r: (Numpy array) Data along r from which to estimate - y: (Numpy array) Data along y from which to estimate - - Returns Numpy array of parameters in the default internal format. - Raises SrMiseEstimationError if parameters cannot be estimated for any - reason.""" + ---------- + r : array-like + Data along r from which to estimate + y : array-like + Data along y from which to estimate + + Returns + ------- + array-like + Numpy array of parameters in the default internal format. + Raises SrMiseEstimationError if parameters cannot be estimated for any + reason. + """ if len(r) != len(y): emsg = "Arrays r, y must have equal length." raise SrMiseEstimationError(emsg) @@ -154,9 +161,19 @@ def scale_at(self, pars, x, scale): SrMiseScalingError if the parameters cannot be scaled. Parameters - pars: (Array) Parameters corresponding to a single peak - x: (float) Position of the border - scale: (float > 0) Size of scaling at x.""" + ---------- + pars : array-like + Parameters corresponding to a single peak + x : float + Position of the border + scale : float + Size of scaling at x. Must be positive. + + Returns + ------- + array-like + The sequence of scaled parameters. + """ if scale <= 0: emsg = "".join(["Cannot scale by ", str(scale), "."]) raise SrMiseScalingError(emsg) @@ -204,19 +221,37 @@ def scale_at(self, pars, x, scale): return tpars def _jacobianraw(self, pars, r, free): - """Return Jacobian of width-limited Gaussian/r. - - pars: Sequence of parameters for a single width-limited Gaussian - pars[0]=peak position - pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. - =tan(pi/2*fwhm/maxwidth) - pars[2]=multiplicative constant a, equivalent to peak area - r: sequence or scalar over which pars is evaluated - free: sequence of booleans which determines which derivatives are - needed. True for evaluation, False for no evaluation. + """ + Compute the Jacobian of a width-limited Gaussian/r function. + + This method calculates the partial derivatives of a Gaussian/r function + with respect to its parameters, considering a limiting width. The Gaussian/r's + width approaches its maximum FWHM (maxwidth) as the effective width parameter + (`pars[1]`) tends to infinity. + + Parameters + ---------- + pars : array-like + Sequence of parameters defining a single width-limited Gaussian: + - pars[0]: Peak position. + - pars[1]: Effective width, which scales up to the full width at half maximum (fwhm=maxwidth) as + `pars[1]` approaches infinity. It is mathematically represented as `tan(pi/2 * fwhm / maxwidth)`. + - pars[2]: Multiplicative constant 'a', equivalent to the peak area. + r : array-like or scalar + The sequence or scalar over which the Gaussian parameters `pars` are evaluated. + free : array-like of bools + Determines which derivatives need to be computed. A `True` value indicates that the derivative + with respect to the corresponding parameter in `pars` should be calculated; + `False` indicates no evaluation is needed. + Returns + ------- + jacobian : ndarray + The Jacobian matrix, where each column corresponds to the derivative of the Gaussian/r function + with respect to one of the input parameters `pars`, evaluated at points `r`. + Only columns corresponding to `True` values in `free` are computed. """ jacobian = [None, None, None] - if (free is False).sum() == self.npars: + if np.sum(np.logical_not(free)) == self.npars: return jacobian # Optimization @@ -304,18 +339,28 @@ def _transform_derivativesraw(self, pars, in_format, out_format): def _transform_parametersraw(self, pars, in_format, out_format): """Convert parameter values from in_format to out_format. - Also restores parameters to a preferred range if it permits multiple - values that correspond to the same physical result. + This method convert parameter values from one format to another and optionally restore + them to a preferred range if the target format supports multiple values + representing the same physical result. Parameters - pars: Sequence of parameters - in_format: A format defined for this class - out_format: A format defined for this class - - Defined Formats - internal: [position, parameterized width-squared, area] - pwa: [position, full width at half maximum, area] - mu_sigma_area: [mu, sigma, area] + ---------- + pars : array_like + Sequence of parameters in the `in_format`. + in_format : str, optional + The input format of the parameters. Supported formats are: + - 'internal': [position, parameterized width-squared, area] + - 'pwa': [position, full width at half maximum, area] + - 'mu_sigma_area': [mu, sigma, area] + Default is 'internal'. + out_format : str, optional + The desired output format of the parameters. Same options as `in_format`. + Default is 'pwa'. + + Returns + ------- + array_like + The transformed parameters in the `out_format`. """ temp = np.array(pars) @@ -367,14 +412,27 @@ def _transform_parametersraw(self, pars, in_format, out_format): return temp def _valueraw(self, pars, r): - """Return value of width-limited Gaussian/r for the given parameters and r values. - - pars: Sequence of parameters for a single width-limited Gaussian - pars[0]=peak position - pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. - =tan(pi/2*fwhm/maxwidth) - pars[2]=multiplicative constant a, equivalent to peak area - r: sequence or scalar over which pars is evaluated + """Compute the value of a width-limited Gaussian/r for the specified parameters at given radial distances. + + This function calculates the value of a Gaussian/r distribution, + where its effective width is constrained and related to the maxwidth. As `pars[1]` approaches infinity, + the effective width reaches `FWHM` (maxwidth). The returned values represent the Gaussian's intensity + across the provided radial coordinates `r`. + + Parameters + ---------- + pars : array_like + A sequence of parameters defining the Gaussian shape: + - pars[0]: Peak position of the Gaussian. + - pars[1]: Effective width factor, approaching infinity implies the FWHM equals `maxwidth`. + It is related to the FWHM by `tan(pi/2*FWHM/maxwidth)`. + - pars[2]: Multiplicative constant 'a', equivalent to the peak area of the Gaussian when integrated. + r : array_like or float + Radial distances or a single value at which the Gaussian is to be evaluated. + Returns + ------- + float + The value of a width-limited Gaussian for the specified parameters at given radial distances. """ return ( np.abs(pars[2]) @@ -388,7 +446,17 @@ def getmodule(self): # Other methods #### def max(self, pars): - """Return position and height of the peak maximum.""" + """Return position and height of the peak maximum. + + Parameters + ---------- + pars : array_like + The sequence of parameters defining the Gaussian shape. + + Returns + ------- + array-like + The sequence of position and height of the peak maximum.""" # TODO: Reconsider this behavior if len(pars) == 0: return None From d55b946b69bfe1e1ad2aa8e4ef2f58f727b9a269 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:43:08 +0800 Subject: [PATCH 52/65] terminationripples.py numpydoc build (#92) --- src/diffpy/srmise/peaks/terminationripples.py | 189 ++++++++++++++---- 1 file changed, 145 insertions(+), 44 deletions(-) diff --git a/src/diffpy/srmise/peaks/terminationripples.py b/src/diffpy/srmise/peaks/terminationripples.py index 4a66b6e..c516c96 100644 --- a/src/diffpy/srmise/peaks/terminationripples.py +++ b/src/diffpy/srmise/peaks/terminationripples.py @@ -26,20 +26,26 @@ class TerminationRipples(PeakFunction): """Methods for evaluation and parameter estimation of a peak function with termination ripples.""" def __init__(self, base, qmax, extension=4.0, supersample=5.0, Cache=None): - """Peak function which adds termination ripples to existing function. + """Peak function constructor which adds termination ripples to existing function. Unlike other peak functions, TerminationRipples can only be evaluated over a uniform grid, or at a single value using an ad hoc uniform grid defined by qmax, extension, and supersample. Parameters - base: Instance of PeakFunction subclass. - qmax: Cut-off frequency in reciprocal space. - extension: How many multiples of 2pi/qmax to extend calculations in - order to avoid edge effects. - supersample: Number intervals over 2pi/qmax when a natural interval - cannot be determined while extending calculations. - Cache: A class (not instance) which implements caching of PeakFunction + ---------- + base : PeakFunction instance + The PeakFunction instance subclass. + qmax : float + The cut-off frequency in reciprocal space. + extension : float + How many multiples of 2pi/qmax to extend calculations in + order to avoid edge effects. Default is 4.0. + supersample : float + Number intervals over 2pi/qmax when a natural interval + cannot be determined while extending calculations. Default is 5.0. + Cache : class + The class (not instance) which implements caching of PeakFunction evaluations.""" parameterdict = base.parameterdict formats = base.parformats @@ -66,12 +72,19 @@ def estimate_parameters(self, r, y): Uses estimation routine provided by base peak function. Parameters - r: (Numpy array) Data along r from which to estimate - y: (Numpy array) Data along y from which to estimate - - Returns Numpy array of parameters in the default internal format. - Raises SrMiseEstimationError if parameters cannot be estimated for any - reason.""" + ---------- + r : array-like + Data along r from which to estimate + y : array-like + Data along y from which to estimate + + Returns + ------- + array-like + Numpy array of parameters in the default internal format. + Raises SrMiseEstimationError if parameters cannot be estimated for any + reason. + """ return self.base.estimate_parameters(r, y) # TODO: Can this be implemented sanely for termination ripples? @@ -82,44 +95,91 @@ def scale_at(self, pars, x, scale): SrMiseScalingError if the parameters cannot be scaled. Parameters - pars: (Array) Parameters corresponding to a single peak - x: (float) Position of the border - scale: (float > 0) Size of scaling at x.""" + ---------- + pars : array-like + The parameters corresponding to a single peak + x : float + The position of the border + scale : float + The size of scaling at x. Must be positive. + + Returns + ------- + array-like + The numpy array of scaled parameters. + """ return self.base.scale_at(pars, x, scale) def _jacobianraw(self, pars, r, free): """Return Jacobian of base function with termination ripples. Parameters - pars: Sequence of parameters for a single peak - r: sequence or scalar over which pars is evaluated - free: sequence of booleans which determines which derivatives are - needed. True for evaluation, False for no evaluation.""" + ---------- + pars : array-like + The sequence of parameters for a single peak + r : array-like + The sequence or scalar over which pars is evaluated + free : array-like + The sequence of booleans which determines which derivatives are + needed. True for evaluation, False for no evaluation. + + Returns + ------- + array-like + The Jacobian matrix of base function with termination ripples. + """ return self.base._jacobianraw(pars, r, free) def _transform_derivativesraw(self, pars, in_format, out_format): """Return gradient matrix for the pars converted from in_format to out_format. Parameters - pars: Sequence of parameters - in_format: A format defined for base peak function - out_format: A format defined for base peak function""" + ---------- + pars : array-like + The sequence of parameters + in_format : str + The format defined for base peak function + out_format : str + The format defined for base peak function + + Returns + ------- + ndarray + The Jacobian matrix of base function with termination ripples with out_format. + """ return self.base._transform_derivativesraw(pars, in_format, out_format) def _transform_parametersraw(self, pars, in_format, out_format): """Convert parameter values from in_format to out_format. Parameters - pars: Sequence of parameters - in_format: A format defined for base peak function - out_format: A format defined for base peak function""" + ---------- + pars : array-like + The sequence of parameters + in_format : str + The format defined for base peak function + out_format : str + The format defined for base peak function + + Returns + ------- + array-like + The sequence of parameter values with out_format. + """ return self.base._transform_parametersraw(pars, in_format, out_format) def _valueraw(self, pars, r): """Return value of base peak function for the given parameters and r values. - pars: Sequence of parameters for a single peak - r: sequence or scalar over which pars is evaluated""" + pars : array-like + The sequence of parameters for a single peak + r : array-like or float + The sequence or scalar over which pars is evaluated + + Returns + ------- + float + The value of base peak function for the given parameters and r.""" return self.base._valueraw(pars, r) # Overridden PeakFunction functions #### @@ -130,12 +190,22 @@ def _valueraw(self, pars, r): def jacobian(self, peak, r, rng=None): """Calculate (rippled) jacobian, possibly restricted by range. - peak: The Peak to be evaluated - r: sequence or scalar over which peak is evaluated - rng: Optional slice object restricts which r-values are evaluated. - The output has same length as r, but unevaluated objects have - a default value of 0. If caching is enabled these may be - previously calculated values instead.""" + Parameters + ---------- + peak : PeakFunction instance + The Peak to be evaluated + r : array-like + The sequence or scalar over which peak is evaluated + rng : slice object + Optional slice object restricts which r-values are evaluated. + The output has same length as r, but unevaluated objects have + a default value of 0. If caching is enabled these may be + previously calculated values instead. Default is None + + Returns + ------- + jac : array-like + The Jacobian of base function with termination ripples.""" if self is not peak._owner: raise ValueError( "Argument 'peak' must be evaluated by the " @@ -180,12 +250,22 @@ def value(self, peak, r, rng=None): to minimize the impact of edge-effects from introducing termination ripples into an existing peak function. - peak: The Peak to be evaluated - r: sequence or scalar over which peak is evaluated - rng: Optional slice object restricts which r-values are evaluated. - The output has same length as r, but unevaluated objects have - a default value of 0. If caching is enabled these may be - previously calculated values instead. + Parameters + ---------- + peak : Peak instance + The Peak to be evaluated + r : array-like + The sequence or scalar over which peak is evaluated + rng : slice object + Optional slice object restricts which r-values are evaluated. + The output has same length as r, but unevaluated objects have + a default value of 0. If caching is enabled these may be + previously calculated values instead. Default is None. + + Returns + ------- + output : array-like + The (rippled) value of peak, possibly restricted by range. """ if self is not peak._owner: raise ValueError( @@ -245,8 +325,17 @@ def cut_freq(self, sequence, delta): function sin(2*pi*r/qmax)/r. Parameters - sequence: (numpy array) The sequence to alter. - delta: The spacing between elements in sequence.""" + ---------- + sequence : array-like + The sequence to alter. + delta : int + The spacing between elements in sequence. + + Returns + ------- + array-like + The sequence with high-frequency components removed. + """ padlen = int(2 ** np.ceil(np.log2(len(sequence)))) padseq = fp.fft(sequence, padlen) dq = 2 * np.pi / ((padlen - 1) * delta) @@ -260,7 +349,19 @@ def cut_freq(self, sequence, delta): return np.real(padseq[0 : len(sequence)]) def extend_grid(self, r, dr): - """Return (extended r, slice giving original range).""" + """Return (extended r, slice giving original range). + + Parameters + ---------- + r : array-like or float + The sequence or scalar over which peak is evaluated + dr : array-like or float + The uncertainties over which peak is evaluated + + Returns + ------- + tuple + The extended r, slice giving original range.""" ext = self.extension * 2 * np.pi / self.qmax left_ext = np.arange(r[0] - dr, max(0.0, r[0] - ext - dr), -dr)[::-1] right_ext = np.arange(r[-1] + dr, r[-1] + ext + dr, dr) From d7228729713d700cb0dd8359861cb1a34badc1aa Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Thu, 22 Aug 2024 23:06:58 +0800 Subject: [PATCH 53/65] numpydoc build for gaussian.py (#90) * numpydoc build for gaussian.py * [pre-commit.ci] auto fixes from pre-commit hooks * pre-commit fix * update for FWHM and maxwidth * update for starting sentence --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/diffpy/srmise/peaks/gaussian.py | 147 ++++++++++++++++++++-------- 1 file changed, 108 insertions(+), 39 deletions(-) diff --git a/src/diffpy/srmise/peaks/gaussian.py b/src/diffpy/srmise/peaks/gaussian.py index 433af3a..b0112b0 100644 --- a/src/diffpy/srmise/peaks/gaussian.py +++ b/src/diffpy/srmise/peaks/gaussian.py @@ -78,12 +78,19 @@ def estimate_parameters(self, r, y): """Estimate parameters for single peak from data provided. Parameters - r: (Numpy array) Data along r from which to estimate - y: (Numpy array) Data along y from which to estimate - - Returns Numpy array of parameters in the default internal format. - Raises SrMiseEstimationError if parameters cannot be estimated for any - reason.""" + ---------- + r : array-like + The data along r from which to estimate + y : array-like + The data along y from which to estimate + + Returns + ------- + array-like + Numpy array of parameters in the default internal format. + Raises SrMiseEstimationError if parameters cannot be estimated for any + reason. + """ if len(r) != len(y): emsg = "Arrays r, y must have equal length." raise SrMiseEstimationError(emsg) @@ -154,9 +161,18 @@ def scale_at(self, pars, x, scale): SrMiseScalingError if the parameters cannot be scaled. Parameters - pars: (Array) Parameters corresponding to a single peak - x: (float) Position of the border - scale: (float > 0) Size of scaling at x.""" + ---------- + pars : array-like + The parameters corresponding to a single peak + x : float + The position of the border + scale : float + The size of scaling at x. Must be positive. + + Returns + ------- + tuple + mu, area, and sigma that are scaled.""" if scale <= 0: emsg = "".join(["Cannot scale by ", str(scale), "."]) raise SrMiseScalingError(emsg) @@ -190,16 +206,36 @@ def scale_at(self, pars, x, scale): return tpars def _jacobianraw(self, pars, r, free): - """Return Jacobian of width-limited Gaussian. - - pars: Sequence of parameters for a single width-limited Gaussian - pars[0]=peak position - pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. - =tan(pi/2*fwhm/maxwidth) - pars[2]=multiplicative constant a, equivalent to peak area - r: sequence or scalar over which pars is evaluated - free: sequence of booleans which determines which derivatives are - needed. True for evaluation, False for no evaluation. + """Compute the Jacobian of a width-limited Gaussian function. + + This method calculates the partial derivatives of a Gaussian function + with respect to its parameters, considering a limiting width. The Gaussian's + width approaches its maximum FWHM (maxwidth) as the effective width parameter + (`pars[1]`) tends to infinity. + + Parameters + ---------- + pars : array-like + The sequence of parameters defining a single width-limited Gaussian: + - pars[0]: Peak position. + - pars[1]: Effective width, which scales up to the full width at half maximum (fwhm=maxwidth) as + `pars[1]` approaches infinity. It is mathematically represented as `tan(pi/2 * fwhm / maxwidth)`. + - pars[2]: Multiplicative constant 'a', equivalent to the peak area. + + r : array-like or scalar + The sequence or scalar over which the Gaussian parameters `pars` are evaluated. + + free : array-like of bools + Determines which derivatives need to be computed. A `True` value indicates that the derivative + with respect to the corresponding parameter in `pars` should be calculated; + `False` indicates no evaluation is needed. + + Returns + ------- + jacobian : ndarray + The Jacobian matrix, where each column corresponds to the derivative of the Gaussian function + with respect to one of the input parameters `pars`, evaluated at points `r`. + Only columns corresponding to `True` values in `free` are computed. """ jacobian = [None, None, None] if (free is False).sum() == self.npars: @@ -236,20 +272,29 @@ def _jacobianraw(self, pars, r, free): return jacobian def _transform_parametersraw(self, pars, in_format, out_format): - """Convert parameter values from in_format to out_format. + """Convert parameter values from one format to another. - Also restores parameters to a preferred range if it permits multiple - values that correspond to the same physical result. + This method also facilitates restoring parameters to a preferred range if the + target format allows for multiple representations of the same physical result. Parameters - pars: Sequence of parameters - in_format: A format defined for this class - out_format: A format defined for this class - - Defined Formats - internal: [position, parameterized width-squared, area] - pwa: [position, full width at half maximum, area] - mu_sigma_area: [mu, sigma, area] + ---------- + pars : array_like + The sequence of parameters in the `in_format`. + in_format : str, optional + The input format of the parameters. Supported formats are: + - 'internal': [position, parameterized width-squared, area] + - 'pwa': [position, full width at half maximum, area] + - 'mu_sigma_area': [mu, sigma, area] + Default is 'internal'. + out_format : str, optional + The desired output format of the parameters. Same options as `in_format`. + Default is 'pwa'. + + Returns + ------- + array_like + The transformed parameters in the `out_format`. """ temp = np.array(pars) @@ -301,14 +346,29 @@ def _transform_parametersraw(self, pars, in_format, out_format): return temp def _valueraw(self, pars, r): - """Return value of width-limited Gaussian for the given parameters and r values. - - pars: Sequence of parameters for a single width-limited Gaussian - pars[0]=peak position - pars[1]=effective width, up to fwhm=maxwidth as par[1] -> inf. - =tan(pi/2*fwhm/maxwidth) - pars[2]=multiplicative constant a, equivalent to peak area - r: sequence or scalar over which pars is evaluated + """Compute the value of a width-limited Gaussian for the specified parameters at given radial distances. + + This function calculates the value of a Gaussian distribution, where its effective width is constrained and + related to the maxwidth. As `pars[1]` approaches infinity, + the effective width reaches `FWHM` (maxwidth). The returned values represent the Gaussian's intensity + across the provided radial coordinates `r`. + + Parameters + ---------- + pars : array_like + A sequence of parameters defining the Gaussian shape: + - pars[0]: Peak position of the Gaussian. + - pars[1]: Effective width factor, approaching infinity implies the FWHM equals `maxwidth`. + It is related to the FWHM by `tan(pi/2*FWHM/maxwidth)`. + - pars[2]: Multiplicative constant 'a', equivalent to the peak area of the Gaussian when integrated. + + r : array_like or float + The radial distances or a single value at which the Gaussian is to be evaluated. + + Returns + ------- + float + The value of a width-limited Gaussian for the specified parameters at given radial distances. """ return ( np.abs(pars[2]) @@ -322,7 +382,16 @@ def getmodule(self): # Other methods #### def max(self, pars): - """Return position and height of the peak maximum.""" + """Return position and height of the peak maximum. + Parameters + ---------- + pars : array_like + A sequence of parameters defining the Gaussian shape. + + Returns + ------- + array_like + The position and height of the peak maximum.""" # TODO: Reconsider this behavior if len(pars) == 0: return None From a66843b95a86ae26ccdfec259e7361549094c0fc Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:27:16 +0800 Subject: [PATCH 54/65] numpydoc build for base.py (#95) --- src/diffpy/srmise/modelevaluators/base.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/diffpy/srmise/modelevaluators/base.py b/src/diffpy/srmise/modelevaluators/base.py index 2e97b3a..179e082 100644 --- a/src/diffpy/srmise/modelevaluators/base.py +++ b/src/diffpy/srmise/modelevaluators/base.py @@ -53,8 +53,14 @@ class ModelEvaluator: worse models.""" def __init__(self, method, higher_is_better): - """method = name of method (string) - higher_is_better = boolean + """Constructor of ModelEvaluator + + Parameters + ---------- + method : str + The name of method + higher_is_better : bool + The boolean to compare higher or lower degree model. """ self.method = method self.higher_is_better = higher_is_better @@ -123,7 +129,16 @@ def __ge__(self, other): return other.stat >= self.stat def chi_squared(self, expected, observed, error): - """Calculates chi-squared statistic.""" + """Calculates chi-squared statistic. + + Parameters + ---------- + expected : float + The expected value. + observed : float + The observed value. + error : float + The error statistic.""" self.chisq = np.sum((expected - observed) ** 2 / error**2) return self.chisq From e7e53cb56ebb757bc3e88334055f8b0b048afb8f Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:27:31 +0800 Subject: [PATCH 55/65] numpydoc build for polynomial.py (#97) --- src/diffpy/srmise/baselines/polynomial.py | 98 ++++++++++++++++------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/src/diffpy/srmise/baselines/polynomial.py b/src/diffpy/srmise/baselines/polynomial.py index bae1b6f..0f4f877 100644 --- a/src/diffpy/srmise/baselines/polynomial.py +++ b/src/diffpy/srmise/baselines/polynomial.py @@ -29,10 +29,13 @@ def __init__(self, degree, Cache=None): """Initialize a polynomial function of degree d. Parameters - degree: The degree of the polynomial. Any negative value is interpreted - as the polynomial of negative infinite degree. - Cache: A class (not instance) which implements caching of BaseFunction - evaluations. + ---------- + degree: int + The degree of the polynomial. Any negative value is interpreted + as the polynomial of negative infinite degree. + Cache: class + The class (not instance) which implements caching of BaseFunction + evaluations. """ # Guarantee valid degree try: @@ -62,13 +65,20 @@ def estimate_parameters(self, r, y): y=baseline+signal, where signal is primarily positive. Parameters - r: (Numpy array) Data along r from which to estimate - y: (Numpy array) Data along y from which to estimate - - Returns Numpy array of parameters in the default internal format. - Raises NotImplementedError if estimation is not implemented for this - degree, or SrMiseEstimationError if parameters cannot be estimated for - any other reason.""" + ---------- + r : array-like + The data along r from which to estimate + y : array-like + The data along y from which to estimate + + Returns + ------- + array-like + The Numpy array of parameters in the default internal format. + Raises NotImplementedError if estimation is not implemented for this + degree, or SrMiseEstimationError if parameters cannot be estimated for + any other reason. + """ if self.degree > 1: emsg = "Polynomial implements estimation for baselines of degree <= 1 only." raise NotImplementedError(emsg) @@ -105,14 +115,23 @@ def _jacobianraw(self, pars, r, free): """Return the Jacobian of a polynomial. Parameters - pars: Sequence of parameters for a polynomial of degree d - pars[0] = a_degree - pars[1] = a_(degree-1) - ... - pars[d] = a_0 - r: sequence or scalar over which pars is evaluated - free: sequence of booleans which determines which derivatives are - needed. True for evaluation, False for no evaluation. + ---------- + pars : array-like + The sequence of parameters for a polynomial of degree d + pars[0] = a_degree + pars[1] = a_(degree-1) + ... + pars[d] = a_0 + r : array-like + The sequence or scalar over which pars is evaluated + free : bool + The sequence of booleans which determines which derivatives are + needed. True for evaluation, False for no evaluation. + + Returns + ------- + jacobian: array-like + The Jacobian of polynomial with degree d """ if len(pars) != self.npars: emsg = "Argument pars must have " + str(self.npars) + " elements." @@ -135,12 +154,22 @@ def _transform_parametersraw(self, pars, in_format, out_format): """Convert parameter values from in_format to out_format. Parameters - pars: Sequence of parameters - in_format: A format defined for this class - out_format: A format defined for this class + pars : array-like + The sequence of parameters + in_format : str + The format defined for this class + out_format : str + The format defined for this class Defined Formats - internal: [a_degree, a_(degree-1), ..., a_0]""" + --------------- + internal: [a_degree, a_(degree-1), ..., a_0] + + Returns + ------- + array-like + The transformed parameters in out_format + """ temp = np.array(pars) # Convert to intermediate format "internal" @@ -160,13 +189,22 @@ def _valueraw(self, pars, r): """Return value of polynomial for the given parameters and r values. Parameters - pars: Sequence of parameters for a polynomial of degree d - pars[0] = a_degree - pars[1] = a_(degree-1) - ... - pars[d] = a_0 - If degree is negative infinity, pars is an empty sequence. - r: sequence or scalar over which pars is evaluated""" + ---------- + pars : array-like + The sequence of parameters for a polynomial of degree d + pars[0] = a_degree + pars[1] = a_(degree-1) + ... + pars[d] = a_0 + If degree is negative infinity, pars is an empty sequence. + r : array-like + The sequence or scalar over which pars is evaluated + + Returns + ------- + float + The value of polynomial for the given parameters and r values. + """ if len(pars) != self.npars: emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) From 608a1c186b9bca36667f60a7ea73482998526efa Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:27:41 +0800 Subject: [PATCH 56/65] numpydoc build for fromsequence.py (#99) --- src/diffpy/srmise/baselines/fromsequence.py | 112 ++++++++++++++++---- 1 file changed, 89 insertions(+), 23 deletions(-) diff --git a/src/diffpy/srmise/baselines/fromsequence.py b/src/diffpy/srmise/baselines/fromsequence.py index c0a2d95..1866441 100644 --- a/src/diffpy/srmise/baselines/fromsequence.py +++ b/src/diffpy/srmise/baselines/fromsequence.py @@ -30,21 +30,37 @@ class FromSequence(BaselineFunction): interpolation domain. This baseline function permits no free parameters.""" def __init__(self, *args, **kwds): - """Initialize baseline corresponding to sequences x and y. + """Initialize a baseline object based on input sequences `x` and `y`. - Usage: - FromSequence(xlist, ylist) or - FromSequence(x=xlist, y=ylist) + This class provides two ways to initialize: by directly providing the sequences or by + specifying a file that contains the sequences. - FromSequence("filename") or - FromSequence(file="filename") + Parameters + ---------- + *args : tuple + The variable length argument list. Can be used to pass `x` and `y` sequences directly. + + **kwds : dict + The arbitrary keyword arguments. Can be used to specify `x`, `y` sequences or a `file` name. + + x : array_like, optional + The sequence of x-values defining the baseline. Can be passed as a positional argument or via keyword. + + y : array_like, optional + The sequence of y-values defining the baseline. Must be provided alongside `x`. + file : str, optional + The name of the file containing two columns: one for x-values and one for y-values. - Parameters/Keywords - x: Sequence of x values defining baseline. - y: Sequence of y values defining baseline. - or - file: Name of file with column of x values and column of y values. + Usage + ----- + 1. Directly with sequences: + - `FromSequence(xlist, ylist)` + - `FromSequence(x=xlist, y=ylist)` + + 2. From a file: + - `FromSequence("filename")` + - `FromSequence(file="filename")` """ if len(args) == 1 and len(kwds) == 0: # load from file @@ -88,8 +104,17 @@ def estimate_parameters(self, r, y): to estimate. Parameters - r: (Numpy array) Data along r from which to estimate, Ignored - y: (Numpy array) Data along y from which to estimate, Ignored""" + ---------- + r : array-like + The data along r from which to estimate, Ignored + y : array-like + The data along y from which to estimate, Ignored + + Returns + ------- + array-like + The empty numpy array + """ return np.array([]) def _jacobianraw(self, pars, r, free): @@ -98,9 +123,19 @@ def _jacobianraw(self, pars, r, free): A FromSequence baseline has no parameters. Parameters - pars: Empty sequence - r: sequence or scalar over which pars is evaluated - free: Empty sequence.""" + ---------- + pars :array-like + The empty sequence + r : array-like + The sequence or scalar over which pars is evaluated + free : array-like + The empty sequence. + + Returns + ------- + array-like + The empty numpy array + """ if len(pars) != self.npars: emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) @@ -113,12 +148,23 @@ def _transform_parametersraw(self, pars, in_format, out_format): """Convert parameter values from in_format to out_format. Parameters - pars: Sequence of parameters - in_format: A format defined for this class - out_format: A format defined for this class + ---------- + pars : array-like + The sequence of parameters + in_format : str + The format defined for this class + out_format : str + The format defined for this class Defined Formats - n/a, FromSequence has no parameters""" + --------------- + n/a, FromSequence has no parameters + + Returns + ------- + array-like + The sequence of parameters converted to out_format + """ temp = np.array(pars) # Convert to intermediate format "internal" @@ -138,8 +184,17 @@ def _valueraw(self, pars, r): """Return value of polynomial for the given parameters and r values. Parameters - pars: Empty sequence - r: sequence or scalar over which pars is evaluated""" + ---------- + pars : array-like + The empty sequence + r : array-like + The sequence or scalar over which pars is evaluated + + Returns + ------- + float + The value of the polynomial for the given parameters + """ if len(pars) != self.npars: emsg = "Argument pars must have " + str(self.npars) + " elements." raise ValueError(emsg) @@ -163,7 +218,18 @@ def getmodule(self): return __name__ def xyrepr(self, var): - """Safe string output of x and y, compatible with eval()""" + """Safe string output of x and y, compatible with eval() + + Parameters + ---------- + var : array-like + The sequence or scalar over which to evaluate + + Returns + ------- + str + The x and y values of the given variable + """ return "[%s]" % ", ".join([repr(v) for v in var]) def readxy(self, filename): From bb716711cb6105e562c642ed6c29fb20a73dd2ab Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:27:57 +0800 Subject: [PATCH 57/65] numpydoc build for nanospherical.py (#98) --- src/diffpy/srmise/baselines/nanospherical.py | 122 ++++++++++++++----- 1 file changed, 94 insertions(+), 28 deletions(-) diff --git a/src/diffpy/srmise/baselines/nanospherical.py b/src/diffpy/srmise/baselines/nanospherical.py index 8c5ae12..a76719f 100644 --- a/src/diffpy/srmise/baselines/nanospherical.py +++ b/src/diffpy/srmise/baselines/nanospherical.py @@ -42,8 +42,10 @@ def __init__(self, Cache=None): """Initialize a spherical nanoparticle baseline. Parameters - Cache - A class (not instance) which implements caching of BaseFunction - evaluations. + ---------- + Cache : class + THe class (not instance) which implements caching of BaseFunction + evaluations. """ # Define parameterdict parameterdict = {"scale": 0, "radius": 1} @@ -74,12 +76,21 @@ def _jacobianraw(self, pars, r, free): """Return the Jacobian of the spherical baseline. Parameters - pars - Sequence of parameters for a spherical baseline - pars[0] = scale - pars[1] = radius - r - sequence or scalar over which pars is evaluated. - free - sequence of booleans which determines which derivatives are - needed. True for evaluation, False for no evaluation. + ---------- + pars : array-like + The Sequence of parameters for a spherical baseline + pars[0] = scale + pars[1] = radius + r : array-like + The sequence or scalar over which pars is evaluated. + free : bool + The sequence of booleans which determines which derivatives are + needed. True for evaluation, False for no evaluation. + + Returns + ------- + array-like + The Jacobian of the nanospherical baseline. """ if len(pars) != self.npars: emsg = "Argument pars must have " + str(self.npars) + " elements." @@ -116,10 +127,18 @@ def _jacobianrawscale(self, pars, r): """Return partial Jacobian wrt scale without bounds checking. Parameters - pars - Sequence of parameters for a spherical baseline - pars[0] = scale - pars[1] = radius - r - sequence or scalar over which pars is evaluated. + ---------- + pars : array-like + The sequence of parameters for a spherical baseline + pars[0] = scale + pars[1] = radius + r : array-like + The sequence or scalar over which pars is evaluated. + + Returns + ------- + array-like + The partial Jacobian of the nanoparticle baseline wrt scale without bounds checking. """ np.abs(pars[0]) R = np.abs(pars[1]) @@ -134,10 +153,18 @@ def _jacobianrawradius(self, pars, r): """Return partial Jacobian wrt radius without bounds checking. Parameters - pars - Sequence of parameters for a spherical baseline - pars[0] = scale - pars[1] = radius - r - sequence or scalar over which pars is evaluated. + ---------- + pars : array-like + The Sequence of parameters for a spherical baseline + pars[0] = scale + pars[1] = radius + r : array-like + The sequence or scalar over which pars is evaluated. + + Returns + ------- + array-like + The partial Jacobian of the nanoparticle baseline wrt radius without bounds checking. """ s = np.abs(pars[0]) R = np.abs(pars[1]) @@ -150,12 +177,22 @@ def _transform_parametersraw(self, pars, in_format, out_format): """Convert parameter values from in_format to out_format. Parameters - pars - Sequence of parameters - in_format - A format defined for this class - out_format - A format defined for this class + ---------- + pars : array-like + The sequence of parameters + in_format : str + The format defined for this class + out_format : str + The format defined for this class Defined Formats + --------------- internal - [scale, radius] + + Returns + ------- + array-like + The transformed parameter values with out_format. """ temp = np.array(pars) @@ -180,10 +217,18 @@ def _valueraw(self, pars, r): Outside the interval [0, radius] the baseline is 0. Parameters - pars - Sequence of parameters for a spherical baseline - pars[0] = scale - pars[1] = radius - r - sequence or scalar over which pars is evaluated. + ---------- + pars : array-like + The sequence of parameters for a spherical baseline + pars[0] = scale + pars[1] = radius + r : array-like + The sequence or scalar over which pars is evaluated. + + Returns + ------- + float + The value of the spherical baseline. """ if len(pars) != self.npars: emsg = "Argument pars must have " + str(self.npars) + " elements." @@ -203,10 +248,18 @@ def _valueraw2(self, pars, r): """Return value of spherical baseline without bounds checking for given parameters and r values. Parameters - pars - Sequence of parameters for a spherical baseline - pars[0] = scale - pars[1] = radius - r - sequence or scalar over which pars is evaluated. + ---------- + pars : array-like + The sequence of parameters for a spherical baseline + pars[0] = scale + pars[1] = radius + r : array-like + The sequence or scalar over which pars is evaluated. + + Returns + ------- + float + The value of spherical baseline without bounds checking for given parameters and r values """ s = np.abs(pars[0]) R = np.abs(pars[1]) @@ -214,7 +267,20 @@ def _valueraw2(self, pars, r): return -s * r * (1 - (3.0 / 4.0) * rdivR + (1.0 / 16.0) * rdivR**3) def _getdomain(self, pars, r): - """Return slice object for which r > 0 and r < twice the radius""" + """Return slice object for which r > 0 and r < twice the radius + + Parameters + ---------- + pars : array-like + The sequence of parameters for a spherical baseline + r : array-like + The sequence or scalar over which pars is evaluated. + + Returns + ------- + slice object + The slice object for which r > 0 and r < twice the radius + """ low = r.searchsorted(0.0, side="right") high = r.searchsorted(2.0 * pars[1], side="left") return slice(low, high) From f7cc0150c8e2e1f7ea44c476c93258398cb08234 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:28:11 +0800 Subject: [PATCH 58/65] numpydoc build for base.py in Baseline class (#96) * numpydoc build for base.py * [pre-commit.ci] auto fixes from pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/diffpy/srmise/baselines/base.py | 103 +++++++++++++++++----------- 1 file changed, 63 insertions(+), 40 deletions(-) diff --git a/src/diffpy/srmise/baselines/base.py b/src/diffpy/srmise/baselines/base.py index 23e242d..c1ecf0e 100644 --- a/src/diffpy/srmise/baselines/base.py +++ b/src/diffpy/srmise/baselines/base.py @@ -28,16 +28,19 @@ class BaselineFunction(BaseFunction): Class members ------------- - parameterdict: A dictionary mapping string keys to their index in the - sequence of parameters. These keys apply only to - the default "internal" format. - parformats: A sequence of strings defining what formats are recognized - by a baseline function. - default_formats: A dictionary which maps the strings "default_input" and - "default_output" to strings also appearing in parformats. - "default_input"-> format used internally within the class - "default_output"-> Default format to use when converting - parameters for outside use. + parameterdict: dict + The dictionary mapping string keys to their index in the + sequence of parameters. These keys apply only to + the default "internal" format. + parformats: array-like + The sequence of strings defining what formats are recognized + by a baseline function. + default_formats: dict + The dictionary which maps the strings "default_input" and + "default_output" to strings also appearing in parformats. + "default_input"-> format used internally within the class + "default_output"-> Default format to use when converting + parameters for outside use. Class methods (implemented by inheriting classes) ------------------------------------------------- @@ -70,19 +73,25 @@ def __init__( ): """Set parameterdict defined by subclass - parameterdict: A dictionary mapping string keys to their index in a - sequence of parameters for this BaselineFunction subclass. - parformats: A sequence strings containing all allowed input/output - formats defined for the peak function's parameters. - default_formats: A dictionary mapping the string keys "internal" and - "default_output" to formats from parformats. - metadict: Dictionary mapping string keys to tuple (v, m) where v is an - additional argument required by function, and m is a method - whose string output recreates v when passed to eval(). - base: A basefunction subclass instance which this one decorates with - additional functionality. - Cache: A class (not instance) which implements caching of BaseFunction - evaluations.""" + parameterdict : dict + The dictionary mapping string keys to their index in a + sequence of parameters for this BaselineFunction subclass. + parformats : array-like + The sequence strings containing all allowed input/output + formats defined for the peak function's parameters. + default_formats : dict + The dictionary mapping the string keys "internal" and + default_output" to formats from parformats. + metadict: dict + The dictionary mapping string keys to tuple (v, m) where v is an + additional argument required by function, and m is a method + whose string output recreates v when passed to eval(). + base : The basefunction subclass + The basefunction subclass instance which this one decorates with + additional functionality. + Cache : class + The class (not instance) which implements caching of BaseFunction + evaluations.""" BaseFunction.__init__(self, parameterdict, parformats, default_formats, metadict, base, Cache) # "Virtual" class methods #### @@ -111,20 +120,31 @@ class Baseline(ModelPart): """Represents a baseline associated with a BaselineFunction subclass.""" def __init__(self, owner, pars, free=None, removable=False, static_owner=False): - """Set instance members. - - owner: an instance of a BaselineFunction subclass - pars: Sequence of parameters which define the baseline - free: Sequence of Boolean variables. If False, the corresponding - parameter will not be changed. - removable: (False) Boolean determines whether the baseline can be removed. - static_owner: (False) Whether or not the owner can be changed with - changeowner() - - Note that free and removable are not mutually exclusive. If any - values are not free but removable=True then the entire baseline may be - may be removed during peak extraction, but the held parameters for the - baseline will remain unchanged until that point. + """Initialize the BaselineComponent instance with specified configurations. + + Parameters + ---------- + owner : BaselineFunction subclass instance + The owner object which is an instance of a subclass of BaselineFunction. + pars : array-like + The sequence of parameters defining the characteristics of the baseline. + free : Sequence of bool, optional + The sequence parallel to `pars` where each boolean value indicates whether + the corresponding parameter is adjustable. If False, that parameter is fixed. + Defaults to None, implying all parameters are free by default. + removable : bool, optional + A flag indicating whether the baseline can be removed during processing. + Defaults to False. + static_owner : bool, optional + Determines if the owner of the baseline can be altered using the + ` changeowner()` method. Defaults to False. + + Notes + ----- + - The `free` and `removable` parameters are independent; a baseline can be marked + as removable even if some of its parameters are fixed (`free` is False). In such + cases, the baseline may be removed during peak extraction, but the fixed + parameters will persist until removal. """ ModelPart.__init__(self, owner, pars, free, removable, static_owner) @@ -132,9 +152,12 @@ def __init__(self, owner, pars, free=None, removable=False, static_owner=False): def factory(baselinestr, ownerlist): """Instantiate a Peak from a string. - Parameters: - baselinestr: string representing Baseline - ownerlist: List of BaseFunctions that owner is in + Parameters + ---------- + baselinestr : str + The string representing Baseline + ownerlist : array-like + The list of BaseFunctions that owner is in """ data = baselinestr.strip().splitlines() From 2b8c4469d4d98ec34b219bea6c9b508404706f4e Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:28:24 +0800 Subject: [PATCH 59/65] numpydoc build for aic.py (#93) --- src/diffpy/srmise/modelevaluators/aic.py | 87 +++++++++++++++++++++--- 1 file changed, 77 insertions(+), 10 deletions(-) diff --git a/src/diffpy/srmise/modelevaluators/aic.py b/src/diffpy/srmise/modelevaluators/aic.py index 8841111..1a1973b 100644 --- a/src/diffpy/srmise/modelevaluators/aic.py +++ b/src/diffpy/srmise/modelevaluators/aic.py @@ -54,10 +54,19 @@ def evaluate(self, fit, count_fixed=False, kshift=0): """Return quality of fit for given ModelCluster using AIC (Akaike's Information Criterion). Parameters - fit: A ModelCluster - count_fixed: Whether fixed parameters are considered. - kshift: (0) Treat the model has having this many additional - parameters. Negative values also allowed.""" + ---------- + fit : ModelCluster instance + The ModelCluster instance to evaluate. + count_fixed : bool + Whether fixed parameters are considered. Default is False. + kshift : int + Treat the model has having this many additional + parameters. Negative values also allowed. Default is 0. + + Returns + ------- + quality : float + The quality of fit for given ModelCluster.""" # Number of parameters. By default, fixed parameters are ignored. k = fit.model.npars(count_fixed=count_fixed) + kshift if k < 0: @@ -79,12 +88,34 @@ def evaluate(self, fit, count_fixed=False, kshift=0): return self.stat def minpoints(self, npars): - """Calculates the minimum number of points required to make an estimate of a model's quality.""" + """Calculates the minimum number of points required to make an estimate of a model's quality. + + Parameters + ---------- + npars : int + The number of parameters in the model. + + Returns + ------- + int + The minimum number of points required to make an estimate of a model's quality. + """ return 1 - def parpenalty(self, k, n): - """Returns the cost for adding k parameters to the current model cluster.""" + def parpenalty(self, k): + """Returns the cost for adding k parameters to the current model cluster. + + Parameters + ---------- + k : int + The number of added parameters in the model. + + Returns + ------- + float + The penalty cost for adding k parameters to the current model cluster. + """ # Weight the penalty for additional parameters. # If this isn't 1 there had better be a good reason. @@ -94,11 +125,26 @@ def parpenalty(self, k, n): def growth_justified(self, fit, k_prime): """Returns whether adding k_prime parameters to the given model (ModelCluster) is justified - given the current quality of the fit. The assumption is that adding k_prime parameters will + given the current quality of the fit. + + The assumption is that adding k_prime parameters will result in "effectively 0" chiSquared cost, and so adding it is justified if the cost of adding these parameters is less than the current chiSquared cost. The validity of this assumption (which depends on an unknown chiSquared value) and the impact of the errors used should be examined more thoroughly in the future. + + Parameters + ---------- + fit : ModelCluster instance + The ModelCluster instance to evaluate. + + k_prime : int + The prime number of added parameters in the model. + + Returns + ------- + bool + Whether adding k_prime parameters to the given model is justified. """ if self.chisq is None: @@ -124,7 +170,18 @@ def growth_justified(self, fit, k_prime): @staticmethod def akaikeweights(aics): - """Return sequence of Akaike weights for sequence of AICs""" + """Return sequence of Akaike weights for sequence of AICs + + Parameters + ---------- + aics : array-like + The sequence of AIC instance. + + Returns + ------- + array-like + The sequence of Akaike weights + """ aic_stats = np.array([aic.stat for aic in aics]) aic_min = min(aic_stats) @@ -132,7 +189,17 @@ def akaikeweights(aics): @staticmethod def akaikeprobs(aics): - """Return sequence of Akaike probabilities for sequence of AICs""" + """Return sequence of Akaike probabilities for sequence of AICs + + Parameters + ---------- + aics : array-like + The sequence of AIC instance. + + Returns + ------- + array-like + The sequence of Akaike probabilities""" aic_weights = AIC.akaikeweights(aics) return aic_weights / np.sum(aic_weights) From caab0af47dc8f148bbfe515e3c7f31c02b47fc81 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:29:24 +0800 Subject: [PATCH 60/65] numpydoc build for aicc.py (#94) --- src/diffpy/srmise/modelevaluators/aicc.py | 80 +++++++++++++++++++++-- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/src/diffpy/srmise/modelevaluators/aicc.py b/src/diffpy/srmise/modelevaluators/aicc.py index da0a083..68dd9a8 100644 --- a/src/diffpy/srmise/modelevaluators/aicc.py +++ b/src/diffpy/srmise/modelevaluators/aicc.py @@ -55,9 +55,17 @@ def evaluate(self, fit, count_fixed=False, kshift=0): Parameters fit: A ModelCluster - count_fixed: Whether fixed parameters are considered. - kshift: (0) Treat the model has having this many additional - parameters. Negative values also allowed.""" + The ModelCluster to evaluate. + count_fixed : bool + Whether fixed parameters are considered. Default is False. + kshift : int + Treat the model has having this many additional + parameters. Negative values also allowed. Default is 0. + + Returns + ------- + float + Quality of AICc""" # Number of parameters. By default, fixed parameters are ignored. k = fit.model.npars(count_fixed=count_fixed) + kshift if k < 0: @@ -79,14 +87,39 @@ def evaluate(self, fit, count_fixed=False, kshift=0): return self.stat def minpoints(self, npars): - """Calculates the minimum number of points required to make an estimate of a model's quality.""" + """Calculates the minimum number of points required to make an estimate of a model's quality. + + Parameters + ---------- + npars : int + The number of points required to make an estimate of a model's quality. + + Returns + ------- + int + The minimum number of points required to make an estimate of a model's quality. + """ # From the denominator of AICc, it is clear that the first positive finite contribution to # parameter cost is at n>=k+2 return npars + 2 def parpenalty(self, k, n): - """Returns the cost for adding k parameters to the current model cluster.""" + """Returns the cost for adding k parameters to the current model cluster. + + Parameters + ---------- + k : int + The number of parameters to add. + + n : int + The number of data points. + + Returns + ------- + float + The cost for adding k parameters to the current model cluster. + """ # Weight the penalty for additional parameters. # If this isn't 1 there had better be a good reason. @@ -101,6 +134,18 @@ def growth_justified(self, fit, k_prime): and so adding it is justified if the cost of adding these parameters is less than the current chiSquared cost. The validity of this assumption (which depends on an unknown chiSquared value) and the impact of the errors used should be examined more thoroughly in the future. + + Parameters + ---------- + fit : ModelCluster + The ModelCluster to evaluate. + k_prime : int + The prime number of parameters to add. + + Returns + ------- + bool + Whether the current model cluster is justified or not. """ if self.chisq is None: @@ -126,7 +171,18 @@ def growth_justified(self, fit, k_prime): @staticmethod def akaikeweights(aics): - """Return sequence of Akaike weights for sequence of AICs""" + """Return sequence of Akaike weights for sequence of AICs + + Parameters + ---------- + aics : array-like + The squence of AIC instances + + Returns + ------- + array-like + The sequence of Akaike weights + """ aic_stats = np.array([aic.stat for aic in aics]) aic_min = min(aic_stats) @@ -134,7 +190,17 @@ def akaikeweights(aics): @staticmethod def akaikeprobs(aics): - """Return sequence of Akaike probabilities for sequence of AICs""" + """Return sequence of Akaike probabilities for sequence of AICs + + Parameters + ---------- + aics : array-like + The squence of AIC instances + + Returns + ------- + array-like + The sequence of Akaike probabilities""" aic_weights = AICc.akaikeweights(aics) return aic_weights / np.sum(aic_weights) From 16039d6cd251f471ac5792fb3e14e79987b893b0 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:30:25 +0800 Subject: [PATCH 61/65] numpydoc build for ModelCovariance (#84) * numpydoc build for ModelCovariance * update format type and fix indentation issue --- src/diffpy/srmise/modelcluster.py | 77 ++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/src/diffpy/srmise/modelcluster.py b/src/diffpy/srmise/modelcluster.py index a65ffb1..636e7e8 100644 --- a/src/diffpy/srmise/modelcluster.py +++ b/src/diffpy/srmise/modelcluster.py @@ -82,10 +82,12 @@ def setcovariance(self, model, cov): Parameters ---------- - model - A ModelParts object - cov - The nxn covariance matrix for n model parameters. If the parameterization includes "fixed" - parameters not included in the covariance matrix, the matrix is expanded to include these - parameters with 0 uncertainty. + model : ModelParts + The ModelParts instance + cov : ndarray + The nxn covariance matrix for n model parameters. If the parameterization includes "fixed" + parameters not included in the covariance matrix, the matrix is expanded to include these + parameters with 0 uncertainty. """ tempcov = np.array(cov) @@ -151,8 +153,10 @@ def transform(self, in_format, out_format, **kwds): Parameters ---------- - in_format - The current format of parameters - out_format - The new format for parameters + in_format : str + The current format of parameters + out_format : str + The new format for parameters Keywords -------- @@ -229,6 +233,18 @@ def getcorrelation(self, i, j): The standard deviation of fixed parameters is 0, in which case the correlation is undefined, but return 0 for simplicity. + + Parameters + ---------- + i : int + The index of variable in peak mapping + j : int + The index of variable in peak mapping + + Returns + ------- + float + The correlation between variables i and j """ if self.cov is None: emsg = "Cannot get correlation on undefined covariance matrix." @@ -257,6 +273,16 @@ def getuncertainty(self, i): The variable may be specified as an integer, or as a two-component tuple of integers (l, m) which indicate the mth parameter of modelpart l. + + Parameters + ---------- + i : int + The index of variable in peak mapping + + Returns + ------- + float + The uncertainty of variable at index i. """ (l, m) = i if i in self.pmap else self.ipmap[i] return np.sqrt(self.getcovariance(i, i)) @@ -266,6 +292,18 @@ def getcovariance(self, i, j): The variables may be specified as integers, or as a two-component tuple of integers (l, m) which indicate the mth parameter of modelpart l. + + Parameters + ---------- + i : int + The index of variable in peak mapping + j : int + The index of variable in peak mapping + + Returns + ------- + float + The covariance between variables at indeex i and j. """ if self.cov is None: emsg = "Cannot get correlation on undefined covariance matrix." @@ -282,6 +320,16 @@ def get(self, i): The variable may be specified as an integer, or as a two-component tuple of integers (l, m) which indicate the mth parameter of modelpart l. + + Parameters + ---------- + i : int + The index of variable in peak mapping + + Returns + ------- + (float, float) + The value and uncertainty of variable at index i. """ return (self.getvalue(i), self.getuncertainty(i)) @@ -294,8 +342,13 @@ def correlationwarning(self, threshold=0.8): Parameters ---------- - threshold - A real number between 0 and 1. + threshold : float + A real number between 0 and 1. + Returns + ------- + tuple (i, j, c) + Indices of the modelpart and their correlations. """ if self.cov is None: emsg = "Cannot calculate correlation on undefined covariance matrix." @@ -323,6 +376,16 @@ def prettypar(self, i): The variable may be specified as an integer, or as a two-component tuple of integers (l, m) which indicate the mth parameter of modelpart l. + + Parameters + ---------- + i : int + The index of variable in peak mapping + + Returns + ------- + str + 'value (uncertainty)' for variable at index i. """ if self.model is None or self.cov is None: return "Model and/or Covariance matrix undefined." From 38ab653e7271ffe87b3db2a4ce386e3b2f633ca0 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Sat, 24 Aug 2024 23:27:42 +0800 Subject: [PATCH 62/65] numpydoc build for modelparts.py (#100) * numpydoc build for modelparts.py * [pre-commit.ci] auto fixes from pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/diffpy/srmise/modelparts.py | 353 ++++++++++++++++++++++++-------- 1 file changed, 266 insertions(+), 87 deletions(-) diff --git a/src/diffpy/srmise/modelparts.py b/src/diffpy/srmise/modelparts.py index f8f0fbc..43a2141 100644 --- a/src/diffpy/srmise/modelparts.py +++ b/src/diffpy/srmise/modelparts.py @@ -73,14 +73,26 @@ def fit( Fitting is performed with the MINPACK leastsq() routine exposed by scipy. Parameters - r - Sequence of r values over which to fit - y - Sequence of y values over which to fit - y_error - Sequence of uncertainties in y - range - Slice object specifying region of r and y over which to fit. - Fits over all the data by default. - ntrials - The maximum number of function evaluations while fitting. - cov - Optional ModelCovariance object preserves covariance information. - cov_format - Parameterization to use in cov. + ---------- + r : array-like + The sequence of r values over which to fit + y : array-like + The sequence of y values over which to fit + y_error : array-like + The sequence of uncertainties in y + range : slice object + The slice object specifying region of r and y over which to fit. + Fits over all the data by default. + ntrials : int + The maximum number of function evaluations while fitting. + cov : ModelCovariance instance + The Optional ModelCovariance object preserves covariance information. + cov_format : str + The parameterization to use in cov. + + Returns + ------- + None """ freepars = self.unpack_freepars() if len(freepars) >= len(r): @@ -226,8 +238,15 @@ def npars(self, count_fixed=True): """Return total number of parameters in all parts. Parameters - count_fixed - Boolean which determines if fixed parameters are - are included in the count. + ---------- + count_fixed : bool + The boolean which determines if fixed parameters are + included in the count. + + Returns + ------- + n : int + The total number of parameters. """ n = 0 for p in self: @@ -235,7 +254,17 @@ def npars(self, count_fixed=True): return n def pack_freepars(self, freepars): - """Update parameters with values from sequence of freepars.""" + """Update parameters with values from sequence of freepars. + + Parameters + ---------- + freepars : array-like + The sequence of free parameters. + + Returns + ------- + None + """ if np.isnan(freepars).any(): emsg = "Non-numeric free parameters." raise ValueError(emsg) @@ -247,12 +276,23 @@ def residual(self, freepars, r, y_expected, y_error, range=None): """Calculate residual of all parameters. Parameters - freepars - sequence of free parameters - r - the input domain - y_expected - sequence of expected values - y_error - sequence of uncertainties in y-variable - range - Slice object specifying region of r and y over which to fit. - All the data by default. + ---------- + freepars : array-like + The sequence of free parameters + r : array-like + The input domain + y_expected : array-like + The sequence of expected values + y_error : array-like + The sequence of uncertainties in y-variable + range : slice object + The slice object specifying region of r and y over which to fit. + All the data by default. + + Returns + ------- + array-like + The residual of all parameters. """ self.pack_freepars(freepars) total = self.value(r, range) @@ -267,12 +307,22 @@ def residual_jacobian(self, freepars, r, y_expected, y_error, range=None): """Calculate the Jacobian of freepars. Parameters - freepars - sequence of free parameters - r - the input domain - y_expected - sequence of expected values - y_error - sequence of uncertainties in y-variable - range - Slice object specifying region of r and y over which to fit. - All the data by default. + freepars : array-like + The sequence of free parameters + r : array-like + The input domain + y_expected : array-like + The sequence of expected values + y_error : array-like + The sequence of uncertainties in y-variable + range : slice object + The slice object specifying region of r and y over which to fit. + All the data by default. + + Returns + ------- + ndarray + The Jacobian of all parameters. """ if len(freepars) == 0: raise ValueError( @@ -297,9 +347,17 @@ def value(self, r, range=None): """Calculate total value of all parts over range. Parameters - r - the input domain - range - Slice object specifying region of r and y over which to fit. - All the data by default. + ---------- + r : array-like + The input domain + range : slice object + The slice object specifying region of r and y over which to fit. + All the data by default. + + Returns + ------- + total : float + The total value of all slice region of r. """ total = r * 0.0 for p in self: @@ -319,13 +377,23 @@ def covariance(self, format="internal", **kwds): defined by the formats for each individual ModelPart. Parameters - format - The format ("internal" by default) to use for all ModelParts. - This may be overridden for specific peaks as shown below. + ---------- + format : str + The format ("internal" by default) to use for all ModelParts. + This may be overridden for specific peaks as shown below. Keywords - f0 - The format of the 0th ModelPart - f1 - The format of the 1st ModelPart + -------- + f0 : str + The format of the 0th ModelPart + f1 : str + The format of the 1st ModelPart etc. + + Returns + ------- + cov : ndarray + The estimated covariance matrix. """ formats = [format for p in self] @@ -344,23 +412,35 @@ def copy(self): """Return deep copy of this ModelParts. The original and the copy are completely independent, except each - ModelPart and its copy still reference the same owner.""" + ModelPart and its copy still reference the same owner. + + Returns + ------- + ModelParts + The deep copy of this ModelParts. + """ return type(self).__call__([p.copy() for p in self]) def __str__(self): """Return string representation of this ModelParts.""" return "".join([str(p) + "\n" for p in self]) - def __getslice__(self, i, j): - """Extends list.__getslice__""" - return self.__class__(list.__getitem__(self, i, j)) + def __getitem__(self, index): + """Extends list.__getitem__""" + if isinstance(index, tuple) and len(index) == 2: + start, end = index + return self.__class__(super().__getitem__(slice(start, end))) + else: + return super().__getitem__(index) def transform(self, in_format="internal", out_format="internal"): """Transforms format of parameters in this modelpart. Parameters - in_format - The format the parameters are already in. - out_format - The format the parameters are transformed to. + in_format : str + The format the parameters are already in. + out_format : str + The format the parameters are transformed to. """ for p in self: try: @@ -378,41 +458,61 @@ def transform(self, in_format="internal", out_format="internal"): class ModelPart(object): """Represents a single part (instance of some function) of a model. - Members + Attributes ------- - pars - Array containing the parameters of this model part - free - Array containing boolean values defining whether the corresponding parameter - is free or not. - removable - Boolean determining whether or not this model part can be - removed during extraction. - static_owner - Boolean determines if owner can be changed with changeowner() + pars : array-like + The array containing the parameters of this model part + free : array-like + The array containing boolean values defining whether the corresponding parameter + is free or not. + removable : bool + The boolean determining whether or not this model part can be + removed during extraction. + static_owner : bool + The boolean determines if owner can be changed with changeowner() Methods ------- - changeowner - Change the owner of self - copy - Return deep copy of self - compress - Return parameters with non-free parameters removed - jacobian - Return jacobian - getfree - Return free parameter by index or keyword define by owner - npars - Return number of parameters in self - owner - Return self.owner - setfree - Set a free parameter by index or keyword defined by owner - update - Update free parameters with values in given sequence - value - Return value - writestr - Return string representation of self + changeowner(new_owner) + Change the owner of the model part instance. + copy() + Return a deep copy of the model part instance. + compress() + Return parameters with non-free parameters removed. + jacobian() + Compute and return the Jacobian matrix for the model part. + getfree(index=None, keyword=None) + Retrieve a free parameter by index or keyword defined by the owner. + npars() + Return the number of parameters in this model part. + owner() + Return the current owner of the model part. + setfree(index=None, value=None, keyword=None, new_value=None) + Set a free parameter by index or keyword defined by the owner. + update(values) + Update free parameters with values from a given sequence. + value() + Compute and return the value of the model part based on its parameters. + writestr() + Generate and return a string representation of the model part. """ def __init__(self, owner, pars, free=None, removable=True, static_owner=False): - """Set instance members. + """Constructor for instance members. Parameters - owner - an instance of a BaseFunction subclass - pars - Sequence of parameters which specify the function explicitly - free - Sequence of Boolean variables. If False, the corresponding - parameter will not be changed. - removable - Boolean determines whether this part can be removed. - static_owner - Whether or not the part can be changed with - changeowner() + owner : BaseFunction subclass + The instance of a BaseFunction subclass + pars : array-like + The sequence of parameters which specify the function explicitly + free : array-like + The sequence of Boolean variables. If False, the corresponding + parameter will not be changed. + removable : bool + The boolean determines whether this part can be removed. + static_owner : bool + Whether or not the part can be changed with + changeowner() Note that free and removable are not mutually exclusive. If any pars are not free but removable=True then the part may be removed, but @@ -447,7 +547,13 @@ def changeowner(self, owner): owner, or if the number of parameters is incompatible. Parameters - owner - an instance of a BaseFunction subclass + ---------- + owner : BaseFunction subclass + The instance of a BaseFunction subclass + + Returns + ------- + None """ if self.static_owner and self._owner is not owner: emsg = "Cannot change owner if static_owner is True." @@ -458,31 +564,54 @@ def changeowner(self, owner): self._owner = owner def compress(self): - """Return part parameters with non-free values removed.""" + """Return part parameters with non-free values removed. + + Returns + ------- + pars : array-like + The compressed parameters of the model part.""" return self.pars[self.free] def jacobian(self, r, range=None): """Return jacobian of this part over r. Parameters - r - the input domain - range - Slice object specifying region of r and y over which to fit. - All the data by default. + ---------- + r : array-like + The input domain + range : slice object + The slice object specifying region of r and y over which to fit. + All the data by default. + + Returns + ------- + jacobian : array-like + The jacobian of the model part. """ return self._owner.jacobian(self, r, range) def owner(self): - """Return the BaseFunction subclass instance which owns this part.""" + """Return the BaseFunction subclass instance which owns this part. + + Returns + ------- + BaseFunction subclass + The BaseFunction subclass which owns this part.""" return self._owner def update(self, freepars): """Sequentially update free parameters from freepars. Parameters - freepars - sequence of new parameter values. May contain more - parameters than can actually be updated. - - Return number of parameters updated from freepars. + ---------- + freepars : array-like + The sequence of new parameter values. May contain more + parameters than can actually be updated. + + Returns + ------- + numfree + number of parameters updated from freepars. """ numfree = self.npars(count_fixed=False) if len(freepars) < numfree: @@ -497,9 +626,17 @@ def value(self, r, range=None): """Return value of peak over r. Parameters - r - the input domain - range - Slice object specifying region of r and y over which to fit. - All the data by default. + ---------- + r : array-like + The input domain + range : slice object + The slice object specifying region of r and y over which to fit. + All the data by default. + + Returns + ------- + value : array-like + The value of peak over r. """ return self._owner.value(self, r, range) @@ -507,15 +644,28 @@ def copy(self): """Return a deep copy of this ModelPart. The original and the copy are completely independent, except they both - reference the same owner.""" + reference the same owner. + + Returns + ------- + ModelPart + A deep copy of this ModelPart. + """ return type(self).__call__(self._owner, self.pars, self.free, self.removable, self.static_owner) def __getitem__(self, key_or_idx): """Return parameter of peak corresponding with key_or_idx. Parameters - key_or_idx - An integer index, slice, or key from owner's parameter - dictionary. + ---------- + key_or_idx : Optional[int, slice, key] + The integer index, slice, or key from owner's parameter + dictionary. + + Returns + ------- + pars : array-like + The value of the peak corresponding to key_or_idx. """ if key_or_idx in self._owner.parameterdict: return self.pars[self._owner.parameterdict[key_or_idx]] @@ -526,8 +676,16 @@ def getfree(self, key_or_idx): """Return value of free corresponding with key_or_idx. Parameters - key_or_idx - An integer index, slice, or key from owner's parameter - dictionary.""" + ---------- + key_or_idx : Optional[int, slice object, key] + The integer index, slice, or key from owner's parameter + dictionary. + + Returns + ------- + freepars : array-like + The value of the free corresponding to key_or_idx. + """ if key_or_idx in self._owner.parameterdict: return self.free[self._owner.parameterdict[key_or_idx]] else: @@ -537,9 +695,17 @@ def setfree(self, key_or_idx, value): """Set value of free corresponding with key_or_idx. Parameters - key_or_idx - An integer index, slice, or key from owner's parameter - dictionary. - value: A boolean""" + ---------- + key_or_idx : Optional[int, slice object, key] + The integer index, slice, or key from owner's parameter + dictionary. + value : bool + The boolean to set in free corresponding to key_or_idx. + + Returns + ------- + None + """ if key_or_idx in self._owner.parameterdict: self.free[self._owner.parameterdict[key_or_idx]] = value else: @@ -553,8 +719,15 @@ def npars(self, count_fixed=True): """Return total number of parameters in all parts. Parameters - count_fixed - Boolean which determines if fixed parameters are - are included in the count.""" + ---------- + count_fixed : bool + The boolean which determines if fixed parameters are + included in the count. + + Returns + ------- + int + The number of parameters in all parts.""" if count_fixed: return self._owner.npars else: @@ -586,7 +759,14 @@ def writestr(self, ownerlist): The value of owner is determined by its index in ownerlist. Parameters - ownerlist - List of owner functions + ---------- + ownerlist : array-like + The list of owner functions + + Returns + ------- + datastring + The string representation of ModelPart. """ if self._owner not in ownerlist: emsg = "ownerlist does not contain this ModelPart's owner." @@ -607,5 +787,4 @@ def writestr(self, ownerlist): # simple test code if __name__ == "__main__": - pass From e47955fc9d555dc7086681dea201a3b8ee0df9a4 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Sat, 24 Aug 2024 23:28:38 +0800 Subject: [PATCH 63/65] numpydoc build for basefunction.py (#101) --- src/diffpy/srmise/basefunction.py | 179 +++++++++++++++++++++--------- 1 file changed, 124 insertions(+), 55 deletions(-) diff --git a/src/diffpy/srmise/basefunction.py b/src/diffpy/srmise/basefunction.py index b2acc2c..1711138 100644 --- a/src/diffpy/srmise/basefunction.py +++ b/src/diffpy/srmise/basefunction.py @@ -27,23 +27,28 @@ class BaseFunction(object): """Base class for mathematical functions which model numeric sequences. - Class members + Attributes ------------- - parameterdict: A dictionary mapping string keys to their index in the - sequence of parameters. These keys apply only to the - default "internal" format. - parformats: A sequence of strings defining what formats are recognized - by a function. - default_formats: A dictionary which maps the strings "default_input" and - "default_output" to strings also appearing in parformats. - "default_input"-> format used internally within the class - "default_output"-> Default format to use when converting - parameters for outside use. - metadict: Dictionary mapping string keys to tuple (v, m) where v is an - additional argument required by function, and m is a method - whose string output recreates v when passed to eval(). - base: A basefunction subclass instance which this one decorates with - additional functionality. + parameterdict : dict + The dictionary mapping string keys to their index in the + sequence of parameters. These keys apply only to the + default "internal" format. + parformats : array-like + The sequence of strings defining what formats are recognized + by a function. + default_formats : dict + The dictionary which maps the strings "default_input" and + "default_output" to strings also appearing in parformats. + "default_input"-> format used internally within the class + "default_output"-> Default format to use when converting + parameters for outside use. + metadict : dict + The Dictionary mapping string keys to tuple (v, m) where v is an + additional argument required by function, and m is a method + whose string output recreates v when passed to eval(). + base : BaseFunction subclass + The basefunction subclass instance which this one decorates with + additional functionality. Class methods (implemented by inheriting classes) ------------------------------------------------- @@ -74,19 +79,26 @@ def __init__( """Set parameterdict defined by subclass Parameters - parameterdict - A dictionary mapping string keys (e.g. "position") - to their index in a sequence of parameters for this - PeakFunction subclass. Every parameter must appear. - parformats - A sequence of strings containing all allowed input/output - formats defined for the function's parameters. - default_formats - A dictionary mapping the string keys "internal" and - "default_output" to formats from parformats. - metadict - Dictionary mapping string keys to additional arguments - required by function. - base - A basefunction subclass instance which this one decorates with - additional functionality. - Cache - A class (not instance) which implements caching of BaseFunction - evaluations. + ---------- + parameterdict : dict + The dictionary mapping string keys (e.g. "position") + to their index in a sequence of parameters for this + PeakFunction subclass. Every parameter must appear. + parformats : array-like + The sequence of strings containing all allowed input/output + formats defined for the function's parameters. + default_formats : dict + The dictionary mapping the string keys "internal" and + "default_output" to formats from parformats. + metadict : dict + The dictionary mapping string keys to additional arguments + required by function. + base : basefunction subclass + The basefunction subclass instance which this one decorates with + additional functionality. + Cache : class + The class (not instance) which implements caching of BaseFunction + evaluations. """ self.parameterdict = parameterdict self.npars = len(self.parameterdict) @@ -177,12 +189,16 @@ def jacobian(self, p, r, rng=None): """Calculate jacobian of p, possibly restricted by range. Parameters - p - The ModelPart to be evaluated - r - sequence or scalar over which function is evaluated - rng - Optional slice object restricts which r-values are evaluated. - The output has same length as r, but unevaluated objects have - a default value of 0. If caching is enabled these may be - previously calculated values instead. + ---------- + p : ModelPart instance + The ModelPart to be evaluated + r :array-like + sequence or scalar over which function is evaluated + rng : slice object + Optional slice object restricts which r-values are evaluated. + The output has same length as r, but unevaluated objects have + a default value of 0. If caching is enabled these may be + previously calculated values instead. """ if self is not p._owner: emsg = "Argument 'p' must be evaluated by the BaseFunction " + "subclass which owns it." @@ -207,9 +223,18 @@ def transform_derivatives(self, pars, in_format=None, out_format=None): """Return gradient matrix for pars converted from in_format to out_format. Parameters - pars - Sequence of parameters - in_format - A format defined for this class - out_format - A format defined for this class + ---------- + pars : array-like + The sequence of parameters + in_format : str + The format defined for this class + out_format : str + The format defined for this class + + Returns + ------- + array-like + The gradient matrix for pars converted from in_format to out_format. """ # Map unspecified formats to specific formats defined in default_formats if in_format is None: @@ -242,9 +267,18 @@ def transform_parameters(self, pars, in_format=None, out_format=None): values that correspond to the same physical result. Parameters - pars - Sequence of parameters - in_format - A format defined for this class - out_format - A format defined for this class + ---------- + pars : array-like + The sequence of parameters + in_format : str + The format defined for this class + out_format : str + The format defined for this class + + Returns + ------- + array-like + The new sequence of parameters with out_format. """ # Map unspecified formats to specific formats defined in default_formats if in_format is None: @@ -274,12 +308,21 @@ def value(self, p, r, rng=None): """Calculate value of ModelPart over r, possibly restricted by range. Parameters - p - The ModelPart to be evaluated - r - sequence or scalar over which function is evaluated - rng - Optional slice object restricts which r-values are evaluated. - The output has same length as r, but unevaluated objects have - a default value of 0. If caching is enabled these may be - previously calculated values instead. + ---------- + p : ModelPart instance + The ModelPart to be evaluated + r : array-like or float + The sequence or scalar over which function is evaluated + rng : slice object + Optional slice object restricts which r-values are evaluated. + The output has same length as r, but unevaluated objects have + a default value of 0. If caching is enabled these may be + previously calculated values instead. + + Returns + ------- + array-like + The value of ModelPart over r, possibly restricted by range. """ if self is not p._owner: emsg = "Argument 'p' must be evaluated by the BaseFunction " + "subclass which owns it." @@ -311,11 +354,16 @@ def pgradient(self, p, format): In the trivial case where format="internal", returns an identity matrix. Parameters - p - A ModelPart - format - The format of the parameters + ---------- + p : ModelPart instance + The ModelPart instance to be evaluated for gradient calculation. + format : str + The format of the parameters Returns - A 2D array containing the partial derivatives. + ------- + array-like + A 2D array containing the partial derivatives. """ return @@ -330,7 +378,13 @@ def writestr(self, baselist): in baselist. Parameters - baselist - List of BaseFunction (or subclass) instances. + ---------- + baselist : array-like + The list of BaseFunction (or subclass) instances. + + Returns + ------- + The string representation of self. """ if self.base is not None and self.base not in baselist: emsg = "baselist does not include this BaseFunction's base function." @@ -358,8 +412,15 @@ def factory(functionstr, baselist): index of that instance in baselist. Parameters - functionstr - The string representation of the BaseFunction instance - baselist - List of BaseFunction (or subclass) instances. + ---------- + functionstr : str + The string representation of the BaseFunction instance + baselist : array-like + The list of BaseFunction (or subclass) instances. + + Returns + Basefunction instance + The BaseFunction instance based on the parameter strings """ data = functionstr.splitlines() data = "\n".join(data) @@ -427,8 +488,16 @@ def safefunction(f, fsafe): Does not handle circular dependencies. Parameters - f: A BaseFunction instance - fsafe: List of BaseFunction instances being built.""" + ---------- + f : BaseFunction instance + The BaseFunction instance + fsafe : array-like + The list of BaseFunction instances being built. + + Returns + ------- + None + """ if f not in fsafe: if f.base is not None: BaseFunction.safefunction(f.base, fsafe) From 356129ae4641a4896f000a638fa5665ffb1dafa3 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Tue, 27 Aug 2024 05:18:14 +0800 Subject: [PATCH 64/65] api workflow build for diffpy.srmise (#102) * api workflow build for diffpy.srmise * [pre-commit.ci] auto fixes from pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- doc/source/api/diffpy.srmise.applications.rst | 28 ++++++ doc/source/api/diffpy.srmise.baselines.rst | 52 ++++++++++ .../api/diffpy.srmise.example_package.rst | 31 ------ .../api/diffpy.srmise.modelevaluators.rst | 36 +++++++ doc/source/api/diffpy.srmise.peaks.rst | 44 +++++++++ doc/source/api/diffpy.srmise.rst | 99 +++++++++++++++++-- 6 files changed, 250 insertions(+), 40 deletions(-) create mode 100644 doc/source/api/diffpy.srmise.applications.rst create mode 100644 doc/source/api/diffpy.srmise.baselines.rst delete mode 100644 doc/source/api/diffpy.srmise.example_package.rst create mode 100644 doc/source/api/diffpy.srmise.modelevaluators.rst create mode 100644 doc/source/api/diffpy.srmise.peaks.rst diff --git a/doc/source/api/diffpy.srmise.applications.rst b/doc/source/api/diffpy.srmise.applications.rst new file mode 100644 index 0000000..87413c9 --- /dev/null +++ b/doc/source/api/diffpy.srmise.applications.rst @@ -0,0 +1,28 @@ +:tocdepth: -1 + +diffpy.srmise.applications package +================================== + +.. automodule:: diffpy.srmise.applications + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +diffpy.srmise.applications.plot module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.applications.plot + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.applications.extract module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.applications.extract + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/diffpy.srmise.baselines.rst b/doc/source/api/diffpy.srmise.baselines.rst new file mode 100644 index 0000000..5e9b791 --- /dev/null +++ b/doc/source/api/diffpy.srmise.baselines.rst @@ -0,0 +1,52 @@ +:tocdepth: -1 + +diffpy.srmise.baselines package +=============================== + +.. automodule:: diffpy.srmise.baselines + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +diffpy.srmise.baselines.nanospherical module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.baselines.nanospherical + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.baselines.arbitrary module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.baselines.arbitrary + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.baselines.fromsequence module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.baselines.fromsequence + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.baselines.polynomial module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.baselines.polynomial + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.baselines.base module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.baselines.base + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/diffpy.srmise.example_package.rst b/doc/source/api/diffpy.srmise.example_package.rst deleted file mode 100644 index 8c7f6ad..0000000 --- a/doc/source/api/diffpy.srmise.example_package.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. _example_package documentation: - -|title| -======= - -.. |title| replace:: diffpy.srmise.example_package package - -.. automodule:: diffpy.srmise.example_package - :members: - :undoc-members: - :show-inheritance: - -|foo| ------ - -.. |foo| replace:: diffpy.srmise.example_package.foo module - -.. automodule:: diffpy.srmise.example_package.foo - :members: - :undoc-members: - :show-inheritance: - -|bar| ------ - -.. |bar| replace:: diffpy.srmise.example_package.bar module - -.. automodule:: diffpy.srmise.example_package.foo - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/diffpy.srmise.modelevaluators.rst b/doc/source/api/diffpy.srmise.modelevaluators.rst new file mode 100644 index 0000000..012c60a --- /dev/null +++ b/doc/source/api/diffpy.srmise.modelevaluators.rst @@ -0,0 +1,36 @@ +:tocdepth: -1 + +diffpy.srmise.modelevaluators package +===================================== + +.. automodule:: diffpy.srmise.modelevaluators + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +diffpy.srmise.modelevaluators.aic module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.modelevaluators.aic + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.modelevaluators.aicc module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.modelevaluators.aicc + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.modelevaluators.base module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.modelevaluators.base + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/diffpy.srmise.peaks.rst b/doc/source/api/diffpy.srmise.peaks.rst new file mode 100644 index 0000000..b88e831 --- /dev/null +++ b/doc/source/api/diffpy.srmise.peaks.rst @@ -0,0 +1,44 @@ +:tocdepth: -1 + +diffpy.srmise.peaks package +=========================== + +.. automodule:: diffpy.srmise.peaks + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +diffpy.srmise.peaks.gaussianoverr module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.peaks.gaussianoverr + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.peaks.terminationripples module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.peaks.terminationripples + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.peaks.base module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.peaks.base + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.peaks.gaussian module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.peaks.gaussian + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/diffpy.srmise.rst b/doc/source/api/diffpy.srmise.rst index 958efe0..98ea790 100644 --- a/doc/source/api/diffpy.srmise.rst +++ b/doc/source/api/diffpy.srmise.rst @@ -1,9 +1,7 @@ :tocdepth: -1 -|title| -======= - -.. |title| replace:: diffpy.srmise package +diffpy.srmise package +===================== .. automodule:: diffpy.srmise :members: @@ -14,17 +12,100 @@ Subpackages ----------- .. toctree:: - diffpy.srmise.example_package + :titlesonly: + + diffpy.srmise.peaks + diffpy.srmise.modelevaluators + diffpy.srmise.applications + diffpy.srmise.baselines Submodules ---------- -|module| --------- +diffpy.srmise.multimodelselection module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.multimodelselection + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.srmiselog module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.srmiselog + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.modelparts module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.modelparts + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.peakstability module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.peakstability + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.basefunction module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.basefunction + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.pdfpeakextraction module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.pdfpeakextraction + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.modelcluster module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.modelcluster + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.dataclusters module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.dataclusters + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.srmiseerrors module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.srmiseerrors + :members: + :undoc-members: + :show-inheritance: + +diffpy.srmise.peakextraction module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.srmise.peakextraction + :members: + :undoc-members: + :show-inheritance: -.. |module| replace:: diffpy.srmise.example_submodule module +diffpy.srmise.pdfdataset module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.srmise.example_submodule +.. automodule:: diffpy.srmise.pdfdataset :members: :undoc-members: :show-inheritance: From 30f856eb2e41d94121dcc01ab1c0c7eb057af808 Mon Sep 17 00:00:00 2001 From: Rundong Hua <157993340+stevenhua0320@users.noreply.github.com> Date: Wed, 4 Sep 2024 04:15:40 -0400 Subject: [PATCH 65/65] add changed news (#103) --- news/cookiecutter.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/cookiecutter.rst diff --git a/news/cookiecutter.rst b/news/cookiecutter.rst new file mode 100644 index 0000000..64626aa --- /dev/null +++ b/news/cookiecutter.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* Moved diffpy.srmise from python2 to python3. + +**Deprecated:** + +* + +**Removed:** + +* Removed travis.yml and other useless files + +**Fixed:** + +* Fixed numpy format boolean counting, numpy int slicing error. + +**Security:** + +*