-
Notifications
You must be signed in to change notification settings - Fork 18
test/func: created a test and function for MorphFuncy #186
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
**Added:** | ||
|
||
* General morph function that applies a user-supplied Python function to the y-coordinates of morph data | ||
|
||
**Changed:** | ||
|
||
* <news item> | ||
|
||
**Deprecated:** | ||
|
||
* <news item> | ||
|
||
**Removed:** | ||
|
||
* <news item> | ||
|
||
**Fixed:** | ||
|
||
* <news item> | ||
|
||
**Security:** | ||
|
||
* <news item> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from diffpy.morph.morphs.morph import LABEL_GR, LABEL_RA, Morph | ||
|
||
|
||
class MorphFuncy(Morph): | ||
"""Apply the user-supplied Python function to the y-coordinates of the | ||
morph data""" | ||
|
||
# Define input output types | ||
summary = "Apply a Python function to the y-axis data" | ||
xinlabel = LABEL_RA | ||
yinlabel = LABEL_GR | ||
xoutlabel = LABEL_RA | ||
youtlabel = LABEL_GR | ||
|
||
def morph(self, x_morph, y_morph, x_target, y_target): | ||
"""General morph function that applies a user-supplied function to the | ||
y-coordinates of morph data to make it align with a target. | ||
|
||
Configuration Variables | ||
----------------------- | ||
function: callable | ||
The user-supplied function that applies a transformation to the | ||
y-coordinates of the data. | ||
|
||
parameters: dict | ||
A dictionary of parameters to pass to the function. | ||
These parameters are unpacked using **kwargs. | ||
|
||
Returns | ||
------- | ||
A tuple (x_morph_out, y_morph_out, x_target_out, y_target_out) | ||
where the target values remain the same and the morph data is | ||
transformed according to the user-specified function and parameters | ||
The morphed data is returned on the same grid as the unmorphed data | ||
|
||
Example | ||
------- | ||
Import the funcy morph function: | ||
>>> from diffpy.morph.morphs.morphfuncy import MorphFuncy | ||
Define or import the user-supplied transformation function: | ||
>>> def sine_function(x, y, amplitude, frequency): | ||
>>> return amplitude * np.sin(frequency * x) * y | ||
Provide initial guess for parameters: | ||
>>> parameters = {'amplitude': 2, 'frequency': 2} | ||
Run the funcy morph given input morph array (x_morph, y_morph) | ||
and target array (x_target, y_target): | ||
>>> morph = MorphFuncy() | ||
>>> morph.function = sine_function | ||
>>> morph.parameters = parameters | ||
>>> x_morph_out, y_morph_out, x_target_out, y_target_out = morph( | ||
... x_morph, y_morph, x_target, y_target) | ||
To access parameters from the morph instance: | ||
>>> x_morph_in = morph.x_morph_in | ||
>>> y_morph_in = morph.y_morph_in | ||
>>> x_target_in = morph.x_target_in | ||
>>> y_target_in = morph.y_target_in | ||
>>> parameters_out = morph.parameters | ||
""" | ||
Morph.morph(self, x_morph, y_morph, x_target, y_target) | ||
|
||
self.y_morph_out = self.function( | ||
self.x_morph_in, self.y_morph_in, **self.parameters | ||
) | ||
|
||
return self.xyallout |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import numpy as np | ||
import pytest | ||
|
||
from diffpy.morph.morphs.morphfuncy import MorphFuncy | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these functions would be better with single blank lines between them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am trying to delete these but every time I commit the files are modified by the hooks. I think this is the pre-commit? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK,, we can let it slide. It is because normally we want the functions well separated, but wehn we are defining a bunch of them for testing we just want them kind of closed up, but it is not a big deal. Just fix the docstring and I can merge this. |
||
|
||
def sine_function(x, y, amplitude, frequency): | ||
return amplitude * np.sin(frequency * x) * y | ||
|
||
|
||
def exponential_decay_function(x, y, amplitude, decay_rate): | ||
return amplitude * np.exp(-decay_rate * x) * y | ||
|
||
|
||
def gaussian_function(x, y, amplitude, mean, sigma): | ||
return amplitude * np.exp(-((x - mean) ** 2) / (2 * sigma**2)) * y | ||
|
||
|
||
def polynomial_function(x, y, a, b, c): | ||
return (a * x**2 + b * x + c) * y | ||
|
||
|
||
def logarithmic_function(x, y, scale): | ||
return scale * np.log(1 + x) * y | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"function, parameters, expected_function", | ||
[ | ||
( | ||
sine_function, | ||
{"amplitude": 2, "frequency": 5}, | ||
lambda x, y: 2 * np.sin(5 * x) * y, | ||
), | ||
( | ||
exponential_decay_function, | ||
{"amplitude": 5, "decay_rate": 0.1}, | ||
lambda x, y: 5 * np.exp(-0.1 * x) * y, | ||
), | ||
( | ||
gaussian_function, | ||
{"amplitude": 1, "mean": 5, "sigma": 1}, | ||
lambda x, y: np.exp(-((x - 5) ** 2) / (2 * 1**2)) * y, | ||
), | ||
( | ||
polynomial_function, | ||
{"a": 1, "b": 2, "c": 0}, | ||
lambda x, y: (x**2 + 2 * x) * y, | ||
), | ||
( | ||
logarithmic_function, | ||
{"scale": 0.5}, | ||
lambda x, y: 0.5 * np.log(1 + x) * y, | ||
), | ||
], | ||
) | ||
def test_funcy(function, parameters, expected_function): | ||
x_morph = np.linspace(0, 10, 101) | ||
y_morph = np.sin(x_morph) | ||
x_target = x_morph.copy() | ||
y_target = y_morph.copy() | ||
x_morph_expected = x_morph | ||
y_morph_expected = expected_function(x_morph, y_morph) | ||
morph = MorphFuncy() | ||
morph.function = function | ||
morph.parameters = parameters | ||
x_morph_actual, y_morph_actual, x_target_actual, y_target_actual = ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason you have the function call inside parens? It should work the same way without these and this is somewhat unconventional and therefore distracting? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is same as comment above, this is modified by the hooks. |
||
morph.morph(x_morph, y_morph, x_target, y_target) | ||
) | ||
assert np.allclose(y_morph_actual, y_morph_expected) | ||
assert np.allclose(x_morph_actual, x_morph_expected) | ||
assert np.allclose(x_target_actual, x_target) | ||
assert np.allclose(y_target_actual, y_target) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this would be easier to read with judiciously placed blank lines, maybe after each code block, but play around till it looks nice? I am not sure if there is a docstring standard for this, in which case follow it, but if not, then I think a few blank lines could increase readability.