Skip to content
Merged
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
8 changes: 6 additions & 2 deletions docs/source/morphpy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Python Morphing Functions

* ``morph_info`` contains all morphs as keys (e.g. ``"scale"``, ``"stretch"``, ``"smear"``) with
the optimized morphing parameters found by ``diffpy.morph`` as values. ``morph_info`` also contains
the Rw and Pearson correlation coefficients found post-morphing. Try printing ``print(morph_info)``
the Rw and Pearson correlation coefficients found post-morphing. Try printing ``print(morph_info)``
and compare the values stored in this dictionary to those given by the CLI output!
* ``morph_table`` is a two-column array of the morphed function interpolated onto the grid of the
target function (e.g. in our example, it returns the contents of `darkSub_rh20_C_01.gr` after
Expand Down Expand Up @@ -74,6 +74,10 @@ General Parameters

save: str or path
Save the morphed function to a the file passed to save. Use '-' for stdout.
get_diff: bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we want to do either/or here. A clearer logic would be that diff is saved if this is true, not saved if it is false, and also the case for the morph function. So TT would save both. To keep the API cleaner, we could have defaults of TF for morph and diff (the current behavior).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, created #238 since I think we should have a discussion on this.

Return the difference function (morphed function minus target function) instead of
the morphed function (default). When save is enabled, the difference function
is saved instead of the morphed function.
verbose: bool
Print additional header details to saved files. These include details about the morph
inputs and outputs.
Expand Down Expand Up @@ -240,4 +244,4 @@ As you can see, the fitted scale and offset values match the ones used
to generate the target (scale=20 & offset=0.8). This example shows how
``MorphFuncy`` can be used to fit and apply custom transformations. Now
it's your turn to experiment with other custom functions that may be useful
for analyzing your data.
for analyzing your data.
23 changes: 23 additions & 0 deletions news/save_diff.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* There is now an option to save the difference curve. This is computed on the common interval between the two curves.

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
18 changes: 9 additions & 9 deletions src/diffpy/morph/morph_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ 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
Name of the morphed function 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
Expand Down Expand Up @@ -144,7 +144,7 @@ def single_morph_output(


def create_morphs_directory(save_directory):
"""Create a directory for saving multiple morphed PDFs.
"""Create a directory for saving multiple morphed functions.

Takes in a user-given path to a directory save_directory and create a
subdirectory named Morphs. diffpy.morph will save all morphs into the
Expand Down Expand Up @@ -183,7 +183,7 @@ def get_multisave_names(target_list: list, save_names_file=None, mm=False):
Parameters
----------
target_list: list
Target (or Morph if mm enabled) PDFs used for each morph.
Target (or Morph if mm enabled) functions used for each morph.
save_names_file
Name of file to import save names dictionary from (default None).
mm: bool
Expand All @@ -192,8 +192,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 function file
names used to produce that morph.
"""

# Dictionary storing save file names
Expand Down Expand Up @@ -252,20 +252,20 @@ def multiple_morph_output(
morph_results: dict
Resulting data after morphing.
target_files: list
PDF files that acted as targets to morphs.
Files that acted as targets to morphs.
save_directory
Name of directory to save morphs in.
field
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.
List of field values for each target function.
Generated by diffpy.morph.tools.field_sort().
morph_file
Name of the morphed PDF file.
Name of the morphed function file.
Required to give summary data after saving to a directory.
target_directory
Name of the directory containing the target PDF files.
Name of the directory containing the target function files.
Required to give summary data after saving to a directory.
verbose: bool
Print additional summary details when True (default False).
Expand Down
54 changes: 40 additions & 14 deletions src/diffpy/morph/morphapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,27 @@ def custom_error(self, msg):
metavar="NAME",
dest="slocation",
help=(
"Save the manipulated PDF to a file named NAME. "
"Save the manipulated function to a file named NAME. "
"Use '-' for stdout.\n"
"When --multiple-<targets/morphs> 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 each manipulated function as a file in a directory "
"named NAME;\n"
"you can specify names for each saved function file using "
"--save-names-file."
),
)
parser.add_option(
"--diff",
"--get-diff",
dest="get_diff",
action="store_true",
help=(
"Save the difference curve rather than the manipulated function.\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we do update the logic, update this help here too.

"This is computed as manipulated function minus target function.\n"
"The difference curve is computed on the interval shared by the "
"grid of the objective and target function."
),
)
parser.add_option(
"-v",
"--verbose",
Expand All @@ -99,12 +112,12 @@ def custom_error(self, msg):
parser.add_option(
"--rmin",
type="float",
help="Minimum r-value to use for PDF comparisons.",
help="Minimum r-value (abscissa) to use for function comparisons.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"r" might be a bit confusing in our more general world of functions. Not sure what to change it to (and let's just make an issue, not do it on this PR0. "x" is widely used by scientists. "minimum value on the absisca" might be mathematically more correct? Anyway, just a note to make an issue.....

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I noted this in #236

)
parser.add_option(
"--rmax",
type="float",
help="Maximum r-value to use for PDF comparisons.",
help="Maximum r-value (abscissa) to use for function comparisons.",
)
parser.add_option(
"--tolerance",
Expand Down Expand Up @@ -419,9 +432,9 @@ def custom_error(self, msg):
"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 "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another issue to make as claning before release (not now) would be to do a global search for PDF and root it out wherever it survives. Like a weed......

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, noted in #236, didn't want to do that now

".json serial file is included in the tutorial directory "
"on the package GitHub repository."
"specifies the name to save the manipulated function as."
"An example .json serial file is included in the tutorial "
"directory on the package GitHub repository."
),
)
group.add_option(
Expand Down Expand Up @@ -492,10 +505,7 @@ def single_morph(
smear_in = "None"
hshift_in = "None"
vshift_in = "None"
config = {}
config["rmin"] = opts.rmin
config["rmax"] = opts.rmax
config["rstep"] = None
config = {"rmin": opts.rmin, "rmax": opts.rmax, "rstep": None}
if (
opts.rmin is not None
and opts.rmax is not None
Expand Down Expand Up @@ -708,13 +718,29 @@ def single_morph(
morph_results.update({"Pearson": pcc})

# Print summary to terminal and save morph to file if requested
xy_save = [chain.x_morph_out, chain.y_morph_out]
if opts.get_diff is not None:
diff_chain = morphs.MorphChain(
{"rmin": None, "rmax": None, "rstep": None}
)
diff_chain.append(morphs.MorphRGrid())
diff_chain(
chain.x_morph_out,
chain.y_morph_out,
chain.x_target_in,
chain.y_target_in,
)
xy_save = [
diff_chain.x_morph_out,
diff_chain.y_morph_out - diff_chain.y_target_out,
]
try:
io.single_morph_output(
morph_inputs,
morph_results,
save_file=opts.slocation,
morph_file=pargs[0],
xy_out=[chain.x_morph_out, chain.y_morph_out],
xy_out=xy_save,
verbose=opts.verbose,
stdout_flag=stdout_flag,
)
Expand Down Expand Up @@ -753,7 +779,7 @@ def single_morph(
# Return different things depending on whether it is python interfaced
if python_wrap:
morph_info = morph_results
morph_table = numpy.array([chain.x_morph_out, chain.y_morph_out]).T
morph_table = numpy.array(xy_save).T
return morph_info, morph_table
else:
return morph_results
Expand Down
2 changes: 2 additions & 0 deletions src/diffpy/morph/morphpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def __get_morph_opts__(parser, scale, stretch, smear, plot, **kwargs):
"addpearson",
"apply",
"reverse",
"diff",
"get-diff",
]
opts_to_ignore = ["multiple-morphs", "multiple-targets"]
for opt in opts_storing_values:
Expand Down
77 changes: 77 additions & 0 deletions tests/test_morphio.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
single_morph,
)
from diffpy.morph.morphpy import morph_arrays
from diffpy.utils.parsers.loaddata import loadData

# Support Python 2
try:
Expand Down Expand Up @@ -64,6 +65,29 @@ def are_files_same(file1, file2):
assert f1_arr[idx] == f2_arr[idx]


def are_diffs_right(file1, file2, diff_file):
"""Assert that diff_file ordinate data is approximately file1
ordinate data minus file2 ordinate data."""
f1_data = loadData(file1)
f2_data = loadData(file2)
diff_data = loadData(diff_file)

rmin = max(min(f1_data[:, 0]), min(f1_data[:, 1]))
rmax = min(max(f2_data[:, 0]), max(f2_data[:, 1]))
rnumsteps = max(
len(f1_data[:, 0][(rmin <= f1_data[:, 0]) & (f1_data[:, 0] <= rmax)]),
len(f2_data[:, 0][(rmin <= f2_data[:, 0]) & (f2_data[:, 0] <= rmax)]),
)

share_grid = np.linspace(rmin, rmax, rnumsteps)
f1_interp = np.interp(share_grid, f1_data[:, 0], f1_data[:, 1])
f2_interp = np.interp(share_grid, f2_data[:, 0], f2_data[:, 1])
diff_interp = np.interp(share_grid, diff_data[:, 0], diff_data[:, 1])

for idx, diff in enumerate(diff_interp):
assert np.isclose(f1_interp[idx] - f2_interp[idx], diff)


class TestApp:
@pytest.fixture
def setup(self):
Expand Down Expand Up @@ -165,6 +189,59 @@ def test_morph_outputs(self, setup, tmp_path):
expected = filter(ignore_path, tf)
are_files_same(actual, expected)

# Similar format as test_morph_outputs
def test_morph_diff_outputs(self, setup, tmp_path):
morph_file = self.testfiles[0]
target_file = self.testfiles[-1]

# Save multiple diff morphs
tmp_diff = tmp_path.joinpath("diff")
tmp_diff_name = tmp_diff.resolve().as_posix()

(opts, pargs) = self.parser.parse_args(
[
"--multiple-targets",
"--sort-by",
"temperature",
"-s",
tmp_diff_name,
"-n",
"--save-names-file",
tssf,
"--diff",
]
)
pargs = [morph_file, testsequence_dir]
multiple_targets(self.parser, opts, pargs, stdout_flag=False)

# Save a single diff morph
diff_name = "single_diff_morph.cgr"
diff_file = tmp_diff.joinpath(diff_name)
df_name = diff_file.resolve().as_posix()
(opts, pargs) = self.parser.parse_args(["-s", df_name, "-n", "--diff"])
pargs = [morph_file, target_file]
single_morph(self.parser, opts, pargs, stdout_flag=False)

# Check that the saved diff matches the morph minus target
# Morphs are saved in testdata/testsequence/testsaving/succinct
# Targets are stored in testdata/testsequence

# Single morph diff
morphed_file = test_saving_succinct / diff_name.replace(
"diff", "succinct"
)
are_diffs_right(morphed_file, target_file, diff_file)

# Multiple morphs diff
diff_files = list((tmp_diff / "Morphs").iterdir())
morphed_files = list((test_saving_succinct / "Morphs").iterdir())
target_files = self.testfiles[1:]
diff_files.sort()
morphed_files.sort()
target_files.sort()
for idx, diff_file in enumerate(diff_files):
are_diffs_right(morphed_files[idx], target_files[idx], diff_file)

def test_morphsqueeze_outputs(self, setup, tmp_path):
# The file squeeze_morph has a squeeze and stretch applied
morph_file = testdata_dir / "squeeze_morph.cgr"
Expand Down
2 changes: 2 additions & 0 deletions tests/test_morphpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def test_morph_opts(self, setup_morph):
"addpearson": False,
"apply": False,
"reverse": False,
"get_diff": False,
"multiple_morphs": False,
"multiple_targets": False,
}
Expand All @@ -96,6 +97,7 @@ def test_morph_opts(self, setup_morph):
"addpearson": True,
"apply": True,
"reverse": True,
"get_diff": True,
"multiple_morphs": True,
"multiple_targets": True,
}
Expand Down
Loading