Skip to content

Do not allow an empty instance of DiffractionObject - require xarrays yarrays xtype #228

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

Merged
merged 33 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
92a2c4c
Add do_minimal setup to conftest
bobleesj Dec 14, 2024
56541e5
Refactor and remove input_data func
bobleesj Dec 14, 2024
1144d1f
Re-organize test with required parameters of xarray, yarray, waveleng…
bobleesj Dec 14, 2024
e7b4dc2
Add input public data back to init func
bobleesj Dec 14, 2024
6dc2738
Remove print diff function in pytest for debugging
bobleesj Dec 14, 2024
50b7d0d
Update input_data docstrings for clarity
bobleesj Dec 14, 2024
291cc89
Add quotation marks around q, tth, d in docstrings
bobleesj Dec 14, 2024
b8a9adc
Use empty string for default scattering quantity
bobleesj Dec 14, 2024
e3c5208
Final cleanup on input_data docstrings
bobleesj Dec 14, 2024
db89596
Merge branch 'main' into no-empty-object
bobleesj Dec 14, 2024
de6f12d
make wavelength optional, test invalid init args
bobleesj Dec 14, 2024
7ccebe8
Add wavelength user behavior back
bobleesj Dec 14, 2024
2752578
[pre-commit.ci] auto fixes from pre-commit hooks
pre-commit-ci[bot] Dec 14, 2024
d1f767b
Restore wavelenght none in one of the test cases
bobleesj Dec 14, 2024
afe814b
Fix typo in docstrings
bobleesj Dec 14, 2024
46c7be8
Use refined param names for pytest parametrize
bobleesj Dec 14, 2024
d24aacd
Remove hard coded wavelength in test func
bobleesj Dec 14, 2024
e285214
Add news for requires 3 input parameters of , ,
bobleesj Dec 14, 2024
4f54439
Merge branch 'main' into no-empty-object
bobleesj Dec 15, 2024
7a2603e
[pre-commit.ci] auto fixes from pre-commit hooks
pre-commit-ci[bot] Dec 15, 2024
8693153
Add metadata data by materials scientist
bobleesj Dec 16, 2024
e68b59e
Add name example to diffraction object example
bobleesj Dec 16, 2024
235f624
Order x, y, xtype for _set_arrays
bobleesj Dec 16, 2024
88ea61b
Remove 0.71 harded coded wavelength
bobleesj Dec 16, 2024
6b20c1a
Add wavelength of 0.711 and 0.71 back
bobleesj Dec 16, 2024
9f0f11a
[pre-commit.ci] auto fixes from pre-commit hooks
pre-commit-ci[bot] Dec 16, 2024
aa1af39
Final setup of wavelenth value comparison
bobleesj Dec 16, 2024
d09d7fb
news tweak
sbillinge Dec 16, 2024
0e362c1
tweak name example
sbillinge Dec 16, 2024
c142240
tweak array length error message
sbillinge Dec 16, 2024
24b1bbc
fix missing quote
sbillinge Dec 16, 2024
2c51af1
fix error message check
sbillinge Dec 16, 2024
ba4b985
[pre-commit.ci] auto fixes from pre-commit hooks
pre-commit-ci[bot] Dec 16, 2024
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
23 changes: 23 additions & 0 deletions news/no-empty-object.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* <news item>

**Changed:**

* `DiffractionObject` requires 3 input parameters of `xarrays`, `yarrays`, `xtype`

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
154 changes: 77 additions & 77 deletions src/diffpy/utils/diffraction_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,82 @@ def _setter_wmsg(attribute):


class DiffractionObject:
"""
Initialize a DiffractionObject instance.

Parameters
----------
xarray : array-like
The independent variable array containing "q", "tth", or "d" values.
yarray : array-like
The dependent variable array corresponding to intensity values.
xtype : str
The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}.
wavelength : float, optional
The wavelength of the incoming beam, specified in angstroms (Å). Default is none.
scat_quantity : str, optional
The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "".
name : str, optional
The name or label for the scattering data. Default is an empty string "".
metadata : dict, optional
The additional metadata associated with the diffraction object. Default is {}.

Examples
--------
Create a DiffractionObject for X-ray scattering data:

>>> import numpy as np
>>> from diffpy.utils.diffraction_objects import DiffractionObject
...
>>> x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q)
>>> y = np.array([10, 20, 40, 60]) # intensity values
>>> metadata = {
... "package_info": {"version": "3.6.0"}
Copy link
Contributor

Choose a reason for hiding this comment

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

this could only have been come up with by a programmer. Could we instead have an example of what we are after from materials scientists. Model some good behavior. Something like metadata = {'sample': "rock salt from the beach", "composition": "NaCl", "temperature": "300 K,", "experimenters": "Phill, Sally" }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Screenshot 2024-12-15 at 9 12 12 PM

Done

... }
>>> do = DiffractionObject(
... xarray=x,
... yarray=y,
... xtype="q",
... wavelength=1.54,
... scat_quantity="x-ray",
... metadata=metadata
... )
>>> print(do.metadata)
"""

