diff --git a/.flake8 b/.flake8 index 2d2cb168..5a56eddd 100644 --- a/.flake8 +++ b/.flake8 @@ -5,7 +5,7 @@ exclude = build, dist, doc/source/conf.py -max-line-length = 115 +max-line-length = 79 # Ignore some style 'errors' produced while formatting by 'black' # https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings extend-ignore = E203 diff --git a/.isort.cfg b/.isort.cfg index e0926f42..6d831957 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,4 +1,4 @@ [settings] -line_length = 115 +line_length = 79 multi_line_output = 3 include_trailing_comma = True diff --git a/doc/source/conf.py b/doc/source/conf.py index 92959232..8a8fbee4 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -223,7 +223,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ("index", "diffpy.pdfmorph.tex", "diffpy.pdfmorph Documentation", ab_authors, "manual"), + ( + "index", + "diffpy.pdfmorph.tex", + "diffpy.pdfmorph Documentation", + ab_authors, + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -251,7 +257,15 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("index", "diffpy.pdfmorph", "diffpy.pdfmorph Documentation", ab_authors, 1)] +man_pages = [ + ( + "index", + "diffpy.pdfmorph", + "diffpy.pdfmorph Documentation", + ab_authors, + 1, + ) +] # If true, show URL addresses after external links. # man_show_urls = False diff --git a/news/line79.rst b/news/line79.rst new file mode 100644 index 00000000..932cde58 --- /dev/null +++ b/news/line79.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* reduce the line width limit to 79 + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index 18ae77f7..7618ba13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ ignore-words = ".codespell/ignore_words.txt" skip = "*.cif,*.dat,*agr" [tool.black] -line-length = 115 +line-length = 79 include = '\.pyi?$' exclude = ''' /( diff --git a/src/diffpy/pdfmorph/morph_helpers/__init__.py b/src/diffpy/pdfmorph/morph_helpers/__init__.py index b3a5c483..271ba4f9 100644 --- a/src/diffpy/pdfmorph/morph_helpers/__init__.py +++ b/src/diffpy/pdfmorph/morph_helpers/__init__.py @@ -11,10 +11,15 @@ # ############################################################################## -"""List of helpers for certain morphing operations (currently only used for smear).""" +"""List of helpers for certain morphing operations +(currently only used for smear).""" -from diffpy.pdfmorph.morph_helpers.transformpdftordf import TransformXtalPDFtoRDF -from diffpy.pdfmorph.morph_helpers.transformrdftopdf import TransformXtalRDFtoPDF +from diffpy.pdfmorph.morph_helpers.transformpdftordf import ( + TransformXtalPDFtoRDF, +) +from diffpy.pdfmorph.morph_helpers.transformrdftopdf import ( + TransformXtalRDFtoPDF, +) # List of helpers morph_helpers = [ diff --git a/src/diffpy/pdfmorph/morph_helpers/transformpdftordf.py b/src/diffpy/pdfmorph/morph_helpers/transformpdftordf.py index 99e39061..91acf26a 100644 --- a/src/diffpy/pdfmorph/morph_helpers/transformpdftordf.py +++ b/src/diffpy/pdfmorph/morph_helpers/transformpdftordf.py @@ -51,7 +51,9 @@ def morph(self, x_morph, y_morph, x_target, y_target): morph_baseline = self.baselineslope * self.x_morph_in self.y_morph_out = self.x_morph_in * (self.y_morph_in - morph_baseline) target_baseline = self.baselineslope * self.x_target_in - self.y_target_out = self.x_target_in * (self.y_target_in - target_baseline) + self.y_target_out = self.x_target_in * ( + self.y_target_in - target_baseline + ) return self.xyallout diff --git a/src/diffpy/pdfmorph/morph_helpers/transformrdftopdf.py b/src/diffpy/pdfmorph/morph_helpers/transformrdftopdf.py index 97a0c33e..58bf958f 100644 --- a/src/diffpy/pdfmorph/morph_helpers/transformrdftopdf.py +++ b/src/diffpy/pdfmorph/morph_helpers/transformrdftopdf.py @@ -53,10 +53,14 @@ def morph(self, x_morph, y_morph, x_target, y_target): morph_baseline = self.baselineslope * self.x_morph_in target_baseline = self.baselineslope * self.x_target_in with numpy.errstate(divide="ignore", invalid="ignore"): - self.y_target_out = self.y_target_in / self.x_target_in + target_baseline + self.y_target_out = ( + self.y_target_in / self.x_target_in + target_baseline + ) self.y_target_out[self.x_target_in == 0] = 0 with numpy.errstate(divide="ignore", invalid="ignore"): - self.y_morph_out = self.y_morph_in / self.x_morph_in + morph_baseline + self.y_morph_out = ( + self.y_morph_in / self.x_morph_in + morph_baseline + ) self.y_morph_out[self.x_target_in == 0] = 0 return self.xyallout diff --git a/src/diffpy/pdfmorph/morphs/morph.py b/src/diffpy/pdfmorph/morphs/morph.py index e55ec92a..73a6f617 100644 --- a/src/diffpy/pdfmorph/morphs/morph.py +++ b/src/diffpy/pdfmorph/morphs/morph.py @@ -26,10 +26,12 @@ class Morph(object): """Base class for implementing a morph given a target. - Adapted from diffpy.pdfgetx to include two sets of arrays that get passed through. + Adapted from diffpy.pdfgetx to include two sets of arrays that get passed + through. - Attributes are taken from config when not found locally. The morph may modify the config dictionary. - This is the means by which to communicate automatically modified attributes. + Attributes are taken from config when not found locally. The morph may + modify the config dictionary. This is the means by which to communicate + automatically modified attributes. Class Attributes ---------------- @@ -142,7 +144,8 @@ def __init__(self, config=None): def morph(self, x_morph, y_morph, x_target, y_target): """Morph arrays morphed or target. - Identity operation. This method should be overloaded in a derived class. + Identity operation. + This method should be overloaded in a derived class. Parameters ---------- @@ -154,7 +157,8 @@ def morph(self, x_morph, y_morph, x_target, y_target): Returns ------- tuple - A tuple of numpy arrays (x_morph_out, y_morph_out, x_target_out, y_target_out) + A tuple of numpy arrays + (x_morph_out, y_morph_out, x_target_out, y_target_out) """ self.x_morph_in = x_morph self.y_morph_in = y_morph @@ -223,7 +227,8 @@ def plotOutputs(self, xylabels=True, **plotargs): xylabels: bool Flag for updating x and y axes labels. plotargs - Arguments passed to the pylab plot function. Note that "label" will be ignored. + Arguments passed to the pylab plot function. + Note that "label" will be ignored. Returns ------- @@ -234,7 +239,9 @@ def plotOutputs(self, xylabels=True, **plotargs): pargs = dict(plotargs) pargs.pop("label", None) - rv = plot(self.x_target_out, self.y_target_out, label="target", **pargs) + rv = plot( + self.x_target_out, self.y_target_out, label="target", **pargs + ) rv = plot(self.x_morph_out, self.y_morph_out, label="morph", **pargs) if xylabels: xlabel(self.xoutlabel) diff --git a/src/diffpy/pdfmorph/morphs/morphchain.py b/src/diffpy/pdfmorph/morphs/morphchain.py index ca310879..4882c547 100644 --- a/src/diffpy/pdfmorph/morphs/morphchain.py +++ b/src/diffpy/pdfmorph/morphs/morphchain.py @@ -20,8 +20,9 @@ class MorphChain(list): """Class for chaining morphs together. - This class is a queue of morphs that get executed in order via the 'morph' method. - This class derives from the built-in list, and list methods are used to modify the queue. + This class is a queue of morphs that get executed in order via the 'morph' + method. This class derives from the built-in list, and list methods are + used to modify the queue. This derives from list and relies on its methods where possible. @@ -57,7 +58,8 @@ class MorphChain(list): xy_target_out Tuple of (x_target_out, y_target_out) from last morph. xyallout - Tuple of (x_morph_out, y_morph_out, x_target_out, y_target_out) from last morph. + Tuple of (x_morph_out, y_morph_out, x_target_out, y_target_out) + from last morph. parnames Names of parameters collected from morphs (Read only). @@ -66,19 +68,47 @@ class MorphChain(list): The properties return tuples of None if there are no morphs. """ - x_morph_in = property(lambda self: None if len(self) == 0 else self[0].x_morph_in) - y_morph_in = property(lambda self: None if len(self) == 0 else self[0].y_morph_in) - x_target_in = property(lambda self: None if len(self) == 0 else self[0].x_target_in) - y_target_in = property(lambda self: None if len(self) == 0 else self[0].y_target_in) - x_morph_out = property(lambda self: None if len(self) == 0 else self[-1].x_morph_out) - y_morph_out = property(lambda self: None if len(self) == 0 else self[-1].y_morph_out) - x_target_out = property(lambda self: None if len(self) == 0 else self[-1].x_target_out) - y_target_out = property(lambda self: None if len(self) == 0 else self[-1].y_target_out) - xy_morph_in = property(lambda self: (None, None) if len(self) == 0 else self[0].xy_morph_in) - xy_morph_out = property(lambda self: (None, None) if len(self) == 0 else self[-1].xy_morph_out) - xy_target_in = property(lambda self: (None, None) if len(self) == 0 else self[0].xy_target_in) - xy_target_out = property(lambda self: (None, None) if len(self) == 0 else self[-1].xy_target_out) - xyallout = property(lambda self: (None, None, None, None) if len(self) == 0 else self[-1].xyallout) + x_morph_in = property( + lambda self: None if len(self) == 0 else self[0].x_morph_in + ) + y_morph_in = property( + lambda self: None if len(self) == 0 else self[0].y_morph_in + ) + x_target_in = property( + lambda self: None if len(self) == 0 else self[0].x_target_in + ) + y_target_in = property( + lambda self: None if len(self) == 0 else self[0].y_target_in + ) + x_morph_out = property( + lambda self: None if len(self) == 0 else self[-1].x_morph_out + ) + y_morph_out = property( + lambda self: None if len(self) == 0 else self[-1].y_morph_out + ) + x_target_out = property( + lambda self: None if len(self) == 0 else self[-1].x_target_out + ) + y_target_out = property( + lambda self: None if len(self) == 0 else self[-1].y_target_out + ) + xy_morph_in = property( + lambda self: (None, None) if len(self) == 0 else self[0].xy_morph_in + ) + xy_morph_out = property( + lambda self: (None, None) if len(self) == 0 else self[-1].xy_morph_out + ) + xy_target_in = property( + lambda self: (None, None) if len(self) == 0 else self[0].xy_target_in + ) + xy_target_out = property( + lambda self: (None, None) if len(self) == 0 else self[-1].xy_target_out + ) + xyallout = property( + lambda self: ( + (None, None, None, None) if len(self) == 0 else self[-1].xyallout + ) + ) parnames = property(lambda self: set(p for m in self for p in m.parnames)) def __init__(self, config, *args): @@ -89,7 +119,8 @@ def __init__(self, config, *args): Notes ----- - Additional arguments are morphs that will extend the queue of morphs. + Additional arguments are morphs that will extend the queue of + morphs. """ self.config = config self.extend(args) @@ -108,7 +139,8 @@ def morph(self, x_morph, y_morph, x_target, y_target): Returns ------- tuple - A tuple of numpy arrays (x_morph_out, y_morph_out, x_target_out, y_target_out). + A tuple of numpy arrays + (x_morph_out, y_morph_out, x_target_out, y_target_out). Notes ----- diff --git a/src/diffpy/pdfmorph/morphs/morphrgrid.py b/src/diffpy/pdfmorph/morphs/morphrgrid.py index 7e5a7bfe..60808eaa 100644 --- a/src/diffpy/pdfmorph/morphs/morphrgrid.py +++ b/src/diffpy/pdfmorph/morphs/morphrgrid.py @@ -43,9 +43,10 @@ class MorphRGrid(Morph): Notes ----- - If any of these is not defined or outside the bounds of the input arrays, - then it will be taken to be the most inclusive value from the input arrays. - These modified values will be stored as the above attributes. + If any of these is not defined or outside the bounds of the input + arrays, then it will be taken to be the most inclusive value from the + input arrays. These modified values will be stored as the above + attributes. """ # Define input output types @@ -63,7 +64,10 @@ def morph(self, x_morph, y_morph, x_target, y_target): r_step_target = self.x_target_in[1] - self.x_target_in[0] r_step_morph = self.x_morph_in[1] - self.x_morph_in[0] rstepinc = max(r_step_target, r_step_morph) - rmaxinc = min(self.x_target_in[-1] + r_step_target, self.x_morph_in[-1] + r_step_morph) + rmaxinc = min( + self.x_target_in[-1] + r_step_target, + self.x_morph_in[-1] + r_step_morph, + ) if self.rmin is None or self.rmin < rmininc: self.rmin = rmininc if self.rmax is None or self.rmax > rmaxinc: @@ -71,10 +75,16 @@ def morph(self, x_morph, y_morph, x_target, y_target): if self.rstep is None or self.rstep < rstepinc: self.rstep = rstepinc # Make sure that rmax is exclusive - self.x_morph_out = numpy.arange(self.rmin, self.rmax - epsilon, self.rstep) - self.y_morph_out = numpy.interp(self.x_morph_out, self.x_morph_in, self.y_morph_in) + self.x_morph_out = numpy.arange( + self.rmin, self.rmax - epsilon, self.rstep + ) + self.y_morph_out = numpy.interp( + self.x_morph_out, self.x_morph_in, self.y_morph_in + ) self.x_target_out = self.x_morph_out.copy() - self.y_target_out = numpy.interp(self.x_target_out, self.x_target_in, self.y_target_in) + self.y_target_out = numpy.interp( + self.x_target_out, self.x_target_in, self.y_target_in + ) return self.xyallout diff --git a/src/diffpy/pdfmorph/morphs/morphshape.py b/src/diffpy/pdfmorph/morphs/morphshape.py index 755469e3..382c3cb2 100644 --- a/src/diffpy/pdfmorph/morphs/morphshape.py +++ b/src/diffpy/pdfmorph/morphs/morphshape.py @@ -89,7 +89,8 @@ def morph(self, x_morph, y_morph, x_target, y_target): def _sphericalCF(r, psize): """Spherical nanoparticle characteristic function. - From Kodama et al., Acta Cryst. A, 62, 444-453 (converted from radius to diameter). + From Kodama et al., Acta Cryst. A, 62, 444-453 + (converted from radius to diameter). Parameters ---------- @@ -163,21 +164,19 @@ def _spheroidalCF2(r, psize, axrat): r = rx[rx <= v * psize] r2 = r * r f1 = ( - 1 - - 3 * r / (4 * d * v) * (1 - r2 / (4 * d2) * (1 + 2.0 / (3 * v2))) - - 3 * r / (4 * d) * (1 - r2 / (4 * d2)) * v / sqrt(1 - v2) * atanh(sqrt(1 - v2)) + 1 - 3*r/(4*d*v)*(1-r2/(4*d2)*(1+2.0/(3*v2))) - 3*r/(4*d)*(1-r2/(4*d2))*v/sqrt(1-v2)*atanh(sqrt(1-v2)) # fmt: skip # noqa: E501 ) r = rx[numpy.logical_and(rx > v * psize, rx <= psize)] r2 = r * r + # fmt: off f2 = ( ( - 3 * d / (8 * r) * (1 + r2 / (2 * d2)) * sqrt(1 - r2 / d2) - - 3 * r / (4 * d) * (1 - r2 / (4 * d2)) * atanh(sqrt(1 - r2 / d2)) + 3*d/(8*r)*(1+r2/(2*d2))*sqrt(1-r2/d2) - 3*r/(4*d)*(1-r2/(4*d2))*atanh(sqrt(1-r2/d2)) # noqa: E501 ) - * v - / sqrt(1 - v2) + * v / sqrt(1-v2) ) + # fmt: on r = rx[rx > psize] f3 = numpy.zeros_like(r) @@ -188,24 +187,13 @@ def _spheroidalCF2(r, psize, axrat): r = rx[rx <= psize] r2 = r * r f1 = ( - 1 - - 3 * r / (4 * d * v) * (1 - r2 / (4 * d2) * (1 + 2.0 / (3 * v2))) - - 3 * r / (4 * d) * (1 - r2 / (4 * d2)) * v / sqrt(v2 - 1) * atan(sqrt(v2 - 1)) + 1 - 3*r/(4*d*v)*(1-r2/(4*d2)*(1+2.0/(3*v2))) - 3*r/(4*d)*(1-r2/(4*d2))*v/sqrt(v2-1)*atan(sqrt(v2-1)) # fmt: skip # noqa: E501 ) r = rx[numpy.logical_and(rx > psize, rx <= v * psize)] r2 = r * r f2 = ( - 1 - - 3 * r / (4 * d * v) * (1 - r2 / (4 * d2) * (1 + 2.0 / (3 * v2))) - - 3.0 / 8 * (1 + r2 / (2 * d2)) * sqrt(1 - d2 / r2) * v / sqrt(v2 - 1) - - 3 - * r - / (4 * d) - * (1 - r2 / (4 * d2)) - * v - / sqrt(v2 - 1) - * (atan(sqrt(v2 - 1)) - atan(sqrt(r2 / d2 - 1))) + 1 - 3*r/(4*d*v)*(1-r2/(4*d2)*(1+2.0/(3*v2))) - 3.0/8*(1+r2/(2*d2))*sqrt(1-d2/r2)*v/sqrt(v2-1) - 3*r/(4*d)*(1-r2/(4*d2))*v/sqrt(v2-1)*(atan(sqrt(v2-1))-atan(sqrt(r2/d2-1))) # fmt: skip # noqa:E501 ) r = rx[rx > v * psize] diff --git a/src/diffpy/pdfmorph/pdfmorph_api.py b/src/diffpy/pdfmorph/pdfmorph_api.py index 2aed055e..d87249d7 100644 --- a/src/diffpy/pdfmorph/pdfmorph_api.py +++ b/src/diffpy/pdfmorph/pdfmorph_api.py @@ -40,7 +40,9 @@ ], qdamp=morphs.MorphResolutionDamping, ) -_default_config = dict(scale=None, stretch=None, smear=None, baselineslope=None, qdamp=None) +_default_config = dict( + scale=None, stretch=None, smear=None, baselineslope=None, qdamp=None +) def morph_default_config(**kwargs): @@ -141,7 +143,8 @@ def pdfmorph( - morph_chain: diffpy.pdfmorph.morphs.morphchain.MorphChain The instance of processed morph chain. - Calling ``x_morph, y_morph, x_target, y_target = morph_chain.xyallout`` + Calling + ``x_morph, y_morph, x_target, y_target = morph_chain.xyallout`` will conveniently return morphed data and reference data - morphed_cfg: dict A dictionary of refined morphing parameters @@ -154,8 +157,9 @@ def pdfmorph( Examples -------- - # morphing (x_morph, y_morph) pair to (x_target, y_target) pair with scaling - from diffpy.pdfmorph.pdfmorph_api import pdfmorph, morph_default_config, plot_morph + # morphing (x_morph, y_morph) pair to (x_target, y_target) pair with + scaling from diffpy.pdfmorph.pdfmorph_api import pdfmorph, + morph_default_config, plot_morph morph_cfg = morph_default_config(scale=1.01) morph_rv_dict = pdfmorph(x_morph, y_morph, x_target, y_target, **morph_cfg) @@ -172,7 +176,11 @@ def pdfmorph( # input config rv_cfg = dict(kwargs) # configure morph operations - active_morphs = [k for k, v in rv_cfg.items() if (v is not None) and k in _morph_step_dict] + active_morphs = [ + k + for k, v in rv_cfg.items() + if (v is not None) and k in _morph_step_dict + ] rv_cfg["rmin"] = rmin rv_cfg["rmax"] = rmax rv_cfg["rstep"] = rstep @@ -230,7 +238,9 @@ def pdfmorph( print("== INFO: Following steps are fixed during morphing ==:\n") print("\n".join(fixed_operations)) print("== INFO: Refined morph parameters ==:\n") - output = "\n".join(["# %s = %f" % (k, v) for k, v in rv_cfg.items() if v is not None]) + output = "\n".join( + ["# %s = %f" % (k, v) for k, v in rv_cfg.items() if v is not None] + ) output += "\n# Rw = %f" % rw output += "\n# Pearson = %f" % pcc print(output) @@ -250,14 +260,16 @@ def plot_morph(chain, ax=None, **kwargs): An instance of processed morph chain. ax: matplotlib.axes.Axes, optional An instance of Axes class to plot the morphing result. - If ax is None, instances of new Figure and Axes will be created. Default to None. + If ax is None, instances of new Figure and Axes will be created. + Default to None. kwargs: Additional keyword arguments will be passed to ``ax.plot(...**kwargs)`` Returns ------- l_list: list - A list of ``matplotlib.lines.Line2D`` objects representing the plotted data. + A list of ``matplotlib.lines.Line2D`` objects representing the + plotted data. """ if ax is None: fig, ax = plt.subplots() diff --git a/src/diffpy/pdfmorph/pdfmorph_io.py b/src/diffpy/pdfmorph/pdfmorph_io.py index 375a8eae..e454b1dc 100644 --- a/src/diffpy/pdfmorph/pdfmorph_io.py +++ b/src/diffpy/pdfmorph/pdfmorph_io.py @@ -46,10 +46,11 @@ def single_morph_output( save_file Name of file to print to. If None (default) print to terminal. morph_file - Name of the morphed PDF file. Required when printing to a non-terminal file. + Name of the morphed PDF file. Required when printing to a + non-terminal file. param xy_out: list - List of the form [x_morph_out, y_morph_out]. x_morph_out is a List of r values and - y_morph_out is a List of gr values. + List of the form [x_morph_out, y_morph_out]. x_morph_out is a List of + r values and y_morph_out is a List of gr values. verbose: bool Print additional details about the morph when True (default False). stdout_flag: bool @@ -58,10 +59,17 @@ def single_morph_output( # Input and output parameters morphs_in = "\n# Input morphing parameters:\n" - morphs_in += "\n".join(f"# {key} = {morph_inputs[key]}" for key in morph_inputs.keys()) + "\n" + morphs_in += ( + "\n".join( + f"# {key} = {morph_inputs[key]}" for key in morph_inputs.keys() + ) + + "\n" + ) morphs_out = "# Optimized morphing parameters:\n" - morphs_out += "\n".join(f"# {key} = {morph_results[key]:.6f}" for key in morph_results.keys()) + morphs_out += "\n".join( + f"# {key} = {morph_results[key]:.6f}" for key in morph_results.keys() + ) # Printing to terminal if stdout_flag: @@ -100,14 +108,16 @@ def single_morph_output( def create_morphs_directory(save_directory): """Create a directory for saving multiple morphed PDFs. - Takes in a user-given path to a directory save_directory and create a subdirectory named Morphs. - PDFmorph will save all morphs into the Morphs subdirectory while metadata about the morphs will - be stored in save_directory outside Morphs. + Takes in a user-given path to a directory save_directory and create a + subdirectory named Morphs. PDFmorph will save all morphs into the Morphs + subdirectory while metadata about the morphs will be stored in + save_directory outside Morphs. Parameters ---------- save_directory - Path to a directory. PDFmorph will save all generated files within this directory. + Path to a directory. PDFmorph will save all generated files within + this directory. Returns ------- @@ -126,8 +136,9 @@ def create_morphs_directory(save_directory): def get_multisave_names(target_list: list, save_names_file=None, mm=False): """Create or import a dictionary that specifies names to save morphs as. - First attempt to import names from a specified file. If names for certain morphs not found, - use default naming scheme: 'Morph_with_Target_.cgr'. + First attempt to import names from a specified file. + If names for certain morphs not found, use default naming scheme: + 'Morph_with_Target_.cgr'. Used when saving multiple morphs. @@ -143,7 +154,8 @@ def get_multisave_names(target_list: list, save_names_file=None, mm=False): Returns ------- dict - The names to save each morph as. Keys are the target PDF file names used to produce that morph. + The names to save each morph as. Keys are the target PDF file names + used to produce that morph. """ # Dictionary storing save file names @@ -158,10 +170,24 @@ def get_multisave_names(target_list: list, save_names_file=None, mm=False): if target_file.name not in save_names.keys(): if not mm: save_names.update( - {target_file.name: {__save_morph_as__: f"Morph_with_Target_{target_file.stem}.cgr"}} + { + target_file.name: { + __save_morph_as__: ( + f"Morph_with_Target_{target_file.stem}.cgr" + ) + } + } ) else: - save_names.update({target_file.name: {__save_morph_as__: f"Morph_of_{target_file.stem}.cgr"}}) + save_names.update( + { + target_file.name: { + __save_morph_as__: ( + f"Morph_of_{target_file.stem}.cgr" + ) + } + } + ) return save_names @@ -192,11 +218,14 @@ def multiple_morph_output( save_directory Name of directory to save morphs in. field - Name of field if data was sorted by a particular field. Otherwise, leave blank. + Name of field if data was sorted by a particular field. + Otherwise, leave blank. field_list: list - List of field values for each target PDF. Generated by diffpy.pdfmorph.tools.field_sort(). + List of field values for each target PDF. + Generated by diffpy.pdfmorph.tools.field_sort(). morph_file - Name of the morphed PDF file. Required to give summary data after saving to a directory. + Name of the morphed PDF file. + Required to give summary data after saving to a directory. target_directory Name of the directory containing the target PDF files. Required to give summary data after saving to a directory. @@ -205,18 +234,21 @@ def multiple_morph_output( stdout_flag: bool Print to terminal when True (default False). mm: bool - Multiple morphs done with a single target rather than multiple targets for a single morphed file. - Swaps morph and target in the code. + Multiple morphs done with a single target rather than multiple + targets for a single morphed file. Swaps morph and target in the code. """ # Input parameters used for every morph inputs = "\n# Input morphing parameters:\n" - inputs += "\n".join(f"# {key} = {morph_inputs[key]}" for key in morph_inputs.keys()) + inputs += "\n".join( + f"# {key} = {morph_inputs[key]}" for key in morph_inputs.keys() + ) # Verbose to get output for every morph verbose_outputs = "" if verbose: - # Output for every morph (information repeated in a succinct table below) + # Output for every morph + # (information repeated in a succinct table below) for target in morph_results.keys(): if not mm: output = f"\n# Target: {target}\n" @@ -224,7 +256,8 @@ def multiple_morph_output( output = f"\n# Morph: {target}\n" output += "# Optimized morphing parameters:\n" output += "\n".join( - f"# {param} = {morph_results[target][param]:.6f}" for param in morph_results[target] + f"# {param} = {morph_results[target][param]:.6f}" + for param in morph_results[target] ) verbose_outputs += f"{output}\n" @@ -270,38 +303,51 @@ def multiple_morph_output( else: header += f"# from morphing directory {target_path_name}\n" header += f"# with target {morph_path_name}" - reference_table = Path(save_directory).joinpath("Morph_Reference_Table.txt") + reference_table = Path(save_directory).joinpath( + "Morph_Reference_Table.txt" + ) with open(reference_table, "w") as reference: - print(f"{header}\n{inputs}\n{verbose_outputs}{table}", file=reference) + print( + f"{header}\n{inputs}\n{verbose_outputs}{table}", file=reference + ) if stdout_flag: # Indicate successful save - save_message = f"# Morphs saved in the directory {save_directory}\n" + save_message = ( + f"# Morphs saved in the directory {save_directory}\n" + ) print(save_message) def tabulate_results(multiple_morph_results): - """Helper function to make a data table summarizing details about the results of multiple morphs. + """Helper function to make a data table summarizing details about the + results of multiple morphs. Parameters ---------- multiple_morph_results - A collection of Dictionaries. Each Dictionary summarizes the resultsof a single morph. + A collection of Dictionaries. Each Dictionary summarizes the + resultsof a single morph. Returns ------- tabulated_results: dict - Keys in tabulated_results are the table's column names and each corresponding value is a list - of data for that column. + Keys in tabulated_results are the table's column names and each + corresponding value is a list of data for that column. """ # We only care about the following parameters in our data tables relevant_parameters = ["Scale", "Smear", "Stretch", "Pearson", "Rw"] - # Keys in this table represent column names and the value will be a list of column data + # Keys in this table represent column names and the value will be a list + # of column data tabulated_results = {} for param in relevant_parameters: tabulated_results.update( - {param: tools.get_values_from_dictionary_collection(multiple_morph_results, param)} + { + param: tools.get_values_from_dictionary_collection( + multiple_morph_results, param + ) + } ) return tabulated_results diff --git a/src/diffpy/pdfmorph/pdfmorphapp.py b/src/diffpy/pdfmorph/pdfmorphapp.py index 2a25a5f3..c8280897 100755 --- a/src/diffpy/pdfmorph/pdfmorphapp.py +++ b/src/diffpy/pdfmorph/pdfmorphapp.py @@ -31,7 +31,9 @@ def create_option_parser(): import optparse - prog_short = Path(sys.argv[0]).name # Program name, compatible w/ all OS paths + prog_short = Path( + sys.argv[0] + ).name # Program name, compatible w/ all OS paths class CustomParser(optparse.OptionParser): def __init__(self, *args, **kwargs): @@ -56,21 +58,34 @@ def custom_error(self, msg): epilog="\n".join( [ "Please report bugs to diffpy-users@googlegroups.com.", - "For more information, see the PDFmorph website at https://www.diffpy.org/diffpy.pdfmorph.", + ( + "For more information, see the PDFmorph website at " + "https://www.diffpy.org/diffpy.pdfmorph." + ), ] ), ) - parser.add_option("-V", "--version", action="version", help="Show program version and exit.") + parser.add_option( + "-V", + "--version", + action="version", + help="Show program version and exit.", + ) parser.version = __version__ parser.add_option( "-s", "--save", metavar="NAME", dest="slocation", - help="""Save the manipulated PDF to a file named NAME. Use \'-\' for stdout. - When --multiple- is enabled, save each manipulated PDF as a file in a directory named NAME; - you can specify names for each saved PDF file using --save-names-file.""", + help=( + "Save the manipulated PDF to a file named NAME. " + "Use '-' for stdout.\n" + "When --multiple- is enabled, " + "save each manipulated PDF as a file in a directory named NAME;\n" + "you can specify names for each saved PDF file using " + "--save-names-file." + ), ) parser.add_option( "-v", @@ -79,13 +94,24 @@ def custom_error(self, msg): action="store_true", help="Print additional header details to saved files.", ) - parser.add_option("--rmin", type="float", help="Minimum r-value to use for PDF comparisons.") - parser.add_option("--rmax", type="float", help="Maximum r-value to use for PDF comparisons.") + parser.add_option( + "--rmin", + type="float", + help="Minimum r-value to use for PDF comparisons.", + ) + parser.add_option( + "--rmax", + type="float", + help="Maximum r-value to use for PDF comparisons.", + ) parser.add_option( "--pearson", action="store_true", dest="pearson", - help="Maximize agreement in the Pearson function. Note that this is insensitive to scale.", + help=( + "Maximize agreement in the Pearson function. " + "Note that this is insensitive to scale." + ), ) parser.add_option( "--addpearson", @@ -99,10 +125,14 @@ def custom_error(self, msg): group = optparse.OptionGroup( parser, "Manipulations", - """These options select the manipulations that are to be applied to - the PDF from FILE1. The passed values will be refined unless specifically - excluded with the -a or -x options. If no option is specified, the PDFs from FILE1 and FILE2 will - be plotted without any manipulations.""", + ( + "These options select the manipulations that are to be applied to " + "the PDF from FILE1. " + "The passed values will be refined unless specifically " + "excluded with the -a or -x options. " + "If no option is specified, the PDFs from FILE1 and FILE2 will " + "be plotted without any manipulations." + ), ) parser.add_option_group(group) group.add_option( @@ -168,43 +198,59 @@ def custom_error(self, msg): "--radius", type="float", metavar="RADIUS", - help="""Apply characteristic function of sphere with radius RADIUS. - If PRADIUS is also specified, instead apply characteristic function of spheroid with equatorial - radius RADIUS and polar radius PRADIUS.""", + help=( + "Apply characteristic function of sphere with radius " + "RADIUS. If PRADIUS is also specified, instead apply " + "characteristic function of spheroid with equatorial " + "radius RADIUS and polar radius PRADIUS." + ), ) group.add_option( "--pradius", type="float", metavar="PRADIUS", - help="""Apply characteristic function of spheroid with equatorial - radius RADIUS and polar radius PRADIUS. If only PRADIUS is specified, instead apply - characteristic function of sphere with radius PRADIUS.""", + help=( + "Apply characteristic function of spheroid with " + "equatorial radius RADIUS and polar radius PRADIUS. If only " + "PRADIUS is specified, instead apply characteristic function of " + "sphere with radius PRADIUS." + ), ) group.add_option( "--iradius", type="float", metavar="IRADIUS", - help="""Apply inverse characteristic function of sphere with radius IRADIUS. - If IPRADIUS is also specified, instead apply inverse characteristic function of spheroid - with equatorial radius IRADIUS and polar radius IPRADIUS.""", + help=( + "Apply inverse characteristic function of sphere with radius " + "IRADIUS. If IPRADIUS is also specified, instead apply inverse " + "characteristic function of spheroid with equatorial radius " + "IRADIUS and polar radius IPRADIUS." + ), ) group.add_option( "--ipradius", type="float", metavar="IPRADIUS", - help="""Apply inverse characteristic function of spheroid with equatorial radius IRADIUS - and polar radius IPRADIUS. If only IPRADIUS is specified, instead apply inverse characteristic - function of sphere with radius IPRADIUS.""", + help=( + "Apply inverse characteristic function of spheroid with " + "equatorial radius IRADIUS and polar radius IPRADIUS. If only " + "IPRADIUS is specified, instead apply inverse characteristic " + "function of sphere with radius IPRADIUS." + ), ) # Plot Options group = optparse.OptionGroup( parser, "Plot Options", - """These options control plotting. - The manipulated and target PDFs will be plotted against each other with a - difference curve below. When --multiple- is enabled, the value of a parameter (specified by - --plot-parameter) will be plotted instead.""", + ( + "These options control plotting. The manipulated and target PDFs " + "will be plotted against each other with a difference curve " + "below. " + "When --multiple- is enabled, the value of a " + "parameter (specified by --plot-parameter) will be plotted " + "instead." + ), ) parser.add_option_group(group) group.add_option( @@ -218,54 +264,88 @@ def custom_error(self, msg): "--mlabel", metavar="MLABEL", dest="mlabel", - help="Set label for morphed data to MLABEL on plot. Default label is FILE1.", + help=( + "Set label for morphed data to MLABEL on plot. " + "Default label is FILE1." + ), ) group.add_option( "--tlabel", metavar="TLABEL", dest="tlabel", - help="Set label for target data to TLABEL on plot. Default label is FILE2.", + help=( + "Set label for target data to TLABEL on plot. " + "Default label is FILE2." + ), + ) + group.add_option( + "--pmin", + type="float", + help="Minimum r-value to plot. Defaults to RMIN.", + ) + group.add_option( + "--pmax", + type="float", + help="Maximum r-value to plot. Defaults to RMAX.", + ) + group.add_option( + "--maglim", + type="float", + help="Magnify plot curves beyond r=MAGLIM by MAG.", + ) + group.add_option( + "--mag", + type="float", + help="Magnify plot curves beyond r=MAGLIM by MAG.", + ) + group.add_option( + "--lwidth", type="float", help="Line thickness of plotted curves." ) - group.add_option("--pmin", type="float", help="Minimum r-value to plot. Defaults to RMIN.") - group.add_option("--pmax", type="float", help="Maximum r-value to plot. Defaults to RMAX.") - group.add_option("--maglim", type="float", help="Magnify plot curves beyond r=MAGLIM by MAG.") - group.add_option("--mag", type="float", help="Magnify plot curves beyond r=MAGLIM by MAG.") - group.add_option("--lwidth", type="float", help="Line thickness of plotted curves.") # Multiple morph options group = optparse.OptionGroup( parser, "Multiple Morphs", - """This program can morph a PDF against multiple targets in one command. - See -s and Plot Options for how saving and plotting functionality changes when performing multiple morphs.""", + ( + "This program can morph a PDF against multiple targets in one " + "command. See -s and Plot Options for how saving and plotting " + "functionality changes when performing multiple morphs." + ), ) parser.add_option_group(group) group.add_option( "--multiple-morphs", dest="multiple_morphs", action="store_true", - help=f"""Changes usage to \'{prog_short} [options] FILE DIRECTORY\'. FILE - will be morphed with each file in DIRECTORY as target. - Files in DIRECTORY are sorted by alphabetical order unless a field is - specified by --sort-by.""", + help=( + f"Changes usage to '{prog_short} [options] FILE DIRECTORY'. " + f"FILE will be morphed with each file in DIRECTORY as target. " + f"Files in DIRECTORY are sorted by alphabetical order unless a " + f"field is specified by --sort-by." + ), ) group.add_option( "--multiple-targets", dest="multiple_targets", action="store_true", - help=f"""Changes usage to \'{prog_short} [options] DIRECTORY FILE\'. Each file - in DIRECTORY will be morphed with FILE as target. - Files in DIRECTORY are sorted by alphabetical order unless a field is - specified by --sort-by.""", + help=( + f"Changes usage to '{prog_short} [options] DIRECTORY FILE'. " + f"Each file in DIRECTORY will be morphed with FILE as target. " + f"Files in DIRECTORY are sorted by alphabetical order unless a " + f"field is specified by --sort-by." + ), ) group.add_option( "--sort-by", metavar="FIELD", dest="field", - help="""Used with --multiple- to sort files in DIRECTORY by FIELD. - If the FIELD being used has a numerical value, sort from lowest to highest. - Otherwise, sort in ASCII sort order. - FIELD must be included in the header of all the PDF files.""", + help=( + "Used with --multiple- to sort files in DIRECTORY " + "by FIELD. " + "If the FIELD being used has a numerical value, sort from lowest " + "to highest. Otherwise, sort in ASCII sort order. FIELD must be " + "included in the header of all the PDF files." + ), ) group.add_option( "--reverse", @@ -284,23 +364,30 @@ def custom_error(self, msg): "--save-names-file", metavar="NAMESFILE", dest="snamesfile", - help=f"""Used when both -s and --multiple- are enabled. - Specify names for each manipulated PDF when saving (see -s) using a serial file - NAMESFILE. The format of NAMESFILE should be as follows: each target PDF - is an entry in NAMESFILE. For each entry, there should be a key {__save_morph_as__} - whose value specifies the name to save the manipulated PDF as. An example .json - serial file is shown in the PDFmorph manual.""", + help=( + "Used when both -s and --multiple- are enabled. " + "Specify names for each manipulated PDF when saving (see -s) " + "using a serial file NAMESFILE. The format of NAMESFILE should be " + "as follows: each target PDF is an entry in NAMESFILE. For each " + "entry, there should be a key {__save_morph_as__} whose value " + "specifies the name to save the manipulated PDF as. An example " + ".json serial file is shown in the PDFmorph manual." + ), ) group.add_option( "--plot-parameter", metavar="PLOTPARAM", dest="plotparam", - help="""Used when both plotting and --multiple- are enabled. - Choose a PLOTPARAM to plot for each morph (i.e. adding --plot-parameter=Pearson means the - program will display a plot of the Pearson correlation coefficient for each morph-target - pair). PLOTPARAM is not case sensitive, so both Pearson and pearson indicate the same - parameter. When PLOTPARAM is not specified, Rw values for each morph-target pair will be - plotted. PLOTPARAM will be displayed as the vertical axis label for the plot.""", + help=( + "Used when both plotting and --multiple- are " + "enabled. Choose a PLOTPARAM to plot for each morph (i.e. adding " + "--plot-parameter=Pearson means the program will display a plot " + "of the Pearson correlation coefficient for each morph-target pair" + "). PLOTPARAM is not case sensitive, so both Pearson and pearson " + "indicate the same parameter. When PLOTPARAM is not specified, Rw " + "values for each morph-target pair will be plotted. PLOTPARAM " + "will be displayed as the vertical axis label for the plot." + ), ) # Defaults @@ -320,7 +407,9 @@ def single_morph(parser, opts, pargs, stdout_flag=True): if len(pargs) < 2: parser.error("You must supply FILE1 and FILE2.") elif len(pargs) > 2: - parser.error("Too many arguments. Make sure you only supply FILE1 and FILE2.") + parser.error( + "Too many arguments. Make sure you only supply FILE1 and FILE2." + ) # Get the PDFs x_morph, y_morph = getPDFFromFile(pargs[0]) @@ -341,7 +430,11 @@ def single_morph(parser, opts, pargs, stdout_flag=True): config["rmin"] = opts.rmin config["rmax"] = opts.rmax config["rstep"] = None - if opts.rmin is not None and opts.rmax is not None and opts.rmax <= opts.rmin: + if ( + opts.rmin is not None + and opts.rmax is not None + and opts.rmax <= opts.rmin + ): e = "rmin must be less than rmax" parser.custom_error(e) @@ -443,11 +536,15 @@ def single_morph(parser, opts, pargs, stdout_flag=True): refiner.refine(*refpars) except ValueError as e: parser.custom_error(str(e)) - # Smear is not being refined, but baselineslope needs to refined to apply smear - # Note that baselineslope is only added to the refine list if smear is applied + # Smear is not being refined, but baselineslope needs to refined to apply + # smear + # Note that baselineslope is only added to the refine list if smear is + # applied elif "baselineslope" in refpars: try: - refiner.refine("baselineslope", baselineslope=config["baselineslope"]) + refiner.refine( + "baselineslope", baselineslope=config["baselineslope"] + ) except ValueError as e: parser.custom_error(str(e)) else: @@ -461,7 +558,11 @@ def single_morph(parser, opts, pargs, stdout_flag=True): chain(x_morph, y_morph, x_target, y_target) # Input morph parameters - morph_inputs = {"scale": scale_in, "stretch": stretch_in, "smear": smear_in} + morph_inputs = { + "scale": scale_in, + "stretch": stretch_in, + "smear": smear_in, + } morph_inputs.update({"hshift": hshift_in, "vshift": vshift_in}) # Output morph parameters @@ -517,19 +618,29 @@ def single_morph(parser, opts, pargs, stdout_flag=True): def multiple_targets(parser, opts, pargs, stdout_flag=True): - # Custom error messages since usage is distinct when --multiple tag is applied + # Custom error messages since usage is distinct when --multiple tag is + # applied if len(pargs) < 2: - parser.custom_error("You must supply FILE and DIRECTORY. See --multiple-targets under --help for usage.") + parser.custom_error( + "You must supply FILE and DIRECTORY. " + "See --multiple-targets under --help for usage." + ) elif len(pargs) > 2: - parser.custom_error("Too many arguments. You must only supply a FILE and a DIRECTORY.") + parser.custom_error( + "Too many arguments. You must only supply a FILE and a DIRECTORY." + ) # Parse paths morph_file = Path(pargs[0]) if not morph_file.is_file(): - parser.custom_error(f"{morph_file} is not a file. Go to --help for usage.") + parser.custom_error( + f"{morph_file} is not a file. Go to --help for usage." + ) target_directory = Path(pargs[1]) if not target_directory.is_dir(): - parser.custom_error(f"{target_directory} is not a directory. Go to --help for usage.") + parser.custom_error( + f"{target_directory} is not a directory. Go to --help for usage." + ) # Get list of files from target directory target_list = list(target_directory.iterdir()) @@ -554,13 +665,21 @@ def multiple_targets(parser, opts, pargs, stdout_flag=True): if field is not None: try: target_list, field_list = tools.field_sort( - target_list, field, opts.reverse, opts.serfile, get_field_values=True + target_list, + field, + opts.reverse, + opts.serfile, + get_field_values=True, ) except KeyError: if opts.serfile is not None: - parser.custom_error("The requested field was not found in the metadata file.") + parser.custom_error( + "The requested field was not found in the metadata file." + ) else: - parser.custom_error("The requested field is missing from a PDF file header.") + parser.custom_error( + "The requested field is missing from a PDF file header." + ) else: # Default is alphabetical sort target_list.sort(reverse=opts.reverse) @@ -571,7 +690,9 @@ def multiple_targets(parser, opts, pargs, stdout_flag=True): # Set up saving save_directory = opts.slocation # User-given directory for saves - save_names_file = opts.snamesfile # User-given serialfile with names for each morph + save_names_file = ( + opts.snamesfile + ) # User-given serialfile with names for each morph save_morphs_here = None # Subdirectory for saving morphed PDFs save_names = {} # Dictionary of names to save each morph as if save_directory is not None: @@ -584,7 +705,9 @@ def multiple_targets(parser, opts, pargs, stdout_flag=True): parser.custom_error(save_fail_message) try: - save_names = io.get_multisave_names(target_list, save_names_file=save_names_file) + save_names = io.get_multisave_names( + target_list, save_names_file=save_names_file + ) # Could not create directory or find names to save morphs as except FileNotFoundError: save_fail_message = "\nUnable to read from save names file" @@ -594,7 +717,8 @@ def multiple_targets(parser, opts, pargs, stdout_flag=True): morph_results = {} for target_file in target_list: if target_file.is_file: - # Set the save file destination to be a file within the SLOC directory + # Set the save file destination to be a file within the SLOC + # directory if save_directory is not None: save_as = save_names[target_file.name][__save_morph_as__] opts.slocation = Path(save_morphs_here).joinpath(save_as) @@ -602,7 +726,9 @@ def multiple_targets(parser, opts, pargs, stdout_flag=True): pargs = [morph_file, target_file] morph_results.update( { - target_file.name: single_morph(parser, opts, pargs, stdout_flag=False), + target_file.name: single_morph( + parser, opts, pargs, stdout_flag=False + ), } ) @@ -610,7 +736,11 @@ def multiple_targets(parser, opts, pargs, stdout_flag=True): for key in morph_results.keys(): target_file_names.append(key) - morph_inputs = {"scale": opts.scale, "stretch": opts.stretch, "smear": opts.smear} + morph_inputs = { + "scale": opts.scale, + "stretch": opts.stretch, + "smear": opts.smear, + } morph_inputs.update({"hshift": opts.hshift, "vshift": opts.vshift}) try: @@ -640,41 +770,61 @@ def multiple_targets(parser, opts, pargs, stdout_flag=True): # Find parameter if specified if opts.plotparam is not None: param_name = opts.plotparam - param_list = tools.case_insensitive_dictionary_search(opts.plotparam, plot_results) - # Not an available parameter to plot or no values found for the parameter + param_list = tools.case_insensitive_dictionary_search( + opts.plotparam, plot_results + ) + # Not an available parameter to plot or no values found for the + # parameter if param_list is None: - parser.custom_error("Cannot find specified plot parameter. No plot shown.") + parser.custom_error( + "Cannot find specified plot parameter. No plot shown." + ) else: try: if field_list is not None: - pdfplot.plot_param(field_list, param_list, param_name, field) + pdfplot.plot_param( + field_list, param_list, param_name, field + ) else: - pdfplot.plot_param(target_file_names, param_list, param_name) + pdfplot.plot_param( + target_file_names, param_list, param_name + ) # Can occur for non-refined plotting parameters - # i.e. --smear is not selected as an option, but smear is the plotting parameter + # i.e. --smear is not selected as an option, but smear is the + # plotting parameter except ValueError: parser.custom_error( - "The plot parameter is missing values for at least one morph and target pair. " - "No plot shown." + "The plot parameter is missing values for at least one " + "morph and target pair. No plot shown." ) return morph_results def multiple_morphs(parser, opts, pargs, stdout_flag=True): - # Custom error messages since usage is distinct when --multiple tag is applied + # Custom error messages since usage is distinct when --multiple tag is + # applied if len(pargs) < 2: - parser.custom_error("You must supply DIRECTORY and FILE. See --multiple-morphs under --help for usage.") + parser.custom_error( + "You must supply DIRECTORY and FILE. " + "See --multiple-morphs under --help for usage." + ) elif len(pargs) > 2: - parser.custom_error("Too many arguments. You must only supply a DIRECTORY and FILE.") + parser.custom_error( + "Too many arguments. You must only supply a DIRECTORY and FILE." + ) # Parse paths target_file = Path(pargs[1]) if not target_file.is_file(): - parser.custom_error(f"{target_file} is not a file. Go to --help for usage.") + parser.custom_error( + f"{target_file} is not a file. Go to --help for usage." + ) morph_directory = Path(pargs[0]) if not morph_directory.is_dir(): - parser.custom_error(f"{morph_directory} is not a directory. Go to --help for usage.") + parser.custom_error( + f"{morph_directory} is not a directory. Go to --help for usage." + ) # Get list of files from morph directory morph_list = list(morph_directory.iterdir()) @@ -699,13 +849,21 @@ def multiple_morphs(parser, opts, pargs, stdout_flag=True): if field is not None: try: morph_list, field_list = tools.field_sort( - morph_list, field, opts.reverse, opts.serfile, get_field_values=True + morph_list, + field, + opts.reverse, + opts.serfile, + get_field_values=True, ) except KeyError: if opts.serfile is not None: - parser.custom_error("The requested field was not found in the metadata file.") + parser.custom_error( + "The requested field was not found in the metadata file." + ) else: - parser.custom_error("The requested field is missing from a PDF file header.") + parser.custom_error( + "The requested field is missing from a PDF file header." + ) else: # Default is alphabetical sort morph_list.sort(reverse=opts.reverse) @@ -716,7 +874,9 @@ def multiple_morphs(parser, opts, pargs, stdout_flag=True): # Set up saving save_directory = opts.slocation # User-given directory for saves - save_names_file = opts.snamesfile # User-given serialfile with names for each morph + save_names_file = ( + opts.snamesfile + ) # User-given serialfile with names for each morph save_morphs_here = None # Subdirectory for saving morphed PDFs save_names = {} # Dictionary of names to save each morph as if save_directory is not None: @@ -729,7 +889,9 @@ def multiple_morphs(parser, opts, pargs, stdout_flag=True): parser.custom_error(save_fail_message) try: - save_names = io.get_multisave_names(morph_list, save_names_file=save_names_file) + save_names = io.get_multisave_names( + morph_list, save_names_file=save_names_file + ) # Could not create directory or find names to save morphs as except FileNotFoundError: save_fail_message = "\nUnable to read from save names file" @@ -739,7 +901,8 @@ def multiple_morphs(parser, opts, pargs, stdout_flag=True): morph_results = {} for morph_file in morph_list: if morph_file.is_file: - # Set the save file destination to be a file within the SLOC directory + # Set the save file destination to be a file within the SLOC + # directory if save_directory is not None: save_as = save_names[morph_file.name][__save_morph_as__] opts.slocation = Path(save_morphs_here).joinpath(save_as) @@ -747,7 +910,9 @@ def multiple_morphs(parser, opts, pargs, stdout_flag=True): pargs = [morph_file, target_file] morph_results.update( { - morph_file.name: single_morph(parser, opts, pargs, stdout_flag=False), + morph_file.name: single_morph( + parser, opts, pargs, stdout_flag=False + ), } ) @@ -755,7 +920,11 @@ def multiple_morphs(parser, opts, pargs, stdout_flag=True): for key in morph_results.keys(): morph_file_names.append(key) - morph_inputs = {"scale": opts.scale, "stretch": opts.stretch, "smear": opts.smear} + morph_inputs = { + "scale": opts.scale, + "stretch": opts.stretch, + "smear": opts.smear, + } morph_inputs.update({"hshift": opts.hshift, "vshift": opts.vshift}) try: @@ -786,22 +955,32 @@ def multiple_morphs(parser, opts, pargs, stdout_flag=True): # Find parameter if specified if opts.plotparam is not None: param_name = opts.plotparam - param_list = tools.case_insensitive_dictionary_search(opts.plotparam, plot_results) - # Not an available parameter to plot or no values found for the parameter + param_list = tools.case_insensitive_dictionary_search( + opts.plotparam, plot_results + ) + # Not an available parameter to plot or no values found for the + # parameter if param_list is None: - parser.custom_error("Cannot find specified plot parameter. No plot shown.") + parser.custom_error( + "Cannot find specified plot parameter. No plot shown." + ) else: try: if field_list is not None: - pdfplot.plot_param(field_list, param_list, param_name, field) + pdfplot.plot_param( + field_list, param_list, param_name, field + ) else: - pdfplot.plot_param(morph_file_names, param_list, param_name) + pdfplot.plot_param( + morph_file_names, param_list, param_name + ) # Can occur for non-refined plotting parameters - # i.e. --smear is not selected as an option, but smear is the plotting parameter + # i.e. --smear is not selected as an option, but smear is the + # plotting parameter except ValueError: parser.custom_error( - "The plot parameter is missing values for at least one morph and target pair. " - "No plot shown." + "The plot parameter is missing values for at least one " + "morph and target pair. No plot shown." ) return morph_results diff --git a/src/diffpy/pdfmorph/pdfplot.py b/src/diffpy/pdfmorph/pdfplot.py index 16bd078f..5dd4d690 100644 --- a/src/diffpy/pdfmorph/pdfplot.py +++ b/src/diffpy/pdfmorph/pdfplot.py @@ -31,15 +31,18 @@ def plotPDFs(pairlist, labels=None, offset="auto", rmin=None, rmax=None): pairlist Iterable of (r, gr) pairs to plot. labels - Iterable of names for the pairs. If this is not the same length as the pairlist, a legend will not - be shown (default []). + Iterable of names for the pairs. If this is not the same length as + the pairlist, a legend will not be shown (default []). offset - Offset to place between plots. PDFs will be sequentially shifted in the y-direction by the offset. - If offset is 'auto' (default), the optimal offset will be determined automatically. + Offset to place between plots. PDFs will be sequentially shifted in + the y-direction by the offset. If offset is 'auto' (default), the + optimal offset will be determined automatically. rmin - The minimum r-value to plot. If this is None (default), the lower bound of the PDF is not altered. + The minimum r-value to plot. If this is None (default), the lower + bound of the PDF is not altered. rmax - The maximum r-value to plot. If this is None (default), the upper bound of the PDF is not altered. + The maximum r-value to plot. If this is None (default), the upper + bound of the PDF is not altered. """ if labels is None: labels = [] @@ -79,24 +82,27 @@ def comparePDFs( ): """Plot two PDFs on top of each other and difference curve. - The second PDF will be shown as blue circles below and the first as a red line. - The difference curve will be in green and offset for clarity. + The second PDF will be shown as blue circles below and the first as a red + line. The difference curve will be in green and offset for clarity. Parameters ---------- pairlist Iterable of (r, gr) pairs to plot labels - Iterable of names for the pairs. If this is not the same length as the pairlist, a legend will not - be shown (default []). + Iterable of names for the pairs. If this is not the same length as + the pairlist, a legend will not be shown (default []). rmin - The minimum r-value to plot. If this is None (default), the lower bound of the PDF is not altered. + The minimum r-value to plot. If this is None (default), the lower + bound of the PDF is not altered. rmax - The maximum r-value to plot. If this is None (default), the upper bound of the PDF is not altered. + The maximum r-value to plot. If this is None (default), the upper + bound of the PDF is not altered. show Show the plot (default True) maglim - Point after which to magnify the signal by mag. If None (default), no magnification will take place. + Point after which to magnify the signal by mag. If None (default), no + magnification will take place. mag Magnification factor (default 5) rw @@ -178,7 +184,15 @@ def comparePDFs( if maglim is not None: # Add a line for the magnification cutoff - plt.axvline(maglim, 0, 1, linestyle="--", color="black", linewidth=1.5, dashes=(14, 7)) + plt.axvline( + maglim, + 0, + 1, + linestyle="--", + color="black", + linewidth=1.5, + dashes=(14, 7), + ) # FIXME - look for a place to put the maglim xpos = (rvmax * 0.85 + maglim) / 2 / (rvmax - rvmin) if xpos <= 0.9: @@ -219,14 +233,17 @@ def plot_param(target_labels, param_list, param_name=None, field=None): Parameters ---------- target_labels: list - Names (or field if --sort-by given) of each file acting as target for the morph. + Names (or field if --sort-by given) of each file acting as target for + the morph. param_list: list Contains the values of some parameter corresponding to each file. param_name: str Name of the parameter. field: list or None - When not None and entries in field are numerical, a line chart of Rw versus field is made. - When None (default) or values are non-numerical, it plots a bar chart of Rw values per file. + When not None and entries in field are numerical, a line chart of Rw + versus field is made. + When None (default) or values are non-numerical, it plots a bar chart + of Rw values per file. """ # ensure all entries in target_labels are distinct for plotting @@ -298,9 +315,11 @@ def truncatePDFs(r, gr, rmin=None, rmax=None): gr PDF g(r) values. rmin - The minimum r-value. If this is None (default), the lower bound of the PDF is not altered. + The minimum r-value. If this is None (default), the lower bound of + the PDF is not altered. rmax - The maximum r-value. If this is None (default), the upper bound of the PDF is not altered. + The maximum r-value. If this is None (default), the upper bound of + the PDF is not altered. Returns ------- diff --git a/src/diffpy/pdfmorph/refine.py b/src/diffpy/pdfmorph/refine.py index cecd50f3..9519880b 100644 --- a/src/diffpy/pdfmorph/refine.py +++ b/src/diffpy/pdfmorph/refine.py @@ -39,7 +39,8 @@ class Refiner(object): pars List of names of parameters to be refined. residual - The residual function to optimize. Default _residual. Can be assigned to other functions. + The residual function to optimize. Default _residual. Can be assigned + to other functions. """ def __init__(self, chain, x_morph, y_morph, x_target, y_target): @@ -71,7 +72,8 @@ def _pearson(self, pvals): """Pearson correlation function. This gives e**-p (vector), where p is the pearson correlation function. - We seek to minimize this, which occurs when the correlation is the largest. + We seek to minimize this, which occurs when the correlation is the + largest. """ self._update_chain(pvals) _x_morph, _y_morph, _x_target, _y_target = self.chain( @@ -90,14 +92,17 @@ def _add_pearson(self, pvals): def refine(self, *args, **kw): """Refine the chain. - Additional arguments are used to specify which parameters are to be refined. + Additional arguments are used to specify which parameters are to be + refined. If no arguments are passed, then all parameters will be refined. - Keywords pass initial values to the parameters, whether or not they are refined. + Keywords pass initial values to the parameters, whether or not they + are refined. This uses the leastsq algorithm from scipy.optimize. This returns the final scalar residual value. - The parameters from the fit can be retrieved from the config dictionary of the morph or morph chain. + The parameters from the fit can be retrieved from the config + dictionary of the morph or morph chain. Raises ------ @@ -114,7 +119,9 @@ def refine(self, *args, **kw): return 0.0 initial = [config[p] for p in self.pars] - sol, cov_sol, infodict, emesg, ier = leastsq(self.residual, initial, full_output=1) + sol, cov_sol, infodict, emesg, ier = leastsq( + self.residual, initial, full_output=1 + ) fvec = infodict["fvec"] if ier not in (1, 2, 3, 4): emesg diff --git a/src/diffpy/pdfmorph/tools.py b/src/diffpy/pdfmorph/tools.py index 5f4239a7..09f96ec3 100644 --- a/src/diffpy/pdfmorph/tools.py +++ b/src/diffpy/pdfmorph/tools.py @@ -43,14 +43,17 @@ def estimateBaselineSlope(r, gr, rmin=None, rmax=None): gr The PDF over the r-grid. rmin - The minimum r-value to consider. If this is None (default) is None, then the minimum of r is used. + The minimum r-value to consider. If this is None (default) is None, + then the minimum of r is used. rmax - The maximum r-value to consider. If this is None (default) is None, then the maximum of r is used. + The maximum r-value to consider. If this is None (default) is None, + then the maximum of r is used. Returns ------- slope: float - The slope of baseline. If the PDF is scaled properly, this is equal to -4*pi*rho0. + The slope of baseline. If the PDF is scaled properly, this is equal + to -4*pi*rho0. """ from numpy import dot from scipy.optimize import leastsq @@ -127,7 +130,10 @@ def readPDF(fname): def nn_value(val, name): """Convenience function for ensuring certain non-negative inputs.""" if val < 0: - negative_value_warning = f"\n# Negative value for {name} given. Using absolute value instead." + negative_value_warning = ( + f"\n# Negative value for {name} given. " + "Using absolute value instead." + ) print(negative_value_warning) return -val return val @@ -171,7 +177,9 @@ def case_insensitive_dictionary_search(key: str, dictionary: dict): return dictionary.get(key) -def field_sort(filepaths: list, field, reverse=False, serfile=None, get_field_values=False): +def field_sort( + filepaths: list, field, reverse=False, serfile=None, get_field_values=False +): """Sort a list of files by a field stored in header information. All files must contain this header information. @@ -186,14 +194,16 @@ def field_sort(filepaths: list, field, reverse=False, serfile=None, get_field_va serfile Path to a serial file with field information for each file. get_field_values: bool - Boolean indicating whether to also return a List of field values (default False). - This List of field values is parallel to the sorted list of filepaths with items - in the same position corresponding to each other. + Boolean indicating whether to also return a List of field values + (default False). This List of field values is parallel to the sorted + list of filepaths with items in the same position corresponding to + each other. Returns ------- list - Sorted list of paths. When get_fv is true, also return an associated field list. + Sorted list of paths. When get_fv is true, also return an associated + field list. """ # Get the field from each file @@ -201,7 +211,9 @@ def field_sort(filepaths: list, field, reverse=False, serfile=None, get_field_va if serfile is None: for path in filepaths: fhd = loadData(path, headers=True) - files_field_values.append([path, case_insensitive_dictionary_search(field, fhd)]) + files_field_values.append( + [path, case_insensitive_dictionary_search(field, fhd)] + ) else: # deserialize the serial file des_dict = deserialize_data(serfile) @@ -221,22 +233,28 @@ def field_sort(filepaths: list, field, reverse=False, serfile=None, get_field_va except (ValueError, TypeError): raise KeyError("Field missing.") if get_field_values: - return [pair[0] for pair in files_field_values], [pair[1] for pair in files_field_values] + return [pair[0] for pair in files_field_values], [ + pair[1] for pair in files_field_values + ] else: return [pair[0] for pair in files_field_values] -def get_values_from_dictionary_collection(dictionary_collection: iter, target_key): - """In an (iterable) collection of dictionaries, search for a target key in each dictionary. - Return a list of all found values corresponding to that key. +def get_values_from_dictionary_collection( + dictionary_collection: iter, target_key +): + """In an (iterable) collection of dictionaries, search for a target key + in each dictionary. Return a list of all found values corresponding + to that key. Parameters ---------- dictionary_collection: iter The collection of dictionaries to search through. target_key - The key to search for in each dictionary. For each dictionary in dictionary_collection that has that key, - the corresponding value is appended to a List called values. + The key to search for in each dictionary. For each dictionary in + dictionary_collection that has that key, the corresponding value is + appended to a List called values. Returns ------- @@ -249,7 +267,8 @@ def get_values_from_dictionary_collection(dictionary_collection: iter, target_ke # Handle dictionary-type iterable if type(dictionary_collection) is dict: - # Assume the dictionaries are stored in the values and keys indicate names of the dictionaries + # Assume the dictionaries are stored in the values and keys indicate + # names of the dictionaries dictionary_collection = dictionary_collection.values() # All other type iterables are handled the same way as a list diff --git a/tests/test_morphchain.py b/tests/test_morphchain.py index 2ad0b184..8a9657b2 100644 --- a/tests/test_morphchain.py +++ b/tests/test_morphchain.py @@ -40,7 +40,9 @@ def test_morph(self, setup): mscale = MorphScale() chain = MorphChain(config, mgrid, mscale) - x_morph, y_morph, x_target, y_target = chain(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = chain( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) assert (x_morph == x_target).all() pytest.approx(x_morph[0], 1.0) diff --git a/tests/test_morphpdftordf.py b/tests/test_morphpdftordf.py index d425cc1b..48cd035e 100644 --- a/tests/test_morphpdftordf.py +++ b/tests/test_morphpdftordf.py @@ -6,7 +6,9 @@ import numpy import pytest -from diffpy.pdfmorph.morph_helpers.transformpdftordf import TransformXtalPDFtoRDF +from diffpy.pdfmorph.morph_helpers.transformpdftordf import ( + TransformXtalPDFtoRDF, +) # useful variables thisfile = locals().get("__file__", "file.py") @@ -18,9 +20,15 @@ class TestTransformXtalPDFtoRDF: @pytest.fixture def setup(self): self.x_morph = numpy.arange(0.01, 5, 0.01) - self.y_morph = numpy.exp(-0.5 * (self.x_morph - 1.0) ** 2) / self.x_morph - self.x_morph + self.y_morph = ( + numpy.exp(-0.5 * (self.x_morph - 1.0) ** 2) / self.x_morph + - self.x_morph + ) self.x_target = numpy.arange(0.01, 5, 0.01) - self.y_target = numpy.exp(-0.5 * (self.x_morph - 2.0) ** 2) / self.x_morph - self.x_morph + self.y_target = ( + numpy.exp(-0.5 * (self.x_morph - 2.0) ** 2) / self.x_morph + - self.x_morph + ) return def test_transform(self, setup): @@ -28,7 +36,9 @@ def test_transform(self, setup): config = {"baselineslope": -1.0} transform = TransformXtalPDFtoRDF(config) - x_morph, y_morph, x_target, y_target = transform(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = transform( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) rdf1 = numpy.exp(-0.5 * (x_morph - 1.0) ** 2) rdf2 = numpy.exp(-0.5 * (x_target - 2.0) ** 2) diff --git a/tests/test_morphrdftopdf.py b/tests/test_morphrdftopdf.py index f48e0e68..f66cb8b1 100644 --- a/tests/test_morphrdftopdf.py +++ b/tests/test_morphrdftopdf.py @@ -6,7 +6,9 @@ import numpy import pytest -from diffpy.pdfmorph.morph_helpers.transformrdftopdf import TransformXtalRDFtoPDF +from diffpy.pdfmorph.morph_helpers.transformrdftopdf import ( + TransformXtalRDFtoPDF, +) # useful variables thisfile = locals().get("__file__", "file.py") @@ -28,7 +30,9 @@ def test_transform(self, setup): config = {"baselineslope": -1.0} transform = TransformXtalRDFtoPDF(config) - x_morph, y_morph, x_target, y_target = transform(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = transform( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) rdf1 = numpy.exp(-0.5 * (x_morph - 1.0) ** 2) / x_morph - x_morph rdf2 = numpy.exp(-0.5 * (x_target - 2.0) ** 2) / x_target - x_target diff --git a/tests/test_morphresolution.py b/tests/test_morphresolution.py index 53d3cefa..a2226228 100644 --- a/tests/test_morphresolution.py +++ b/tests/test_morphresolution.py @@ -28,7 +28,9 @@ def test_morph(self, setup): config = {"qdamp": 0.01} morph = MorphResolutionDamping(config) - x_morph, y_morph, x_target, y_target = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) assert numpy.allclose(self.y_target, y_target) assert numpy.allclose(y_morph, y_target) diff --git a/tests/test_morphrgrid.py b/tests/test_morphrgrid.py index 2439936c..43a957f4 100644 --- a/tests/test_morphrgrid.py +++ b/tests/test_morphrgrid.py @@ -42,7 +42,9 @@ def testRangeInBounds(self, setup): "rstep": 0.1, } morph = MorphRGrid(config) - xyallout = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + xyallout = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) pytest.approx(config["rmin"], morph.rmin) pytest.approx(config["rmax"], morph.rmax) pytest.approx(config["rstep"], morph.rstep) @@ -58,7 +60,9 @@ def testRmaxOut(self, setup): "rstep": 0.1, } morph = MorphRGrid(config) - xyallout = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + xyallout = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) pytest.approx(config["rmin"], morph.rmin) pytest.approx(5, morph.rmax) pytest.approx(config["rstep"], morph.rstep) @@ -74,7 +78,9 @@ def testRminOut(self, setup): "rstep": 0.01, } morph = MorphRGrid(config) - xyallout = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + xyallout = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) pytest.approx(1.0, morph.rmin) pytest.approx(config["rmax"], morph.rmax) pytest.approx(config["rstep"], morph.rstep) @@ -90,7 +96,9 @@ def testRstepOut(self, setup): "rstep": 0.001, } morph = MorphRGrid(config) - xyallout = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + xyallout = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) pytest.approx(config["rmin"], morph.rmin) pytest.approx(config["rmax"], morph.rmax) pytest.approx(0.01, morph.rstep) diff --git a/tests/test_morphscale.py b/tests/test_morphscale.py index c6ebd9fb..ae20025e 100644 --- a/tests/test_morphscale.py +++ b/tests/test_morphscale.py @@ -28,7 +28,9 @@ def test_morph(self, setup): config = {"scale": 2.0} morph = MorphScale(config) - x_morph, y_morph, x_target, y_target = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) assert numpy.allclose(2 * self.y_morph, y_morph) assert numpy.allclose(self.y_target, y_target) diff --git a/tests/test_morphshape.py b/tests/test_morphshape.py index 990b0ca5..3d4e9a11 100644 --- a/tests/test_morphshape.py +++ b/tests/test_morphshape.py @@ -31,7 +31,9 @@ def test_morph(self, setup): config = {"radius": 17.5} morph = MorphSphere(config) - x_morph, y_morph, x_target, y_target = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) assert numpy.allclose(self.y_target, y_target) assert numpy.allclose(y_morph, y_target) @@ -52,7 +54,9 @@ class TestMorphSpheroid: ispheroid_configs = [iconfig_sphere, iconfig_oblate] # Files used for testing - flag_inverse = 0 # Indicates whether we are testing MorphSpheroid or MorphISpheroid + flag_inverse = ( + 0 # Indicates whether we are testing MorphSpheroid or MorphISpheroid + ) testfiles = [ ["ni_qmax25.cgr", "ni_qmax25_psize35.cgr"], # Sphere ["ni_qmax25.cgr", "ni_qmax25_e17.5_p5.0.cgr"], # Oblate spheroid @@ -63,9 +67,13 @@ def reset(self): if len(self.testfile) == 0: # Ignore first init return - morph_file = os.path.join(testdata_dir, self.testfile[0 - self.flag_inverse]) + morph_file = os.path.join( + testdata_dir, self.testfile[0 - self.flag_inverse] + ) self.x_morph, self.y_morph = numpy.loadtxt(morph_file, unpack=True) - target_file = os.path.join(testdata_dir, self.testfile[1 - self.flag_inverse]) + target_file = os.path.join( + testdata_dir, self.testfile[1 - self.flag_inverse] + ) self.x_target, self.y_target = numpy.loadtxt(target_file, unpack=True) return @@ -89,7 +97,9 @@ def test_morph(self): def shape_test_helper(self, config): morph = MorphSpheroid(config) - x_morph, y_morph, x_target, y_target = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) assert numpy.allclose(self.y_target, y_target) assert numpy.allclose(y_morph, y_target) @@ -98,7 +108,9 @@ def shape_test_helper(self, config): def ishape_test_helper(self, config): morph = MorphISpheroid(config) - x_morph, y_morph, x_target, y_target = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) assert numpy.allclose(self.y_target, y_target) diff --git a/tests/test_morphshift.py b/tests/test_morphshift.py index 2e1c0bbd..feda66a4 100644 --- a/tests/test_morphshift.py +++ b/tests/test_morphshift.py @@ -34,7 +34,9 @@ def test_morph(self, setup): config = {"hshift": self.hshift, "vshift": self.vshift} morph = MorphShift(config) - x_morph, y_morph, x_target, y_target = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) # Only care about the shifted data past the shift # Everything to left of shift is outside our input data domain diff --git a/tests/test_morphsmear.py b/tests/test_morphsmear.py index b947d9da..430a569d 100644 --- a/tests/test_morphsmear.py +++ b/tests/test_morphsmear.py @@ -20,7 +20,9 @@ def setup(self): self.smear = 0.1 self.r0 = 7 * numpy.pi / 22.0 * 2 self.x_morph = numpy.arange(0.01, 5, 0.01) - self.y_morph = numpy.exp(-0.5 * ((self.x_morph - self.r0) / self.smear) ** 2) + self.y_morph = numpy.exp( + -0.5 * ((self.x_morph - self.r0) / self.smear) ** 2 + ) self.x_target = self.x_morph.copy() self.y_target = self.x_target.copy() return @@ -30,7 +32,9 @@ def test_morph(self, setup): morph = MorphSmear() morph.smear = 0.15 - x_morph, y_morph, x_target, y_target = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) # Target should be unchanged assert numpy.allclose(self.y_target, y_target) diff --git a/tests/test_morphstretch.py b/tests/test_morphstretch.py index 8f753351..c87cb4a6 100644 --- a/tests/test_morphstretch.py +++ b/tests/test_morphstretch.py @@ -30,7 +30,9 @@ def test_morph(self, setup): # Stretch by 50% morph.stretch = 0.5 - x_morph, y_morph, x_target, y_target = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) # Target should be unchanged assert numpy.allclose(self.y_target, y_target) @@ -45,7 +47,9 @@ def test_morph(self, setup): # Stretch by -10% morph.stretch = -0.1 - x_morph, y_morph, x_target, y_target = morph(self.x_morph, self.y_morph, self.x_target, self.y_target) + x_morph, y_morph, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) # Target should be unchanged assert numpy.allclose(self.y_target, y_target) diff --git a/tests/test_pdfmorphapp.py b/tests/test_pdfmorphapp.py index 62b84c8a..c1d7cd5d 100644 --- a/tests/test_pdfmorphapp.py +++ b/tests/test_pdfmorphapp.py @@ -4,7 +4,11 @@ import pytest -from diffpy.pdfmorph.pdfmorphapp import create_option_parser, multiple_targets, single_morph +from diffpy.pdfmorph.pdfmorphapp import ( + create_option_parser, + multiple_targets, + single_morph, +) thisfile = locals().get("__file__", "file.py") tests_dir = Path(thisfile).parent.resolve() @@ -55,8 +59,25 @@ def test_parser_numerical(self, setup_parser): "--slope", "--qdamp", ] - n_values = ["2.5", "40", "2.1", "-0.8", "0.0000005", "-0.0000005", ".00000003"] - n_names.extend(["--radius", "--pradius", "--iradius", "--ipradius", "--pmin", "--pmax"]) + n_values = [ + "2.5", + "40", + "2.1", + "-0.8", + "0.0000005", + "-0.0000005", + ".00000003", + ] + n_names.extend( + [ + "--radius", + "--pradius", + "--iradius", + "--ipradius", + "--pmin", + "--pmax", + ] + ) n_values.extend(["+0.5", "-0.2", "+.3", "-.1", "2.5", "40"]) n_names.extend(["--lwidth", "--maglim", "--mag"]) n_values.extend(["1.6", "50", "5"]) @@ -72,11 +93,15 @@ def test_parser_numerical(self, setup_parser): n_parsed_name = n_names[idx][2:] n_parsed_val = n_opts_dict.get(n_parsed_name) if n_parsed_val is None: - assert n_parsed_name in renamed_dests # Ensure .get() failed due to destination renaming + assert ( + n_parsed_name in renamed_dests + ) # Ensure .get() failed due to destination renaming n_parsed_name = renamed_dests.get(n_parsed_name) n_parsed_val = n_opts_dict.get(n_parsed_name) assert isinstance(n_parsed_val, float) # Check if value is a float - assert n_parsed_val == float(n_values[idx]) # Check correct value parsed + assert n_parsed_val == float( + n_values[idx] + ) # Check correct value parsed assert len(n_args) == 1 # Check one leftover def test_parser_systemexits(self, setup_parser): @@ -95,12 +120,15 @@ def test_parser_systemexits(self, setup_parser): multiple_targets(self.parser, opts, pargs, stdout_flag=False) # Make sure rmax greater than rmin - (opts, pargs) = self.parser.parse_args([f"{nickel_PDF}", f"{nickel_PDF}", "--rmin", "10", "--rmax", "1"]) + (opts, pargs) = self.parser.parse_args( + [f"{nickel_PDF}", f"{nickel_PDF}", "--rmin", "10", "--rmax", "1"] + ) with pytest.raises(SystemExit): single_morph(self.parser, opts, pargs, stdout_flag=False) # ###Tests exclusive to multiple morphs### - # Make sure we save to a directory that exists (user must create the directory if non-existing) + # Make sure we save to a directory that exists + # (user must create the directory if non-existing) (opts, pargs) = self.parser.parse_args( [ f"{nickel_PDF}", @@ -115,15 +143,21 @@ def test_parser_systemexits(self, setup_parser): multiple_targets(self.parser, opts, pargs, stdout_flag=False) # Ensure first parg is a FILE and second parg is a DIRECTORY - (opts, pargs) = self.parser.parse_args([f"{nickel_PDF}", f"{nickel_PDF}"]) + (opts, pargs) = self.parser.parse_args( + [f"{nickel_PDF}", f"{nickel_PDF}"] + ) with pytest.raises(SystemExit): multiple_targets(self.parser, opts, pargs, stdout_flag=False) - (opts, pargs) = self.parser.parse_args([f"{testsequence_dir}", f"{testsequence_dir}"]) + (opts, pargs) = self.parser.parse_args( + [f"{testsequence_dir}", f"{testsequence_dir}"] + ) with pytest.raises(SystemExit): multiple_targets(self.parser, opts, pargs, stdout_flag=False) # Try sorting by non-existing field - (opts, pargs) = self.parser.parse_args([f"{nickel_PDF}", f"{testsequence_dir}", "--sort-by", "fake_field"]) + (opts, pargs) = self.parser.parse_args( + [f"{nickel_PDF}", f"{testsequence_dir}", "--sort-by", "fake_field"] + ) with pytest.raises(SystemExit): multiple_targets(self.parser, opts, pargs, stdout_flag=False) (opts, pargs) = self.parser.parse_args( @@ -141,14 +175,24 @@ def test_parser_systemexits(self, setup_parser): # Try plotting an unknown parameter (opts, pargs) = self.parser.parse_args( - [f"{nickel_PDF}", f"{testsequence_dir}", "--plot-parameter", "unknown"] + [ + f"{nickel_PDF}", + f"{testsequence_dir}", + "--plot-parameter", + "unknown", + ] ) with pytest.raises(SystemExit): multiple_targets(self.parser, opts, pargs, stdout_flag=False) # Try plotting an unrefined parameter (opts, pargs) = self.parser.parse_args( - [f"{nickel_PDF}", f"{testsequence_dir}", "--plot-parameter", "stretch"] + [ + f"{nickel_PDF}", + f"{testsequence_dir}", + "--plot-parameter", + "stretch", + ] ) with pytest.raises(SystemExit): multiple_targets(self.parser, opts, pargs, stdout_flag=False) @@ -156,7 +200,15 @@ def test_parser_systemexits(self, setup_parser): def test_morphsequence(self, setup_morphsequence): # Parse arguments sorting by field (opts, pargs) = self.parser.parse_args( - ["--scale", "1", "--stretch", "0", "-n", "--sort-by", "temperature"] + [ + "--scale", + "1", + "--stretch", + "0", + "-n", + "--sort-by", + "temperature", + ] ) # Run multiple single morphs @@ -165,11 +217,19 @@ def test_morphsequence(self, setup_morphsequence): for target_file in self.testfiles[1:]: pargs = [morph_file, target_file] # store in same format of dictionary as multiple_targets - single_results.update({target_file.name: single_morph(self.parser, opts, pargs, stdout_flag=False)}) + single_results.update( + { + target_file.name: single_morph( + self.parser, opts, pargs, stdout_flag=False + ) + } + ) pargs = [morph_file, testsequence_dir] # Run a morph sequence - sequence_results = multiple_targets(self.parser, opts, pargs, stdout_flag=False) + sequence_results = multiple_targets( + self.parser, opts, pargs, stdout_flag=False + ) # Compare results assert sequence_results == single_results @@ -190,7 +250,9 @@ def test_morphsequence(self, setup_morphsequence): ] ) pargs = [morph_file, testsequence_dir] - s_sequence_results = multiple_targets(self.parser, opts, pargs, stdout_flag=False) + s_sequence_results = multiple_targets( + self.parser, opts, pargs, stdout_flag=False + ) assert s_sequence_results == sequence_results diff --git a/tests/test_pdfmorphio.py b/tests/test_pdfmorphio.py index d45e3e4a..9794a347 100644 --- a/tests/test_pdfmorphio.py +++ b/tests/test_pdfmorphio.py @@ -4,7 +4,11 @@ import pytest -from diffpy.pdfmorph.pdfmorphapp import create_option_parser, multiple_targets, single_morph +from diffpy.pdfmorph.pdfmorphapp import ( + create_option_parser, + multiple_targets, + single_morph, +) # Support Python 2 try: @@ -116,7 +120,9 @@ def ignore_path(line): # Save a single verbose morph svm = tmp_verbose.joinpath("single_verbose_morph.cgr") svm_name = svm.resolve().as_posix() - (opts, pargs) = self.parser.parse_args(["-s", svm_name, "-n", "--verbose"]) + (opts, pargs) = self.parser.parse_args( + ["-s", svm_name, "-n", "--verbose"] + ) pargs = [morph_file, target_file] single_morph(self.parser, opts, pargs, stdout_flag=False) diff --git a/tests/test_pdfplot.py b/tests/test_pdfplot.py index 012efb1b..c5084f1b 100644 --- a/tests/test_pdfplot.py +++ b/tests/test_pdfplot.py @@ -5,7 +5,11 @@ import matplotlib.pyplot as plt import numpy as np -from diffpy.pdfmorph.pdfmorph_api import morph_default_config, pdfmorph, plot_morph +from diffpy.pdfmorph.pdfmorph_api import ( + morph_default_config, + pdfmorph, + plot_morph, +) from tests.test_morphstretch import heaviside @@ -19,7 +23,9 @@ def test_plot_morph(): x_morph = x_target.copy() y_morph = heaviside(x_target, lb * (1 + stretch), ub * (1 + stretch)) cfg = morph_default_config(stretch=0.1) # off init - morph_rv = pdfmorph(x_morph, y_morph, x_target, y_target, verbose=True, **cfg) + morph_rv = pdfmorph( + x_morph, y_morph, x_target, y_target, verbose=True, **cfg + ) chain = morph_rv["morph_chain"] fig, ax = plt.subplots() l_list = plot_morph(chain, ax) diff --git a/tests/test_refine.py b/tests/test_refine.py index 345d3593..e14ab257 100644 --- a/tests/test_refine.py +++ b/tests/test_refine.py @@ -6,8 +6,12 @@ import numpy import pytest -from diffpy.pdfmorph.morph_helpers.transformpdftordf import TransformXtalPDFtoRDF -from diffpy.pdfmorph.morph_helpers.transformrdftopdf import TransformXtalRDFtoPDF +from diffpy.pdfmorph.morph_helpers.transformpdftordf import ( + TransformXtalPDFtoRDF, +) +from diffpy.pdfmorph.morph_helpers.transformrdftopdf import ( + TransformXtalRDFtoPDF, +) from diffpy.pdfmorph.morphs.morphchain import MorphChain from diffpy.pdfmorph.morphs.morphscale import MorphScale from diffpy.pdfmorph.morphs.morphsmear import MorphSmear @@ -37,7 +41,9 @@ def test_refine_morph(self, setup): } mscale = MorphScale(config) - refiner = Refiner(mscale, self.x_morph, self.y_morph, self.x_target, self.y_target) + refiner = Refiner( + mscale, self.x_morph, self.y_morph, self.x_target, self.y_target + ) refiner.refine() x_morph, y_morph, x_target, y_target = mscale.xyallout @@ -60,7 +66,9 @@ def test_refine_chain(self, setup): mstretch = MorphStretch(config) chain = MorphChain(config, mscale, mstretch) - refiner = Refiner(chain, self.x_morph, self.y_morph, self.x_target, self.y_target) + refiner = Refiner( + chain, self.x_morph, self.y_morph, self.x_target, self.y_target + ) res = refiner.refine() # Compare the morph to the target. Note that due to @@ -82,9 +90,13 @@ class TestRefineUC: @pytest.fixture def setup(self): morph_file = os.path.join(testdata_dir, "nickel_ss0.01.cgr") - self.x_morph, self.y_morph = numpy.loadtxt(morph_file, unpack=True, skiprows=8) + self.x_morph, self.y_morph = numpy.loadtxt( + morph_file, unpack=True, skiprows=8 + ) target_file = os.path.join(testdata_dir, "nickel_ss0.02_eps0.002.cgr") - self.x_target, self.y_target = numpy.loadtxt(target_file, unpack=True, skiprows=8) + self.x_target, self.y_target = numpy.loadtxt( + target_file, unpack=True, skiprows=8 + ) self.y_target *= 1.5 return @@ -105,7 +117,9 @@ def test_refine(self, setup): chain.append(MorphSmear()) chain.append(TransformXtalRDFtoPDF()) - refiner = Refiner(chain, self.x_morph, self.y_morph, self.x_target, self.y_target) + refiner = Refiner( + chain, self.x_morph, self.y_morph, self.x_target, self.y_target + ) # Do this as two-stage fit. First refine amplitude parameters, and then # position parameters. diff --git a/tests/test_tools.py b/tests/test_tools.py index 7bf79e13..12d061d1 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -77,13 +77,16 @@ def test_field_sort(self, setup): path_sequence = [] for file in absolute_sf: path_sequence.append(Path(file).absolute()) - sorted_path_sequence, fvs = tools.field_sort(path_sequence, "temperature", get_field_values=True) + sorted_path_sequence, fvs = tools.field_sort( + path_sequence, "temperature", get_field_values=True + ) sorted_sequence = [] for path in sorted_path_sequence: print(path) sorted_sequence.append(path.name) - # Temperature sort should produce same result as alphanumerical if leading character is removed + # Temperature sort should produce same result as alphanumerical if + # leading character is removed sequence_files.sort(key=lambda entry: entry[2:]) assert sequence_files == sorted_sequence @@ -91,7 +94,9 @@ def test_field_sort(self, setup): assert fvs == [174, 180, 186, 192, 198, 204, 210] # Now reverse the sort - reversed_path_sequence = tools.field_sort(path_sequence, "temperature", reverse=True) + reversed_path_sequence = tools.field_sort( + path_sequence, "temperature", reverse=True + ) reversed_sequence = [] for path in reversed_path_sequence: reversed_sequence.append(path.name) @@ -100,9 +105,14 @@ def test_field_sort(self, setup): sequence_files.sort() assert sequence_files == reversed_sequence - # Check we get the same sequence when we load header information from a serial file - serial_file = os.path.join(testdata_dir, "testsequence_serialfile.json") - metadata_path_sequence = tools.field_sort(path_sequence, "temperature", serfile=serial_file, reverse=True) + # Check we get the same sequence when we load header information from + # a serial file + serial_file = os.path.join( + testdata_dir, "testsequence_serialfile.json" + ) + metadata_path_sequence = tools.field_sort( + path_sequence, "temperature", serfile=serial_file, reverse=True + ) metadata_sequence = [] for path in metadata_path_sequence: metadata_sequence.append(path.name) @@ -132,7 +142,12 @@ def test_get_values_from_dictionary_collection(self): # Check get_values_from_dictionary_collection output gives target for collection_type in all_types: - assert tools.get_values_from_dictionary_collection(collection_type, target_key="target") == target_list + assert ( + tools.get_values_from_dictionary_collection( + collection_type, target_key="target" + ) + == target_list + ) # End of class TestTools diff --git a/tests/test_version.py b/tests/test_version.py index db948e0e..21318422 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -5,6 +5,7 @@ def test_package_version(): - """Ensure the package version is defined and not set to the initial placeholder.""" + """Ensure the package version is defined and not set to the initial + placeholder.""" assert hasattr(diffpy.pdfmorph, "__version__") assert diffpy.pdfmorph.__version__ != "0.0.0"