Skip to content

Change libraries #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion diffpy/pdfmorph/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"""

# obtain version information
__version__ = '0.0.1'
from diffpy.pdfmorph.version import __version__

# top-level import
from diffpy.pdfmorph.pdfmorph_api import pdfmorph, morph_default_config, plot_morph
Expand Down
199 changes: 170 additions & 29 deletions diffpy/pdfmorph/pdfmorphapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@
from __future__ import print_function

import sys
import os
import os.path
from pathlib import Path

import numpy
from diffpy.pdfmorph import __version__
from diffpy.pdfmorph.version import __version__
import diffpy.pdfmorph.tools as tools
import diffpy.pdfmorph.pdfplot as pdfplot
import diffpy.pdfmorph.morphs as morphs
Expand All @@ -31,6 +29,7 @@

def create_option_parser():
import optparse
prog_short = Path(sys.argv[0]).name # Program name, compatible w/ all OS paths

class CustomParser(optparse.OptionParser):
def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -62,9 +61,27 @@ def custom_error(self, msg):
parser.add_option(
'-s',
'--save',
metavar="FILE",
metavar="SFILE",
dest="savefile",
help="Save manipulated PDF from FILE1 to FILE. Use \'-\' for stdout.",
help="Save manipulated PDF from FILE1 to SFILE. Use \'-\' for stdout.",
)
parser.add_option(
'--sequence',
dest="sequence",
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
--temperature is enabled. Plotting and saving options are for
a Rw plot and table respectively.""",
)
parser.add_option(
'--temperature',
dest="temp",
action="store_true",
help="""Used with --sequence to sort files in DIRECTORY by temperature.
File names in DIRECTORY should end in _#K.gr or _#K.cgr
to use this option.""",
)
parser.add_option(
'--rmin', type="float", help="Minimum r-value to use for PDF comparisons."
Expand Down Expand Up @@ -179,12 +196,6 @@ def custom_error(self, msg):
dest="plot",
help="Do not show the plot.",
)
group.add_option(
'--usefilenames',
action="store_true",
dest="usefilenames",
help="Use the file names as labels on plot."
)
group.add_option(
'--mlabel',
metavar="MLABEL",
Expand Down Expand Up @@ -214,6 +225,7 @@ def custom_error(self, msg):
)

# Defaults
parser.set_defaults(sequence=False)
parser.set_defaults(plot=True)
parser.set_defaults(refine=True)
parser.set_defaults(pearson=False)
Expand All @@ -227,9 +239,130 @@ def custom_error(self, msg):
def main():
parser = create_option_parser()
(opts, pargs) = parser.parse_args()
if opts.sequence:
multiple_morphs(parser, opts, pargs, stdout_flag=True)
else:
single_morph(parser, opts, pargs, stdout_flag=True)


def multiple_morphs(parser, opts, pargs, stdout_flag):
# Custom error messages since usage is distinct when --sequence tag is applied
if len(pargs) < 2:
parser.custom_error("You must supply FILE and DIRECTORY. Go to --help for usage.")
elif len(pargs) > 2:
parser.custom_error("Too many arguments. Go to --help for usage.")

# 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.")
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.")

# Sort files in directory
target_list = list(target_directory.iterdir())
if opts.temp:
# Sort by temperature
try:
target_list = tools.temperature_sort(target_list)
except ValueError:
parser.custom_error("All file names in directory must end in _###K.gr or _###K.cgr to use "
"the --temperature option.")
else:
# Default is alphabetical sort
target_list.sort()

# Disable single morph plotting and saving
plot_opt = opts.plot
opts.plot = False
save_opt = opts.savefile
opts.savefile = None

# Do not morph morph_file against itself if it is in the same directory
if morph_file in target_list:
target_list.remove(morph_file)

# Morph morph_file against all other files in target_directory
results = []
for target_file in target_list:
if target_file.is_file:
results.append([
target_file.name,
single_morph(parser, opts, [morph_file, target_file], stdout_flag=False),
])

# Input parameters used for every morph
inputs = [None, None]
inputs[0] = f"# Morphed file: {morph_file.name}"
inputs[1] = "\n# Input morphing parameters:"
inputs[1] += f"\n# scale = {opts.scale}"
inputs[1] += f"\n# stretch = {opts.stretch}"
inputs[1] += f"\n# smear = {opts.smear}"

# If print enabled
if stdout_flag:
# Separated for saving
print(f"\n{inputs[0]}{inputs[1]}")

# Results from each morph
for entry in results:
outputs = f"\n# Target: {entry[0]}\n"
outputs += "# Optimized morphing parameters:\n"
outputs += "\n".join(f"# {i[0]} = {i[1]:.6f}" for i in entry[1])
print(outputs)

rws = []
target_labels = []
results_length = len(results)
for entry in results:
if opts.temp:
name = entry[0]
target_labels.append(tools.extract_temperatures([name])[0])
else:
target_labels.append(entry[0])
for item in entry[1]:
if item[0] == "Rw":
rws.append(item[1])

if len(pargs) != 2:
parser.error("You must supply FILE1 and FILE2")
if save_opt is not None:
# Save table of Rw values
try:
with open(save_opt, 'w') as outfile:
# Header
print(f"{inputs[0]}\n{inputs[1]}", file=outfile)
if opts.temp:
print(f"\n# L T(K) Rw", file=outfile)
else:
print(f"\n# L Target Rw", file=outfile)

# Table
for idx in range(results_length):
print(f"{target_labels[idx]} {rws[idx]}", file=outfile)
outfile.close()

# Output to stdout
path_name = Path(outfile.name).absolute()
save_message = f"\n# Rw table saved to {path_name}"
print(save_message)

# Save failed
except FileNotFoundError as e:
save_fail_message = "\nUnable to save to designated location"
print(save_fail_message)
parser.custom_error(str(e))

if plot_opt:
pdfplot.plot_rws(target_labels, rws, opts.temp)

return results


def single_morph(parser, opts, pargs, stdout_flag):
if len(pargs) < 2:
parser.error("You must supply FILE1 and FILE2.")
elif len(pargs) > 2:
parser.error("Too many arguments.")

# Get the PDFs
x_morph, y_morph = getPDFFromFile(pargs[0])
Expand Down Expand Up @@ -352,15 +485,23 @@ def main():
morphs_in += f"\n# scale = {scale_in}"
morphs_in += f"\n# stretch = {stretch_in}"
morphs_in += f"\n# smear = {smear_in}"
print(morphs_in)

items = list(config.items())
items.sort()
# Output morph parameters
morph_results = list(config.items())
morph_results.sort()

# Ensure Rw, Pearson last two outputs
morph_results.append(("Rw", rw))
morph_results.append(("Pearson", pcc))

output = "\n# Optimized morphing parameters:\n"
output += "\n".join(f"# {i[0]} = {i[1]:.6f}" for i in items)
output += f"\n# Rw = {rw:.6f}"
output += f"\n# Pearson = {pcc:.6f}"
print(output)
output += "\n".join(f"# {i[0]} = {i[1]:.6f}" for i in morph_results)

# No stdout output when running morph sequence
if stdout_flag:
print(morphs_in)
print(output)

if opts.savefile is not None:
path_name = Path(pargs[0]).absolute()
header = "# PDF created by pdfmorph\n"
Expand Down Expand Up @@ -394,14 +535,14 @@ def main():

if opts.plot:
pairlist = [chain.xy_morph_out, chain.xy_target_out]
labels = ["morph", "target"] # Default label names
if opts.usefilenames:
labels = [pargs[0], pargs[1]]
else:
if opts.mlabel is not None:
labels[0] = opts.mlabel
if opts.tlabel is not None:
labels[1] = opts.tlabel
labels = [pargs[0], pargs[1]] # Default is to use file names

# If user chooses labels
if opts.mlabel is not None:
labels[0] = opts.mlabel
if opts.tlabel is not None:
labels[1] = opts.tlabel

# Plot extent defaults to calculation extent
pmin = opts.pmin if opts.pmin is not None else opts.rmin
pmax = opts.pmax if opts.pmax is not None else opts.rmax
Expand All @@ -412,7 +553,7 @@ def main():
pairlist, labels, rmin=pmin, rmax=pmax, maglim=maglim, mag=mag, rw=rw, l_width=l_width
)

return
return morph_results


def getPDFFromFile(fn):
Expand Down
47 changes: 47 additions & 0 deletions diffpy/pdfmorph/pdfplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"""Collection of plotting functions for PDFs."""

import matplotlib.pyplot as plt
import diffpy.pdfmorph.tools as tools
from bg_mpl_stylesheet.bg_mpl_stylesheet import bg_mpl_style
import numpy

Expand Down Expand Up @@ -207,6 +208,52 @@ def comparePDFs(
return


def plot_rws(target_labels, rws, temp_flag=False):
"""
Plot Rw values for multiple morphs.
:param target_labels: Names (or temperatures if --temperature is enabled) of each file acting as target for the morph.
:type target_labels: list
:param rws: Contains the Rw values corresponding to each file.
:type rws: list
:param temp_flag: When True, temperature is extracted from file names in target_files. Then, a line chart of
Rw versus temperature is made. Otherwise (default), it plots a bar chart of Rw values per file.
:type temp_flag: bool
"""

# If we can extract temperature data
if temp_flag:
temps = target_labels

# Plot Rw vs Temperature
plt.plot(temps, rws, linestyle='-', marker='o')
plt.ylabel(r"$R_w$")
plt.xlabel(r"Temperature ($K$)")
plt.minorticks_on()

# Create bar chart for each file
else:
file_names = target_labels
# Ensure file names do not crowd
bar_size = 5
max_len = bar_size
for name in file_names:
max_len = max(max_len, len(name))
angle = numpy.arccos(bar_size / max_len)
angle *= 180 / numpy.pi # Convert to degrees
plt.xticks(rotation=angle)

# Plot Rw for each file
plt.bar(file_names, rws)
plt.ylabel(r"$R_w$")
plt.xlabel(r"Target File")

# Show plot
plt.tight_layout()
plt.show()

return


def truncatePDFs(r, gr, rmin=None, rmax=None):
"""Truncate a PDF to specified bounds.

Expand Down
27 changes: 14 additions & 13 deletions diffpy/pdfmorph/tests/test_morphchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


import os
import unittest
import pytest

import numpy

Expand All @@ -16,16 +16,17 @@
from diffpy.pdfmorph.morphs.morphrgrid import MorphRGrid


class TestMorphChain(unittest.TestCase):
def setUp(self):
class TestMorphChain:
@pytest.fixture
def setup(self):
self.x_morph = numpy.arange(0.01, 5, 0.01)
self.y_morph = numpy.ones_like(self.x_morph)
self.x_target = numpy.arange(0.01, 5, 0.01)
self.y_target = 3 * numpy.ones_like(self.x_target)

return

def test_morph(self):
def test_morph(self, setup):
"""check MorphChain.morph()"""
# Define the morphs
config = {
Expand All @@ -41,20 +42,20 @@ def test_morph(self):

x_morph, y_morph, x_target, y_target = chain(self.x_morph, self.y_morph, self.x_target, self.y_target)

self.assertTrue((x_morph == x_target).all())
self.assertAlmostEqual(x_morph[0], 1.0)
self.assertAlmostEqual(x_morph[-1], 4.9)
self.assertAlmostEqual(x_morph[1] - x_morph[0], 0.1)
self.assertAlmostEqual(x_morph[0], mgrid.rmin)
self.assertAlmostEqual(x_morph[-1], mgrid.rmax - mgrid.rstep)
self.assertAlmostEqual(x_morph[1] - x_morph[0], mgrid.rstep)
self.assertTrue(numpy.allclose(y_morph, y_target))
assert (x_morph == x_target).all()
pytest.approx(x_morph[0], 1.0)
pytest.approx(x_morph[-1], 4.9)
pytest.approx(x_morph[1] - x_morph[0], 0.1)
pytest.approx(x_morph[0], mgrid.rmin)
pytest.approx(x_morph[-1], mgrid.rmax - mgrid.rstep)
pytest.approx(x_morph[1] - x_morph[0], mgrid.rstep)
assert numpy.allclose(y_morph, y_target)
return


# End of class TestMorphChain

if __name__ == '__main__':
unittest.main()
TestMorphChain()

# End of file
Loading