def __init__(
self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype=None
self,
xarray,
yarray,
xtype,
wavelength=None,
scat_quantity="",
name="",
metadata={},
):
if name is None:
name = ""
self.name = name
if metadata is None:
metadata = {}
self.metadata = metadata
if xtype is None:
xtype = ""
self.scat_quantity = scat_quantity
self.wavelength = wavelength

if xarray is None:
xarray = np.empty(0)
if yarray is None:
yarray = np.empty(0)

self._id = uuid.uuid4()
self.input_data(xarray, yarray, xtype)
self._input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata)

def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata):
if xtype not in XQUANTITIES:
raise ValueError(_xtype_wmsg(xtype))

# Check xarray and yarray have the same length
if len(xarray) != len(yarray):
raise ValueError(
"'xarray' and 'yarray' must have the same length. "
"Please re-initialize 'DiffractionObject' or re-run the method 'input_data' "
"with 'xarray' and 'yarray' of identical length."
)

self.scat_quantity = scat_quantity
self.wavelength = wavelength
self.metadata = metadata
self.name = name
self._input_xtype = xtype
self._set_arrays(xarray, xtype, yarray)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

as discussed - private func

self._set_min_max_xarray()

def __eq__(self, other):
if not isinstance(other, DiffractionObject):
Expand Down Expand Up @@ -231,16 +286,16 @@ def get_array_index(self, value, xtype=None):
the index of the value in the array
"""

if xtype is None:
xtype = self._input_xtype
xtype = self._input_xtype
array = self.on_xtype(xtype)[0]
if len(array) == 0:
raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.")
i = (np.abs(array - value)).argmin()
return i

def _set_xarrays(self, xarray, xtype):
def _set_arrays(self, xarray, xtype, yarray):
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we should stay consistent...x, y, xtype order?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

self._all_arrays = np.empty(shape=(len(xarray), 4))
self._all_arrays[:, 0] = yarray
if xtype.lower() in QQUANTITIES:
self._all_arrays[:, 1] = xarray
self._all_arrays[:, 2] = q_to_tth(xarray, self.wavelength)
Expand All @@ -253,70 +308,15 @@ def _set_xarrays(self, xarray, xtype):
self._all_arrays[:, 3] = xarray
self._all_arrays[:, 1] = d_to_q(xarray)
self._all_arrays[:, 2] = d_to_tth(xarray, self.wavelength)

def _set_min_max_xarray(self):
self.qmin = np.nanmin(self._all_arrays[:, 1], initial=np.inf)
self.qmax = np.nanmax(self._all_arrays[:, 1], initial=0.0)
self.tthmin = np.nanmin(self._all_arrays[:, 2], initial=np.inf)
self.tthmax = np.nanmax(self._all_arrays[:, 2], initial=0.0)
self.dmin = np.nanmin(self._all_arrays[:, 3], initial=np.inf)
self.dmax = np.nanmax(self._all_arrays[:, 3], initial=0.0)

def input_data(
self,
xarray,
yarray,
xtype,
metadata={},
scat_quantity=None,
name=None,
wavelength=None,
):
f"""
insert a new scattering quantity into the scattering object

Parameters
----------
xarray array-like of floats
the independent variable array
yarray array-like of floats
the dependent variable array
xtype string
the type of quantity for the independent variable from {*XQUANTITIES, }
metadata, scat_quantity, name and wavelength are optional. They have the same
meaning as in the constructor. Values will only be overwritten if non-empty values are passed.

Returns
-------
Nothing. Updates the object in place.

"""

# Check xarray and yarray have the same length
if len(xarray) != len(yarray):
raise ValueError(
"'xarray' and 'yarray' must have the same length. "
"Please re-initialize 'DiffractionObject' or re-run the method 'input_data' "
"with 'xarray' and 'yarray' of identical length."
)

self._set_xarrays(xarray, xtype)
self._all_arrays[:, 0] = yarray
self._input_xtype = xtype
# only update these optional values if non-empty quantities are passed to avoid overwriting
# valid data inadvertently
if metadata:
self.metadata = metadata
if scat_quantity is not None:
self.scat_quantity = scat_quantity
if name is not None:
self.name = name
if wavelength is not None:
self.wavelength = wavelength

# Check xtype is valid. An empty string is the default value.
if xtype != "":
if xtype not in XQUANTITIES:
raise ValueError(_xtype_wmsg(xtype))

def _get_original_array(self):
if self._input_xtype in QQUANTITIES:
return self.on_q(), "q"
Expand Down
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ def _load(filename):
return _load


@pytest.fixture
def do_minimal():
# Create an instance of DiffractionObject with empty xarray and yarray values, and a non-empty wavelength
return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="tth", wavelength=1.54)


@pytest.fixture
def do_minimal_tth():
# Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values
return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth")
Loading
Loading