From c0ffe30876f8702faa1acdeff0457013224df89c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 20:26:44 -0400 Subject: [PATCH 001/445] Empty build.txt file which is used for C++/C --- requirements/build.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/requirements/build.txt b/requirements/build.txt index f72d870d..e69de29b 100644 --- a/requirements/build.txt +++ b/requirements/build.txt @@ -1,2 +0,0 @@ -python -setuptools From 3e8dd2348b73887f6604befdeecd9c46ca1ee20b Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 20:32:27 -0400 Subject: [PATCH 002/445] Fix arb value to fix warning --- tests/test_diffraction_objects.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index fa613a53..a410f313 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -236,18 +236,20 @@ def test_dump(tmp_path, mocker): directory = Path(tmp_path) file = directory / "testfile" test = Diffraction_object() - test.wavelength = 1.54 + test.wavelength = 1.01 test.name = "test" test.scat_quantity = "x-ray" test.insert_scattering_quantity( x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) + + with mocker.patch("importlib.metadata.version", return_value="3.3.0"), freeze_time("2012-01-14"): test.dump(file, "q") with open(file, "r") as f: actual = f.read() expected = ( - "[Diffraction_object]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" + "[Diffraction_object]\nname = test\nwavelength = 1.01\nscat_quantity = x-ray\nthing1 = 1\n" "thing2 = thing2\npackage_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n" "creation_time = 2012-01-14 00:00:00\n\n" "#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" @@ -262,4 +264,5 @@ def test_dump(tmp_path, mocker): "9.000000000000000000e+00 9.000000000000000000e+00\n" "1.000000000000000000e+01 1.000000000000000000e+01\n" ) + assert actual == expected From b62fddb46ce88d11814a78e9bb75a022ca8ece52 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 20:42:18 -0400 Subject: [PATCH 003/445] Remove patch warning --- tests/test_diffraction_objects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index a410f313..21b6c66e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -243,9 +243,10 @@ def test_dump(tmp_path, mocker): x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) - - with mocker.patch("importlib.metadata.version", return_value="3.3.0"), freeze_time("2012-01-14"): + mocker.patch("importlib.metadata.version", return_value="3.3.0") + with freeze_time("2012-01-14"): test.dump(file, "q") + with open(file, "r") as f: actual = f.read() expected = ( From 6e2ed38c9f8e3e760fbaf5957dfddbd3027e95ec Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 17 Sep 2024 20:43:51 -0400 Subject: [PATCH 004/445] Add news --- news/warning.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/warning.rst diff --git a/news/warning.rst b/news/warning.rst new file mode 100644 index 00000000..45768802 --- /dev/null +++ b/news/warning.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Two Pytest warnings due to numpy and pytest mocker in test_dump function + +**Security:** + +* From de82a7705e207a16e90639abe75e21adf172676d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 19 Sep 2024 07:16:27 -0400 Subject: [PATCH 005/445] Clean up tests for catching error --- tests/test_diffraction_objects.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 8958c17f..aa99326a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -241,21 +241,28 @@ def test_dump(tmp_path, mocker): test.name = "test" test.scat_quantity = "x-ray" - # Capture warnings due to invalid arcsin input value due to the wavelenght being 1.54 + # Setup the testing environment to capture warnings triggered by an invalid arcsin input with warnings.catch_warnings(record=True) as captured_warnings: + # Configure the warnings system to capture all warnings warnings.simplefilter("always") + + # Perform the method call that is expected to trigger the RuntimeWarning due to the specified wavelength test.insert_scattering_quantity( x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) - # Assert that a RuntimeWarning was raised + # Verify that at least one RuntimeWarning is raised due to the arcsin domain error from wavelength 1.54 assert any( isinstance(w.message, RuntimeWarning) for w in captured_warnings - ), "Expected a RuntimeWarning due to invalid arcsin input value from the wavelenght set to 1.54" - # Expect only one warning message produced - assert len(captured_warnings) == 1 + ), "Expected a RuntimeWarning due to invalid arcsin input value from the wavelength set to 1.54" + + # Ensure that exactly one warning was captured to confirm that no additional unexpected warnings are raised + assert len(captured_warnings) == 1, "Expected exactly one warning to be captured" + + # Patch the version lookup to control the external dependency in the test environment mocker.patch("importlib.metadata.version", return_value="3.3.0") + with freeze_time("2012-01-14"): test.dump(file, "q") From d65aa57402cb4114c896e50d835b3497cc875756 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:16:37 +0000 Subject: [PATCH 006/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aa99326a..7a32b3bf 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -262,7 +262,6 @@ def test_dump(tmp_path, mocker): # Patch the version lookup to control the external dependency in the test environment mocker.patch("importlib.metadata.version", return_value="3.3.0") - with freeze_time("2012-01-14"): test.dump(file, "q") From b9b05a505f06f00ee12638cc4b5fb6ae974ef371 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 19 Sep 2024 07:18:51 -0400 Subject: [PATCH 007/445] Shorten comments for test_diffraction_objects --- tests/test_diffraction_objects.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 7a32b3bf..9e3abe3e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -241,7 +241,6 @@ def test_dump(tmp_path, mocker): test.name = "test" test.scat_quantity = "x-ray" - # Setup the testing environment to capture warnings triggered by an invalid arcsin input with warnings.catch_warnings(record=True) as captured_warnings: # Configure the warnings system to capture all warnings warnings.simplefilter("always") @@ -251,15 +250,14 @@ def test_dump(tmp_path, mocker): x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) - # Verify that at least one RuntimeWarning is raised due to the arcsin domain error from wavelength 1.54 + # Verify that at least one RuntimeWarning is raised due to the arcsin error from wavelength 1.54 assert any( isinstance(w.message, RuntimeWarning) for w in captured_warnings ), "Expected a RuntimeWarning due to invalid arcsin input value from the wavelength set to 1.54" - # Ensure that exactly one warning was captured to confirm that no additional unexpected warnings are raised + # Ensure that exactly one warning was captured assert len(captured_warnings) == 1, "Expected exactly one warning to be captured" - # Patch the version lookup to control the external dependency in the test environment mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): From 3aec5a3f1baf9e0d218f6d6970b5fc45b466325d Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:34:45 -0400 Subject: [PATCH 008/445] Update README --- README.rst | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 777f01bf..f0476d94 100644 --- a/README.rst +++ b/README.rst @@ -80,16 +80,18 @@ To add "conda-forge" to the conda channels, run the following in a terminal. :: We want to install our packages in a suitable conda environment. The following creates and activates a new environment named ``diffpy.utils_env`` :: - conda create -n diffpy.utils_env python=3 + conda create -n diffpy.utils_env diffpy.utils conda activate diffpy.utils_env -Then, to fully install ``diffpy.utils`` in our active environment, run :: +To confirm that the installation was successful, type :: - conda install diffpy.utils + python -c "import diffpy.utils; print(diffpy.utils.__version__)" -Another option is to use ``pip`` to download and install the latest release from +The output should print the latest version displayed on the badges above. + +If the above does not work, you can use ``pip`` to download and install the latest release from `Python Package Index `_. -To install using ``pip`` into your ``diffpy.utils_env`` environment type :: +To install using ``pip`` into your ``diffpy.utils_env`` environment, type :: pip install diffpy.utils @@ -99,12 +101,17 @@ and run the following :: pip install . +Getting Started +--------------- + +You may consult our `online documentation `_ for tutorials and API references. + Support and Contribute ---------------------- `Diffpy user group `_ is the discussion forum for general questions and discussions about the use of diffpy.utils. Please join the diffpy.utils users community by joining the Google group. The diffpy.utils project welcomes your expertise and enthusiasm! -If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. +If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. Feel free to fork the project and contribute. To install diffpy.utils in a development mode, with its sources being directly used by Python From 2fdf24d4a2742f1e1c744057879feaf3d2b69ab5 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:36:54 -0400 Subject: [PATCH 009/445] Add .github/ISSUE_TEMPLATE --- .github/ISSUE_TEMPLATE/bug_feature.md | 16 +++++++++++++++ .github/ISSUE_TEMPLATE/release_checklist.md | 22 +++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_feature.md create mode 100644 .github/ISSUE_TEMPLATE/release_checklist.md diff --git a/.github/ISSUE_TEMPLATE/bug_feature.md b/.github/ISSUE_TEMPLATE/bug_feature.md new file mode 100644 index 00000000..b3454deb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_feature.md @@ -0,0 +1,16 @@ +--- +name: Bug Report or Feature Request +about: Report a bug or suggest a new feature! +title: "" +labels: "" +assignees: "" +--- + +### Problem + + + +### Proposed solution diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md new file mode 100644 index 00000000..a87a44a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -0,0 +1,22 @@ +--- +name: Release +about: Checklist and communication channel for PyPI and GitHub release +title: "Ready for PyPI/GitHub release" +labels: "release" +assignees: "" +--- + +### Release checklist for GitHub contributors + +- [ ] All PRs/issues attached to the release are merged. +- [ ] All the badges on the README are passing. +- [ ] License information is verified as correct. If you are unsure, please comment below. +- [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are + missing), tutorials, and other human written text is up-to-date with any changes in the code. +- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated and + tested +- [ ] Successfully run any tutorial examples or do functional testing in some other way. +- [ ] Grammar and writing quality have been checked (no typos). + +Please mention @sbillinge when you are ready for release. Include any additional comments necessary, such as +version information and details about the pre-release. From b2bad3606dc53c548c49f362b8319703fd180701 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:38:04 -0400 Subject: [PATCH 010/445] Add build-wheel-release-upload.yml --- .github/workflows/build-wheel-release-upload.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/build-wheel-release-upload.yml diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml new file mode 100644 index 00000000..9ea21782 --- /dev/null +++ b/.github/workflows/build-wheel-release-upload.yml @@ -0,0 +1,16 @@ +name: Release (GitHub/PyPI) + +on: + workflow_dispatch: + push: + tags: + - '*' # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml + +jobs: + release: + uses: Billingegroup/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 + with: + project: diffpy.utils + secrets: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} From d04a029e30d67fccd8ead4aa6996bb83584a490e Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:39:10 -0400 Subject: [PATCH 011/445] Add example_package.rst in api --- .../api/diffpy.utils.example_package.rst | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 doc/source/api/diffpy.utils.example_package.rst diff --git a/doc/source/api/diffpy.utils.example_package.rst b/doc/source/api/diffpy.utils.example_package.rst new file mode 100644 index 00000000..40388ba1 --- /dev/null +++ b/doc/source/api/diffpy.utils.example_package.rst @@ -0,0 +1,31 @@ +.. _example_package documentation: + +|title| +======= + +.. |title| replace:: diffpy.utils.example_package package + +.. automodule:: diffpy.utils.example_package + :members: + :undoc-members: + :show-inheritance: + +|foo| +----- + +.. |foo| replace:: diffpy.utils.example_package.foo module + +.. automodule:: diffpy.utils.example_package.foo + :members: + :undoc-members: + :show-inheritance: + +|bar| +----- + +.. |bar| replace:: diffpy.utils.example_package.bar module + +.. automodule:: diffpy.utils.example_package.foo + :members: + :undoc-members: + :show-inheritance: From 27b57378f90ebdadc9761dab9de516a8053cf27c Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:39:35 -0400 Subject: [PATCH 012/445] Add README for requirements --- requirements/README.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 requirements/README.txt diff --git a/requirements/README.txt b/requirements/README.txt new file mode 100644 index 00000000..3740a138 --- /dev/null +++ b/requirements/README.txt @@ -0,0 +1,10 @@ +# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! +# +# This directory is where you should place your project dependencies. +# build.txt should list all Conda packages required for building the package in GitHub CI, including those specified in the build section of meta.yaml (conda-recipe). +# conda.txt should list all Conda packages required (including optional) for running the package in GitHub CI, as well as those specified in the run section of meta.yaml (conda-recipe). +# pip.txt should list all PyPI packages (including optional) required to install the package via `pip install `. +# test.txt should list all Conda/PyPI packages required for the testing suite to ensure all tests pass. +# docs.txt should list all Conda/PyPI packages required for building the package documentation page. +# +# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! From c241c9c33997ef5bd0bd4a1b19ea3222dc462cc7 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:39:54 -0400 Subject: [PATCH 013/445] Add test_version.py --- tests/test_version.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/test_version.py diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 00000000..421a96e4 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,10 @@ +"""Unit tests for __version__.py +""" + +import diffpy.utils + + +def test_package_version(): + """Ensure the package version is defined and not set to the initial placeholder.""" + assert hasattr(diffpy.utils, "__version__") + assert diffpy.utils.__version__ != "0.0.0" From 54170fef7d62730ae70a29629e43499001544c67 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:41:33 -0400 Subject: [PATCH 014/445] Add news for recut --- news/recut.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/recut.rst diff --git a/news/recut.rst b/news/recut.rst new file mode 100644 index 00000000..70e17b5d --- /dev/null +++ b/news/recut.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Recut to group's package standard, fix installation, add GitHub release workflow + +**Security:** + +* From 3b67d83a618f13a2112f24d8432da6144bb61a25 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 12:43:39 -0400 Subject: [PATCH 015/445] Added newline to README --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index f0476d94..5a2875d8 100644 --- a/README.rst +++ b/README.rst @@ -111,7 +111,7 @@ Support and Contribute `Diffpy user group `_ is the discussion forum for general questions and discussions about the use of diffpy.utils. Please join the diffpy.utils users community by joining the Google group. The diffpy.utils project welcomes your expertise and enthusiasm! -If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. +If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. Feel free to fork the project and contribute. To install diffpy.utils in a development mode, with its sources being directly used by Python @@ -140,3 +140,4 @@ Contact ------- For more information on diffpy.utils please visit the project `web-page `_ or email Prof. Simon Billinge at sb2896@columbia.edu. + From a9fd906c1a0d0d3100aa6320f5d7eda76f3aee87 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 13:03:51 -0400 Subject: [PATCH 016/445] deleted README.txt --- requirements/README.txt | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 requirements/README.txt diff --git a/requirements/README.txt b/requirements/README.txt deleted file mode 100644 index 3740a138..00000000 --- a/requirements/README.txt +++ /dev/null @@ -1,10 +0,0 @@ -# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! -# -# This directory is where you should place your project dependencies. -# build.txt should list all Conda packages required for building the package in GitHub CI, including those specified in the build section of meta.yaml (conda-recipe). -# conda.txt should list all Conda packages required (including optional) for running the package in GitHub CI, as well as those specified in the run section of meta.yaml (conda-recipe). -# pip.txt should list all PyPI packages (including optional) required to install the package via `pip install `. -# test.txt should list all Conda/PyPI packages required for the testing suite to ensure all tests pass. -# docs.txt should list all Conda/PyPI packages required for building the package documentation page. -# -# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! From 86813a5634cc1be90720cf19f947efd39eac7674 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 13:04:33 -0400 Subject: [PATCH 017/445] deleted new line --- README.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/README.rst b/README.rst index 5a2875d8..98e34649 100644 --- a/README.rst +++ b/README.rst @@ -140,4 +140,3 @@ Contact ------- For more information on diffpy.utils please visit the project `web-page `_ or email Prof. Simon Billinge at sb2896@columbia.edu. - From ce8cdef672cb4e673657e7d6f1efceef05b63b57 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 23 Oct 2024 13:17:46 -0400 Subject: [PATCH 018/445] deleted example_package.rst in api --- .../api/diffpy.utils.example_package.rst | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 doc/source/api/diffpy.utils.example_package.rst diff --git a/doc/source/api/diffpy.utils.example_package.rst b/doc/source/api/diffpy.utils.example_package.rst deleted file mode 100644 index 40388ba1..00000000 --- a/doc/source/api/diffpy.utils.example_package.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. _example_package documentation: - -|title| -======= - -.. |title| replace:: diffpy.utils.example_package package - -.. automodule:: diffpy.utils.example_package - :members: - :undoc-members: - :show-inheritance: - -|foo| ------ - -.. |foo| replace:: diffpy.utils.example_package.foo module - -.. automodule:: diffpy.utils.example_package.foo - :members: - :undoc-members: - :show-inheritance: - -|bar| ------ - -.. |bar| replace:: diffpy.utils.example_package.bar module - -.. automodule:: diffpy.utils.example_package.foo - :members: - :undoc-members: - :show-inheritance: From 31f3c03b25e01341fc351deede00f368b6de3276 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 25 Oct 2024 10:59:14 -0400 Subject: [PATCH 019/445] Setup requirements --- requirements/{run.txt => conda.txt} | 0 requirements/pip.txt | 1 + 2 files changed, 1 insertion(+) rename requirements/{run.txt => conda.txt} (100%) diff --git a/requirements/run.txt b/requirements/conda.txt similarity index 100% rename from requirements/run.txt rename to requirements/conda.txt diff --git a/requirements/pip.txt b/requirements/pip.txt index e69de29b..24ce15ab 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -0,0 +1 @@ +numpy From e69255afb24f01902f547bf3c38ef0bcb2b2a3cc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 25 Oct 2024 10:59:39 -0400 Subject: [PATCH 020/445] Set valid q range for test dump function --- tests/test_diffraction_objects.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9e3abe3e..8798930b 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -233,31 +233,17 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_dump(tmp_path, mocker): - x, y = np.linspace(0, 10, 11), np.linspace(0, 10, 11) + x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" test = Diffraction_object() test.wavelength = 1.54 test.name = "test" test.scat_quantity = "x-ray" - - with warnings.catch_warnings(record=True) as captured_warnings: - # Configure the warnings system to capture all warnings - warnings.simplefilter("always") - - # Perform the method call that is expected to trigger the RuntimeWarning due to the specified wavelength - test.insert_scattering_quantity( + test.insert_scattering_quantity( x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} - ) - - # Verify that at least one RuntimeWarning is raised due to the arcsin error from wavelength 1.54 - assert any( - isinstance(w.message, RuntimeWarning) for w in captured_warnings - ), "Expected a RuntimeWarning due to invalid arcsin input value from the wavelength set to 1.54" - - # Ensure that exactly one warning was captured - assert len(captured_warnings) == 1, "Expected exactly one warning to be captured" - + ) + mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): @@ -275,11 +261,6 @@ def test_dump(tmp_path, mocker): "3.000000000000000000e+00 3.000000000000000000e+00\n" "4.000000000000000000e+00 4.000000000000000000e+00\n" "5.000000000000000000e+00 5.000000000000000000e+00\n" - "6.000000000000000000e+00 6.000000000000000000e+00\n" - "7.000000000000000000e+00 7.000000000000000000e+00\n" - "8.000000000000000000e+00 8.000000000000000000e+00\n" - "9.000000000000000000e+00 9.000000000000000000e+00\n" - "1.000000000000000000e+01 1.000000000000000000e+01\n" ) assert actual == expected From 7b12bafda78914961df782289ec92d689f3f17f0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:01:46 +0000 Subject: [PATCH 021/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 8798930b..5658b9b1 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -241,9 +241,9 @@ def test_dump(tmp_path, mocker): test.name = "test" test.scat_quantity = "x-ray" test.insert_scattering_quantity( - x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} + x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) - + mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): From 0cb34e28641b44ae21826fa79a6e09faccac3e05 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 25 Oct 2024 11:06:36 -0400 Subject: [PATCH 022/445] Pre-commit --- tests/test_diffraction_objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 8798930b..b5581384 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -1,4 +1,3 @@ -import warnings from pathlib import Path import numpy as np From 859755e6b57c75a7b96bc3f750795fc7230e94d6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 25 Oct 2024 11:07:08 -0400 Subject: [PATCH 023/445] Apply pre-commit --- tests/test_diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index b5581384..a155b95e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -240,9 +240,9 @@ def test_dump(tmp_path, mocker): test.name = "test" test.scat_quantity = "x-ray" test.insert_scattering_quantity( - x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} + x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} ) - + mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): From f17b979076b0769713b3601c1bd1e0139c3a8b17 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 25 Oct 2024 15:38:47 -0400 Subject: [PATCH 024/445] update pyproject.toml for python 3.13 --- news/python313.rst | 23 +++++++++++++++++++++++ pyproject.toml | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 news/python313.rst diff --git a/news/python313.rst b/news/python313.rst new file mode 100644 index 00000000..4b34bb41 --- /dev/null +++ b/news/python313.rst @@ -0,0 +1,23 @@ +**Added:** + +* Support for Python 3.13 + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Support for Python 3.10 + +**Fixed:** + +* + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index 421f8886..41fe385a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ maintainers = [ description = "Shared utilities for diffpy packages" keywords = ['"text data parsers" "wx grid" "diffraction objects"'] readme = "README.rst" -requires-python = ">=3.10" +requires-python = ">=3.11, <3.14" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -25,9 +25,9 @@ classifiers = [ 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: Unix', - 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Scientific/Engineering :: Physics', 'Topic :: Scientific/Engineering :: Chemistry', ] From 4834db1ae0e6c9af00276db66fa4fd27aa2ee95d Mon Sep 17 00:00:00 2001 From: sbillinge Date: Sat, 26 Oct 2024 20:56:55 +0000 Subject: [PATCH 025/445] update changelog --- CHANGELOG.rst | 15 +++++++++++++++ news/git-version.rst | 23 ----------------------- news/pip.rst | 23 ----------------------- news/recut.rst | 23 ----------------------- news/sos.rst | 23 ----------------------- news/warning.rst | 23 ----------------------- 6 files changed, 15 insertions(+), 115 deletions(-) delete mode 100644 news/git-version.rst delete mode 100644 news/pip.rst delete mode 100644 news/recut.rst delete mode 100644 news/sos.rst delete mode 100644 news/warning.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b57c7e79..d574883f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,21 @@ Release Notes .. current developments +3.4.3 +===== + +**Added:** + +* Diffraction_objects mentioned in the README + +**Fixed:** + +* Recut to group's package standard, fix installation, add GitHub release workflow +* setuptools-git-versioning from <2.0 to >= 2.0 in pyproject.toml +* Two Pytest warnings due to numpy and pytest mocker in test_dump function +* Add pip dependencies under pip.txt and conda dependencies under conda.txt + + 3.4.2 ===== diff --git a/news/git-version.rst b/news/git-version.rst deleted file mode 100644 index 7b4e85bf..00000000 --- a/news/git-version.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* setuptools-git-versioning from <2.0 to >= 2.0 in pyproject.toml - -**Security:** - -* diff --git a/news/pip.rst b/news/pip.rst deleted file mode 100644 index d10e3b6d..00000000 --- a/news/pip.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Add pip dependencies under pip.txt and conda dependencies under conda.txt - -**Security:** - -* diff --git a/news/recut.rst b/news/recut.rst deleted file mode 100644 index 70e17b5d..00000000 --- a/news/recut.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Recut to group's package standard, fix installation, add GitHub release workflow - -**Security:** - -* diff --git a/news/sos.rst b/news/sos.rst deleted file mode 100644 index b5a35246..00000000 --- a/news/sos.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Diffraction_objects mentioned in the README - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/warning.rst b/news/warning.rst deleted file mode 100644 index 45768802..00000000 --- a/news/warning.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Two Pytest warnings due to numpy and pytest mocker in test_dump function - -**Security:** - -* From ebbb644a9d9bb4cccd2fdadbb4c7a56fe2dca972 Mon Sep 17 00:00:00 2001 From: sbillinge Date: Sun, 27 Oct 2024 00:32:12 +0000 Subject: [PATCH 026/445] update changelog --- CHANGELOG.rst | 12 ++++++++++++ news/python313.rst | 23 ----------------------- 2 files changed, 12 insertions(+), 23 deletions(-) delete mode 100644 news/python313.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d574883f..63320901 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,18 @@ Release Notes .. current developments +3.5.0 +===== + +**Added:** + +* Support for Python 3.13 + +**Removed:** + +* Support for Python 3.10 + + 3.4.3 ===== diff --git a/news/python313.rst b/news/python313.rst deleted file mode 100644 index 4b34bb41..00000000 --- a/news/python313.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Support for Python 3.13 - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* Support for Python 3.10 - -**Fixed:** - -* - -**Security:** - -* From f3cd7b4c4ca8963f698d20cd601b1b334f6328c6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 26 Oct 2024 22:33:27 -0400 Subject: [PATCH 027/445] Add commas in keywords in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 41fe385a..a43118ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ maintainers = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, ] description = "Shared utilities for diffpy packages" -keywords = ['"text data parsers" "wx grid" "diffraction objects"'] +keywords = ["text data parsers", "wx grid", "diffraction objects"] readme = "README.rst" requires-python = ">=3.11, <3.14" classifiers = [ From 611d219f3402c0b0b26c51fca0a6b91bd32d6018 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 4 Nov 2024 18:15:01 -0500 Subject: [PATCH 028/445] add xtype=d in dump function --- src/diffpy/utils/scattering_objects/diffraction_objects.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index bd5b16db..94d2831a 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -445,6 +445,8 @@ def dump(self, filepath, xtype=None): data_to_save = np.column_stack((self.on_q[0], self.on_q[1])) elif xtype == "tth": data_to_save = np.column_stack((self.on_tth[0], self.on_tth[1])) + elif xtype == "d": + data_to_save = np.column_stack((self.on_d[0], self.on_d[1])) else: print(f"WARNING: cannot handle the xtype '{xtype}'") self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) From 7112fac0c7804badf4ad41733cabec0960f063ff Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 5 Nov 2024 16:31:25 -0500 Subject: [PATCH 029/445] Add warning to missing global config file --- news/get-user-fix.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/tools.py | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 news/get-user-fix.rst diff --git a/news/get-user-fix.rst b/news/get-user-fix.rst new file mode 100644 index 00000000..5b2fd3df --- /dev/null +++ b/news/get-user-fix.rst @@ -0,0 +1,23 @@ +**Added:** + +* Warning message for missing global config file + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 1dfb8284..dc908a27 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -3,6 +3,7 @@ import os from copy import copy from pathlib import Path +import warnings def clean_dict(obj): @@ -114,6 +115,7 @@ def get_user_info(args=None): global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: + warnings.warn("No global config file, please follow prompts below. For more information, refer to www.diffpy.org/diffpy.utils/") config_bool = _create_global_config(args) global_config = load_config(Path().home() / "diffpyconfig.json") config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) From d24acb6305e6c9247618fc20b9633162b1096e79 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 5 Nov 2024 16:39:23 -0500 Subject: [PATCH 030/445] fix line too long --- src/diffpy/utils/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index dc908a27..7574d093 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -115,7 +115,7 @@ def get_user_info(args=None): global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: - warnings.warn("No global config file, please follow prompts below. For more information, refer to www.diffpy.org/diffpy.utils/") + warnings.warn("No global config file, please follow prompts below.") config_bool = _create_global_config(args) global_config = load_config(Path().home() / "diffpyconfig.json") config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) From 0a59a0be30f1b5acb811dca376d88c18de97ae82 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 5 Nov 2024 17:03:12 -0500 Subject: [PATCH 031/445] better wording for capture user info --- news/capture-user.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/tools.py | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 news/capture-user.rst diff --git a/news/capture-user.rst b/news/capture-user.rst new file mode 100644 index 00000000..b789d46e --- /dev/null +++ b/news/capture-user.rst @@ -0,0 +1,23 @@ +**Added:** + +* Better wording on the capture user info functionality + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 7574d093..aab4a64f 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -115,7 +115,9 @@ def get_user_info(args=None): global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: - warnings.warn("No global config file, please follow prompts below.") + warnings.warn("No global config file, please follow prompts below. " + "The global config file is very important in crediting your work in the future. " + "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html") config_bool = _create_global_config(args) global_config = load_config(Path().home() / "diffpyconfig.json") config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) From 2379a62ca863778361395c8779ff16a190b055fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 22:05:56 +0000 Subject: [PATCH 032/445] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tools.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index aab4a64f..2f165f87 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,9 +1,9 @@ import importlib.metadata import json import os +import warnings from copy import copy from pathlib import Path -import warnings def clean_dict(obj): @@ -115,9 +115,11 @@ def get_user_info(args=None): global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: - warnings.warn("No global config file, please follow prompts below. " - "The global config file is very important in crediting your work in the future. " - "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html") + warnings.warn( + "No global config file, please follow prompts below. " + "The global config file is very important in crediting your work in the future. " + "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" + ) config_bool = _create_global_config(args) global_config = load_config(Path().home() / "diffpyconfig.json") config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) From e5a6c381bce9b0ea4760834654bbc0434cfec7ce Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 6 Nov 2024 10:26:57 -0500 Subject: [PATCH 033/445] configure codespell at pyproject.toml and pre-commit --- .codespell/ignore_lines.txt | 2 ++ .codespell/ignore_words.txt | 11 +++++++++++ .pre-commit-config.yaml | 6 ++++++ CHANGELOG.rst | 2 +- README.rst | 2 +- news/codespell.rst | 23 +++++++++++++++++++++++ pyproject.toml | 5 +++++ src/diffpy/utils/parsers/loaddata.py | 2 +- src/diffpy/utils/parsers/resample.py | 2 +- src/diffpy/utils/parsers/serialization.py | 2 +- 10 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 .codespell/ignore_lines.txt create mode 100644 .codespell/ignore_words.txt create mode 100644 news/codespell.rst diff --git a/.codespell/ignore_lines.txt b/.codespell/ignore_lines.txt new file mode 100644 index 00000000..07fa7c8c --- /dev/null +++ b/.codespell/ignore_lines.txt @@ -0,0 +1,2 @@ +;; Please include filenames and explanations for each ignored line. +;; See https://docs.openverse.org/meta/codespell.html for docs. diff --git a/.codespell/ignore_words.txt b/.codespell/ignore_words.txt new file mode 100644 index 00000000..9757d7c0 --- /dev/null +++ b/.codespell/ignore_words.txt @@ -0,0 +1,11 @@ +;; Please include explanations for each ignored word (lowercase). +;; See https://docs.openverse.org/meta/codespell.html for docs. + +;; abbreviation for "materials" often used in a journal title +mater + +;; alternative use of socioeconomic +socio-economic + +;; Frobenius norm used in np.linalg.norm +fro diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3070e199..aee43d0e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,3 +44,9 @@ repos: name: Prevent Commit to Main Branch args: ["--branch", "main"] stages: [pre-commit] + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + additional_dependencies: + - tomli diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b57c7e79..ceed2511 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -90,7 +90,7 @@ v3.2.3 **Added:** -* Compatability with Python 3.12.0rc3, 3.11. +* Compatibility with Python 3.12.0rc3, 3.11. * CI Coverage. * New tests for loadData function. * loadData function now toggleable. Can return either (a) data read from data blocks or (b) header information stored diff --git a/README.rst b/README.rst index 98e34649..48967571 100644 --- a/README.rst +++ b/README.rst @@ -134,7 +134,7 @@ trying to commit again. Improvements and fixes are always appreciated. -Before contribuing, please read our `Code of Conduct `_. +Before contributing, please read our `Code of Conduct `_. Contact ------- diff --git a/news/codespell.rst b/news/codespell.rst new file mode 100644 index 00000000..8c5ba6d2 --- /dev/null +++ b/news/codespell.rst @@ -0,0 +1,23 @@ +**Added:** + +* Spelling check via Codespell in pre-commit + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index 421f8886..53e14e82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,11 @@ namespaces = false # to disable scanning PEP 420 namespaces (true by default) [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} +[tool.codespell] +exclude-file = ".codespell/ignore_lines.txt" +ignore-words = ".codespell/ignore_words.txt" +skip = "*.cif,*.dat" + [tool.black] line-length = 115 include = '\.pyi?$' diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 4bb0d792..18375d90 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -30,7 +30,7 @@ def loadData(filename, minrows=10, headers=False, hdel="=", hignore=None, **kwar Minimum number of rows in the first data block. All rows must have the same number of floating point values. headers: bool - when False (defualt), the function returns a numpy array of the data in the data block. + when False (default), the function returns a numpy array of the data in the data block. When True, the function instead returns a dictionary of parameters and their corresponding values parsed from header (information prior the data block). See hdel and hignore for options to help with parsing header information. diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/parsers/resample.py index f81fc2a2..e88c4f0b 100644 --- a/src/diffpy/utils/parsers/resample.py +++ b/src/diffpy/utils/parsers/resample.py @@ -113,7 +113,7 @@ def resample(r, s, dr): # spad = numpy.concatenate([s,spad]) # rnew = numpy.arange(0, rpad[-1], dr) # snew = numpy.zeros_like(rnew) - # Accomodate for the fact that r[0] might not be 0 + # Accommodate for the fact that r[0] might not be 0 # u = (rnew-r[0]) / dr0 # for n in range(len(spad)): # snew += spad[n] * numpy.sinc(u - n) diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index a0bec92c..46d4b8ff 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -56,7 +56,7 @@ def serialize_data( include a path element in the database entry (default True). If 'path' is not included in hddata, extract path from filename. serial_file - Serial language file to dump dictionary into. If None (defualt), no dumping will occur. + Serial language file to dump dictionary into. If None (default), no dumping will occur. Returns ------- From 4bb5ecf7b6362594de8f099fbef3cc651aed7980 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 6 Nov 2024 17:06:04 -0500 Subject: [PATCH 034/445] delete unnecessary get-user-fix.rst --- news/get-user-fix.rst | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 news/get-user-fix.rst diff --git a/news/get-user-fix.rst b/news/get-user-fix.rst deleted file mode 100644 index 5b2fd3df..00000000 --- a/news/get-user-fix.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Warning message for missing global config file - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From 2732b498d33c5799cb4565a3aa2f99740f363537 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 6 Nov 2024 17:41:11 -0500 Subject: [PATCH 035/445] edited get user prompt --- src/diffpy/utils/tools.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 2f165f87..812e320b 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,7 +1,6 @@ import importlib.metadata import json import os -import warnings from copy import copy from pathlib import Path @@ -78,11 +77,11 @@ def _sorted_merge(*dicts): def _create_global_config(args): username = input( - f"Please enter the name of the user to put in the diffpy global config file " + f"Please enter the name of the user would want future work to be credited to " f"[{args.get('username', '')}]: " ).strip() or args.get("username", "") email = input( - f"Please enter the email of the user to put in the diffpy global config file " + f"Please enter the email of the user " f"[{args.get('email', '')}]: " ).strip() or args.get("email", "") return_bool = False if username is None or email is None else True @@ -115,9 +114,8 @@ def get_user_info(args=None): global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: - warnings.warn( + print( "No global config file, please follow prompts below. " - "The global config file is very important in crediting your work in the future. " "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" ) config_bool = _create_global_config(args) From 7e25316974d3426339b7520ddd8ed45c575d5f5b Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 6 Nov 2024 17:56:41 -0500 Subject: [PATCH 036/445] add news --- news/dump.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/dump.rst diff --git a/news/dump.rst b/news/dump.rst new file mode 100644 index 00000000..7f99d586 --- /dev/null +++ b/news/dump.rst @@ -0,0 +1,23 @@ +**Added:** + +* functionality in dump to allow writing data on dspace + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From e36726e83e3135f76507236bebf7b76f90f7db72 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 6 Nov 2024 18:33:26 -0500 Subject: [PATCH 037/445] edited message and prompt to capture user info --- src/diffpy/utils/tools.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 812e320b..1e804212 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -77,11 +77,11 @@ def _sorted_merge(*dicts): def _create_global_config(args): username = input( - f"Please enter the name of the user would want future work to be credited to " + f"Please enter the name you would want future work to be credited to " f"[{args.get('username', '')}]: " ).strip() or args.get("username", "") email = input( - f"Please enter the email of the user " + f"Please enter the your email " f"[{args.get('email', '')}]: " ).strip() or args.get("email", "") return_bool = False if username is None or email is None else True @@ -115,7 +115,11 @@ def get_user_info(args=None): local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: print( - "No global config file, please follow prompts below. " + "No global configuration file was found containing information about the user to associate with the data. " + "By following the prompts below you can add your name and email to this file on the current computer and " + "your name will be automatically associated with subsequent diffpy data by default. " + "This is not recommended on a shared or public computer. " + "You will only have to do that once. " "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" ) config_bool = _create_global_config(args) From c367349d85bb39510d7987dba3abb47d660be10f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:33:43 +0000 Subject: [PATCH 038/445] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tools.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 1e804212..461d3f71 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -77,13 +77,9 @@ def _sorted_merge(*dicts): def _create_global_config(args): username = input( - f"Please enter the name you would want future work to be credited to " - f"[{args.get('username', '')}]: " + f"Please enter the name you would want future work to be credited to " f"[{args.get('username', '')}]: " ).strip() or args.get("username", "") - email = input( - f"Please enter the your email " - f"[{args.get('email', '')}]: " - ).strip() or args.get("email", "") + email = input(f"Please enter the your email " f"[{args.get('email', '')}]: ").strip() or args.get("email", "") return_bool = False if username is None or email is None else True with open(Path().home() / "diffpyconfig.json", "w") as f: f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) From 5bfc9dd539e21dd9d8a892fd7192646ce4360f67 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Wed, 6 Nov 2024 18:49:44 -0500 Subject: [PATCH 039/445] fixed pre-commit issues --- src/diffpy/utils/tools.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 461d3f71..82cca8c9 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -111,11 +111,12 @@ def get_user_info(args=None): local_config = load_config(Path().cwd() / "diffpyconfig.json") if global_config is None and local_config is None: print( - "No global configuration file was found containing information about the user to associate with the data. " - "By following the prompts below you can add your name and email to this file on the current computer and " - "your name will be automatically associated with subsequent diffpy data by default. " + "No global configuration file was found containing " + "information about the user to associate with the data.\n" + "By following the prompts below you can add your name and email to this file on the current" + " computer and your name will be automatically associated with subsequent diffpy data by default.\n" "This is not recommended on a shared or public computer. " - "You will only have to do that once. " + "You will only have to do that once.\n" "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" ) config_bool = _create_global_config(args) From f204ebdc10f8c1e7a50bb16f0281d4a55948828d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 6 Nov 2024 19:13:34 -0500 Subject: [PATCH 040/445] use latest .codecov.yml, remove coveragerc, add codecov secret to test on pr workflow --- .codecov.yml | 42 ++++++++----------------------- .coveragerc | 13 ---------- .github/workflows/tests-on-pr.yml | 2 ++ 3 files changed, 13 insertions(+), 44 deletions(-) delete mode 100644 .coveragerc diff --git a/.codecov.yml b/.codecov.yml index 04dd6510..7e61a29f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,34 +1,14 @@ -# codecov can find this file anywhere in the repo, so we don't need to clutter -# the root folder. -#comment: false - -codecov: - notify: - require_ci_to_pass: no - coverage: status: - patch: + project: # more options at https://docs.codecov.com/docs/commit-status default: - target: '70' - if_no_uploads: error - if_not_found: success - if_ci_failed: failure - project: - default: false - library: - target: auto - if_no_uploads: error - if_not_found: success - if_ci_failed: error - paths: '!*/tests/.*' - - tests: - target: 97.9% - paths: '*/tests/.*' - if_not_found: success - -flags: - tests: - paths: - - tests/ + target: auto # use the coverage from the base commit, fail if coverage is lower + threshold: 0% # allow the coverage to drop by + +comment: + layout: " diff, flags, files" + behavior: default + require_changes: false + require_base: false # [true :: must have a base report to post] + require_head: false # [true :: must have a head report to post] + hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] \ No newline at end of file diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 0ed6eac5..00000000 --- a/.coveragerc +++ /dev/null @@ -1,13 +0,0 @@ -[run] -source = - diffpy.utils -[report] -omit = - */python?.?/* - */site-packages/nose/* - # ignore _version.py and versioneer.py - .*version.* - *_version.py - -exclude_lines = - if __name__ == '__main__': diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index dd0a102e..baac1aeb 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -14,3 +14,5 @@ jobs: project: diffpy.utils c_extension: false headless: false + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From aa2ce946de4e543cab7f0497f88fffb7d40586c4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 6 Nov 2024 19:14:18 -0500 Subject: [PATCH 041/445] Apply precommit --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 7e61a29f..5a94096e 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -11,4 +11,4 @@ comment: require_changes: false require_base: false # [true :: must have a base report to post] require_head: false # [true :: must have a head report to post] - hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] \ No newline at end of file + hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] From c79ebb4b3817e1997e1f71e4d4c47982ad89de60 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 22:45:36 -0500 Subject: [PATCH 042/445] modify issue checklist --- .github/ISSUE_TEMPLATE/release_checklist.md | 14 ++++++++++---- .github/workflows/build-wheel-release-upload.yml | 2 +- .github/workflows/check-news-item.yml | 2 +- .../matrix-and-codecov-on-merge-to-main.yml | 2 +- .github/workflows/publish-docs-on-release.yml | 14 -------------- .github/workflows/tests-on-pr.yml | 2 +- .pre-commit-config.yaml | 6 +++--- 7 files changed, 17 insertions(+), 25 deletions(-) delete mode 100644 .github/workflows/publish-docs-on-release.yml diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index a87a44a8..b96c782d 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -13,10 +13,16 @@ assignees: "" - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are missing), tutorials, and other human written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated and - tested -- [ ] Successfully run any tutorial examples or do functional testing in some other way. +- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) updated. +- [ ] Successfully run any tutorial examples or do functional testing with the latest Python version - [ ] Grammar and writing quality have been checked (no typos). Please mention @sbillinge when you are ready for release. Include any additional comments necessary, such as -version information and details about the pre-release. +version information and details about the pre-release here: + +### Post-release checklist + +Before closing this issue, please complete the following: + +- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. +- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 9ea21782..0c9f1357 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -1,4 +1,4 @@ -name: Release (GitHub/PyPI) +name: Release (GitHub/PyPI) and Deploy Docs on: workflow_dispatch: diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index be549960..54a86e99 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -6,7 +6,7 @@ on: - main jobs: - check-news-item: + build: uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.utils diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index 99a3c623..bdf97643 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -11,7 +11,7 @@ on: workflow_dispatch: jobs: - CI: + coverage: uses: Billingegroup/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.utils diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml deleted file mode 100644 index 56667dfa..00000000 --- a/.github/workflows/publish-docs-on-release.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Build and Deploy Docs - -on: - release: - types: - - published - workflow_dispatch: - -jobs: - publish-docs-on-release: - uses: Billingegroup/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 - with: - project: diffpy.utils - c_extension: false diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index baac1aeb..7371ec89 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: jobs: - tests-on-pr: + validate: uses: Billingegroup/release-scripts/.github/workflows/_tests-on-pr.yml@v0 with: project: diffpy.utils diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aee43d0e..9cf0556f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,6 +47,6 @@ repos: - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - - id: codespell - additional_dependencies: - - tomli + - id: codespell + additional_dependencies: + - tomli From 579a293f3d121598e5b43c0f540ac522ffdc40cc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 22:54:44 -0500 Subject: [PATCH 043/445] Use "check-for-new" job name --- .github/workflows/check-news-item.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index 54a86e99..8faed9ea 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -6,7 +6,7 @@ on: - main jobs: - build: + check-for-news: uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.utils From 9908ee1090ee44bb4e9b6f4844b933b3af776e92 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 22:55:03 -0500 Subject: [PATCH 044/445] use matrix-coverage job name --- .github/workflows/matrix-and-codecov-on-merge-to-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index bdf97643..8543c786 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -11,7 +11,7 @@ on: workflow_dispatch: jobs: - coverage: + matrix-coverage: uses: Billingegroup/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.utils From cd3f078afc71b3014a592b2b37caa13c546e835a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 23:01:55 -0500 Subject: [PATCH 045/445] use `tests-on-pr` job name --- .github/workflows/tests-on-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index 7371ec89..baac1aeb 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: jobs: - validate: + tests-on-pr: uses: Billingegroup/release-scripts/.github/workflows/_tests-on-pr.yml@v0 with: project: diffpy.utils From 6eba041ecdeec4c4e1d0b52b74f6b7d32f256179 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 23:03:31 -0500 Subject: [PATCH 046/445] Rename to check-news-items --- .github/workflows/check-news-item.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index 8faed9ea..2e18c710 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -6,7 +6,7 @@ on: - main jobs: - check-for-news: + check-news-items: uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.utils From 07072468b0660afeabd262e82eabea98b954fa83 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 7 Nov 2024 23:06:06 -0500 Subject: [PATCH 047/445] rename `check-news-items` to `check-news-item` --- .github/workflows/check-news-item.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index 2e18c710..be549960 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -6,7 +6,7 @@ on: - main jobs: - check-news-items: + check-news-item: uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.utils From 1c7eae6f2800baddcb0c39d85bae68d754922e1e Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 8 Nov 2024 21:25:17 -0500 Subject: [PATCH 048/445] remove relative path imports --- src/diffpy/utils/parsers/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index a2104181..cd95dd63 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -16,12 +16,4 @@ """Various utilities related to data parsing and manipulation. """ -from .loaddata import loadData -from .resample import resample -from .serialization import deserialize_data, serialize_data - -# silence the pyflakes syntax checker -assert loadData or resample or True -assert serialize_data or deserialize_data or True - # End of file From 165ffb8f0d2fbee0c9d27fc0c38bfbc33c523d73 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 8 Nov 2024 21:25:41 -0500 Subject: [PATCH 049/445] moved resample out of parsers --- src/diffpy/utils/{parsers/resample.py => resampler.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/diffpy/utils/{parsers/resample.py => resampler.py} (100%) diff --git a/src/diffpy/utils/parsers/resample.py b/src/diffpy/utils/resampler.py similarity index 100% rename from src/diffpy/utils/parsers/resample.py rename to src/diffpy/utils/resampler.py From 11a340141c4f3977d6317e4febe88228833eb9cf Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 8 Nov 2024 21:26:06 -0500 Subject: [PATCH 050/445] edited tests based on fixed file and function names --- tests/test_loaddata.py | 2 +- tests/test_resample.py | 2 +- tests/test_serialization.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index 2ca2fa07..3d775c74 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -8,7 +8,7 @@ import numpy import pytest -from diffpy.utils.parsers import loadData +from diffpy.utils.parsers.loaddata import loadData ############################################################################## diff --git a/tests/test_resample.py b/tests/test_resample.py index fa9c7e70..deddcca2 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from diffpy.utils.parsers.resample import wsinterp +from diffpy.utils.resampler import wsinterp def test_wsinterp(): diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 51921e01..fc3696bf 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -3,8 +3,9 @@ import numpy import pytest -from diffpy.utils.parsers import deserialize_data, loadData, serialize_data from diffpy.utils.parsers.custom_exceptions import ImproperSizeError, UnsupportedTypeError +from diffpy.utils.parsers.loaddata import loadData +from diffpy.utils.parsers.serialization import deserialize_data, serialize_data tests_dir = os.path.dirname(os.path.abspath(locals().get("__file__", "file.py"))) From ccecf1074d3b95076706ae604d21f0e9737e6a3c Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Fri, 8 Nov 2024 21:26:28 -0500 Subject: [PATCH 051/445] add news item --- news/resampler-relocation.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/resampler-relocation.rst diff --git a/news/resampler-relocation.rst b/news/resampler-relocation.rst new file mode 100644 index 00000000..220fc2f5 --- /dev/null +++ b/news/resampler-relocation.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* Moved resampler out of parsers, new path is diffpy.utils.resampler + +**Deprecated:** + +* + +**Removed:** + +* Relative imports in parser's __init__.py + +**Fixed:** + +* + +**Security:** + +* From f0a39043270233289e92031934e970f5142a3d4d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 9 Nov 2024 16:10:53 -0500 Subject: [PATCH 052/445] Add conda-forge release checklist to GitHub Issue template --- .github/ISSUE_TEMPLATE/release_checklist.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index b96c782d..0f560278 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -6,23 +6,30 @@ labels: "release" assignees: "" --- -### Release checklist for GitHub contributors +### PyPI/GitHub release checklist: - [ ] All PRs/issues attached to the release are merged. - [ ] All the badges on the README are passing. - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are missing), tutorials, and other human written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) updated. -- [ ] Successfully run any tutorial examples or do functional testing with the latest Python version -- [ ] Grammar and writing quality have been checked (no typos). +- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. +- [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. +- [ ] Grammar and writing quality are checked (no typos). -Please mention @sbillinge when you are ready for release. Include any additional comments necessary, such as +Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: +### conda-forge release checklist: + + + +- [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. +- [ ] All relevant issues in the feedstock are addressed in the release PR. + ### Post-release checklist -Before closing this issue, please complete the following: + -- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. +- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. - [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. From c486d35ce7c6393eafc40dc082b75fc684c1a363 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 11 Nov 2024 11:53:52 -0500 Subject: [PATCH 053/445] Update release_checklist.md - use fourigui 0.2.0 version --- .github/ISSUE_TEMPLATE/release_checklist.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 0f560278..33410e8c 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -6,26 +6,35 @@ labels: "release" assignees: "" --- -### PyPI/GitHub release checklist: +### PyPI/GitHub rc-release preparation checklist: - [ ] All PRs/issues attached to the release are merged. - [ ] All the badges on the README are passing. - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are missing), tutorials, and other human written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. +- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). -Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as -version information and details about the pre-release here: +Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: -### conda-forge release checklist: +### PyPI/GitHub full-release preparation checklist: +- [ ] Create a new conda environment and install the rc from pypi (`pip install =??`) +- [ ] License information at Pypi is verified as correct. +- [ ] Docs deployed successfully to `.github.io` +- [ ] Successfully run all tests, tutorial examples or do functional testing + +Please let @sbillinge know that all checks are done and package is ready for full release. + +### conda-forge release preparation checklist: +- [ ] Ensure that the full release has appeared on Pypi successfully - [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. -- [ ] All relevant issues in the feedstock are addressed in the release PR. +- [ ] Close any open issues on the feedstock. Reach out to @bobleesj if you have questions +- [ ] let @sbillinge and @bobleesj when this is ready ### Post-release checklist From 1831b797f9a5cb1edab47b5aaff1898ba537aa02 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:54:46 +0000 Subject: [PATCH 054/445] [pre-commit.ci] auto fixes from pre-commit hooks --- .github/ISSUE_TEMPLATE/release_checklist.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 33410e8c..ba1d40ce 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -13,7 +13,7 @@ assignees: "" - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are missing), tutorials, and other human written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. +- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). @@ -21,7 +21,7 @@ Please mention @sbillinge here when you are ready for PyPI/GitHub release. Inclu ### PyPI/GitHub full-release preparation checklist: - [ ] Create a new conda environment and install the rc from pypi (`pip install =??`) -- [ ] License information at Pypi is verified as correct. +- [ ] License information at Pypi is verified as correct. - [ ] Docs deployed successfully to `.github.io` - [ ] Successfully run all tests, tutorial examples or do functional testing From b86ce5e525f6553f823514316a4d5fc0520969e6 Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Mon, 11 Nov 2024 17:01:29 -0500 Subject: [PATCH 055/445] Documentation update --- doc/source/api/diffpy.utils.parsers.rst | 13 +++---------- doc/source/api/diffpy.utils.rst | 9 +++++++++ doc/source/api/diffpy.utils.scattering_objects.rst | 13 +++++++------ doc/source/api/diffpy.utils.wx.rst | 1 + doc/source/examples/parsersexample.rst | 6 +++--- doc/source/examples/resampleexample.rst | 6 +++--- doc/source/utilities/parsersutility.rst | 6 +++--- 7 files changed, 29 insertions(+), 25 deletions(-) diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index 0709cf3a..bf3d6cc6 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -11,10 +11,10 @@ diffpy.utils.parsers package Submodules ---------- -diffpy.utils.parsers.resample module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.parsers.serialization module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.parsers.resample +.. automodule:: diffpy.utils.parsers.serialization :members: :undoc-members: :show-inheritance: @@ -35,10 +35,3 @@ diffpy.utils.parsers.custom_exceptions module :undoc-members: :show-inheritance: -diffpy.utils.parsers.serialization module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.parsers.serialization - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index cf69f596..bd4cda5b 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -28,3 +28,12 @@ diffpy.utils.tools module :members: :undoc-members: :show-inheritance: + +diffpy.utils.resampler module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.resampler + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.scattering_objects.rst b/doc/source/api/diffpy.utils.scattering_objects.rst index fe511bf5..b7c24bf1 100644 --- a/doc/source/api/diffpy.utils.scattering_objects.rst +++ b/doc/source/api/diffpy.utils.scattering_objects.rst @@ -11,18 +11,19 @@ diffpy.utils.scattering_objects package Submodules ---------- -diffpy.utils.scattering_objects.user_config module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.scattering_objects.diffraction_objects module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.scattering_objects.user_config +.. automodule:: diffpy.utils.scattering_objects.diffraction_objects :members: :undoc-members: :show-inheritance: -diffpy.utils.scattering_objects.diffraction_objects module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.scattering_objects.user_config module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.scattering_objects.diffraction_objects +.. automodule:: diffpy.utils.scattering_objects.user_config :members: :undoc-members: :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index 76c89035..60f60599 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -18,3 +18,4 @@ diffpy.utils.wx.gridutils module :members: :undoc-members: :show-inheritance: + diff --git a/doc/source/examples/parsersexample.rst b/doc/source/examples/parsersexample.rst index 90605741..b709de60 100644 --- a/doc/source/examples/parsersexample.rst +++ b/doc/source/examples/parsersexample.rst @@ -14,7 +14,7 @@ Using the parsers module, we can load file data into simple and easy-to-work-wit 2) To get the data table, we will use the ``loadData`` function. The default behavior of this function is to find and extract a data table from a file.:: - from diffpy.utils.parsers import loadData + from diffpy.utils.parsers.loaddata import loadData data_table = loadData('') While this will work with most datasets, on our ``data.txt`` file, we got a ``ValueError``. The reason for this is @@ -53,7 +53,7 @@ Using the parsers module, we can load file data into simple and easy-to-work-wit 4) Rather than working with separate ``data_table`` and ``hdata`` objects, it may be easier to combine them into a single dictionary. We can do so using the ``serialize_data`` function. :: - from diffpy.utils.parsers import serialize_data + from diffpy.utils.parsers.loaddata import serialize_data file_data = serialize_data('') 6) Finally, ``serialize_data`` allows us to store data from multiple text file in a single serial file. For one last bit diff --git a/doc/source/examples/resampleexample.rst b/doc/source/examples/resampleexample.rst index efa3c474..f7ba5e18 100644 --- a/doc/source/examples/resampleexample.rst +++ b/doc/source/examples/resampleexample.rst @@ -15,14 +15,14 @@ given enough datapoints. `_. :: - from diffpy.utils.parsers import loadData + from diffpy.utils.parsers.loaddata import loadData nickel_datatable = loadData('') nitarget_datatable = loadData('') Each data table has two columns: first is the grid and second is the function value. To extract the columns, we can utilize the serialize function ... :: - from diffpy.utils.parsers import serialize_data + from diffpy.utils.parsers.serialization import serialize_data nickel_data = serialize_data('Nickel.gr', {}, nickel_datatable, dt_colnames=['grid', 'func']) nickel_grid = nickel_data['Nickel.gr']['grid'] nickel_func = nickel_data['Nickel.gr']['func'] @@ -54,7 +54,7 @@ given enough datapoints. ... and use the diffpy.utils ``wsinterp`` function to resample on this grid.:: - from diffpy.utils.parsers import wsinterp + from diffpy.utils.resampler import wsinterp nickel_resample = wsinterp(grid, nickel_grid, nickel_func) target_resample = wsinterp(grid, target_grid, target_func) diff --git a/doc/source/utilities/parsersutility.rst b/doc/source/utilities/parsersutility.rst index f653c3d7..ffaf768e 100644 --- a/doc/source/utilities/parsersutility.rst +++ b/doc/source/utilities/parsersutility.rst @@ -5,7 +5,7 @@ Parsers Utility The ``diffpy.utils.parsers`` module allows users to easily and robustly load file data into a Python project. -- ``loadData()``: Find and load a data table/block from a text file. This seems to work for most datafiles +- ``loaddata.loadData()``: Find and load a data table/block from a text file. This seems to work for most datafiles including those generated by diffpy programs. Running only ``numpy.loadtxt`` will result in errors for most these files as there is often excess data or parameters stored above the data block. Users can instead choose to load all the parameters of the form `` = `` into a dictionary @@ -14,10 +14,10 @@ The ``diffpy.utils.parsers`` module allows users to easily and robustly load fil The program identifies data blocks as the first matrix block with a constant number of columns. A user can tune the minimum number of rows this matrix block must have. -- ``deserialize_data()``: Load data from a serial file format into a Python dictionary. Currently, the only supported +- ``serialization.deserialize_data()``: Load data from a serial file format into a Python dictionary. Currently, the only supported serial format is ``.json``. -- ``serialize_data()``: Serialize the data generated by ``loadData()`` into a serial file format. Currently, the only +- ``serialization.serialize_data()``: Serialize the data generated by ``loadData()`` into a serial file format. Currently, the only supported serial format is ``.json``. For a more in-depth tutorial for how to use these parser utilities, click :ref:`here `. From dac72cb6a0dad627ccf43e4ecc56e293cc44106d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:02:24 +0000 Subject: [PATCH 056/445] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/api/diffpy.utils.parsers.rst | 1 - doc/source/api/diffpy.utils.rst | 1 - doc/source/api/diffpy.utils.scattering_objects.rst | 1 - doc/source/api/diffpy.utils.wx.rst | 1 - 4 files changed, 4 deletions(-) diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index bf3d6cc6..3456f24e 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -34,4 +34,3 @@ diffpy.utils.parsers.custom_exceptions module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index bd4cda5b..b873c333 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -36,4 +36,3 @@ diffpy.utils.resampler module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.scattering_objects.rst b/doc/source/api/diffpy.utils.scattering_objects.rst index b7c24bf1..4607ad98 100644 --- a/doc/source/api/diffpy.utils.scattering_objects.rst +++ b/doc/source/api/diffpy.utils.scattering_objects.rst @@ -26,4 +26,3 @@ diffpy.utils.scattering_objects.user_config module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index 60f60599..76c89035 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -18,4 +18,3 @@ diffpy.utils.wx.gridutils module :members: :undoc-members: :show-inheritance: - From 8cd36296793162f311e361681343a26f4f1cc31c Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Mon, 11 Nov 2024 17:03:16 -0500 Subject: [PATCH 057/445] news --- news/doc-update.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/doc-update.rst diff --git a/news/doc-update.rst b/news/doc-update.rst new file mode 100644 index 00000000..1993a616 --- /dev/null +++ b/news/doc-update.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* Paths in our documentation reflect changes made in code. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From ada9157a2264592343c8ef97d19cd8c4c99a488a Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 12 Nov 2024 11:02:46 -0500 Subject: [PATCH 058/445] restructure tests dir to match src --- tests/{ => diffpy/utils/parsers}/test_loaddata.py | 0 tests/{ => diffpy/utils/parsers}/test_serialization.py | 0 .../utils/scattering_objects}/test_diffraction_objects.py | 0 tests/{ => diffpy/utils}/test_resample.py | 0 tests/{ => diffpy/utils}/test_tools.py | 0 tests/{ => diffpy/utils}/test_version.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tests/{ => diffpy/utils/parsers}/test_loaddata.py (100%) rename tests/{ => diffpy/utils/parsers}/test_serialization.py (100%) rename tests/{ => diffpy/utils/scattering_objects}/test_diffraction_objects.py (100%) rename tests/{ => diffpy/utils}/test_resample.py (100%) rename tests/{ => diffpy/utils}/test_tools.py (100%) rename tests/{ => diffpy/utils}/test_version.py (100%) diff --git a/tests/test_loaddata.py b/tests/diffpy/utils/parsers/test_loaddata.py similarity index 100% rename from tests/test_loaddata.py rename to tests/diffpy/utils/parsers/test_loaddata.py diff --git a/tests/test_serialization.py b/tests/diffpy/utils/parsers/test_serialization.py similarity index 100% rename from tests/test_serialization.py rename to tests/diffpy/utils/parsers/test_serialization.py diff --git a/tests/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py similarity index 100% rename from tests/test_diffraction_objects.py rename to tests/diffpy/utils/scattering_objects/test_diffraction_objects.py diff --git a/tests/test_resample.py b/tests/diffpy/utils/test_resample.py similarity index 100% rename from tests/test_resample.py rename to tests/diffpy/utils/test_resample.py diff --git a/tests/test_tools.py b/tests/diffpy/utils/test_tools.py similarity index 100% rename from tests/test_tools.py rename to tests/diffpy/utils/test_tools.py diff --git a/tests/test_version.py b/tests/diffpy/utils/test_version.py similarity index 100% rename from tests/test_version.py rename to tests/diffpy/utils/test_version.py From b7f9ff24ba3c64f7d7358031d060f8da018b0d3d Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 12 Nov 2024 11:25:20 -0500 Subject: [PATCH 059/445] fixed file paths in test files for the changed tests structure --- tests/conftest.py | 3 ++- tests/diffpy/utils/parsers/test_serialization.py | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5471acc1..b1783f57 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,8 +22,9 @@ def user_filesystem(tmp_path): @pytest.fixture def datafile(): """Fixture to dynamically load any test file.""" + base_path = Path(__file__).parent / "testdata" # Adjusted base path def _load(filename): - return "tests/testdata/" + filename + return str(base_path / filename) return _load diff --git a/tests/diffpy/utils/parsers/test_serialization.py b/tests/diffpy/utils/parsers/test_serialization.py index fc3696bf..97f01c9b 100644 --- a/tests/diffpy/utils/parsers/test_serialization.py +++ b/tests/diffpy/utils/parsers/test_serialization.py @@ -7,20 +7,19 @@ from diffpy.utils.parsers.loaddata import loadData from diffpy.utils.parsers.serialization import deserialize_data, serialize_data -tests_dir = os.path.dirname(os.path.abspath(locals().get("__file__", "file.py"))) - def test_load_multiple(tmp_path, datafile): # Load test data targetjson = datafile("targetjson.json") generatedjson = tmp_path / "generated_serialization.json" - tlm_list = os.listdir(os.path.join(tests_dir, "testdata", "dbload")) - tlm_list.sort() + dbload_dir = os.path.dirname(datafile("dbload/e1.gr")) + tlm_list = sorted(os.listdir(dbload_dir)) + generated_data = None for hfname in tlm_list: # gather data using loadData - headerfile = os.path.normpath(os.path.join(tests_dir, "testdata", "dbload", hfname)) + headerfile = os.path.join(dbload_dir, hfname) hdata = loadData(headerfile, headers=True) data_table = loadData(headerfile) From 116f5c5f9db17fe8f9becde18dbcd87e3ab77875 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 12 Nov 2024 11:27:01 -0500 Subject: [PATCH 060/445] add news item --- news/tests-restruct.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/tests-restruct.rst diff --git a/news/tests-restruct.rst b/news/tests-restruct.rst new file mode 100644 index 00000000..4b28c56f --- /dev/null +++ b/news/tests-restruct.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* \tests directory tree to match \src + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* file paths of the test files according to new \tests directory tree + +**Security:** + +* From 9e292035f0f445a4c8803c62c023a79dacf99de3 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 12 Nov 2024 21:25:42 -0500 Subject: [PATCH 061/445] used pathlib Path --- tests/conftest.py | 2 +- tests/diffpy/utils/parsers/test_serialization.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b1783f57..09de40f6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,6 +25,6 @@ def datafile(): base_path = Path(__file__).parent / "testdata" # Adjusted base path def _load(filename): - return str(base_path / filename) + return base_path / filename return _load diff --git a/tests/diffpy/utils/parsers/test_serialization.py b/tests/diffpy/utils/parsers/test_serialization.py index 97f01c9b..cc205c43 100644 --- a/tests/diffpy/utils/parsers/test_serialization.py +++ b/tests/diffpy/utils/parsers/test_serialization.py @@ -1,4 +1,5 @@ import os +from pathlib import Path import numpy import pytest @@ -13,19 +14,18 @@ def test_load_multiple(tmp_path, datafile): targetjson = datafile("targetjson.json") generatedjson = tmp_path / "generated_serialization.json" - dbload_dir = os.path.dirname(datafile("dbload/e1.gr")) - tlm_list = sorted(os.listdir(dbload_dir)) + dbload_dir = datafile("dbload") + tlm_list = sorted(dbload_dir.glob("*.gr")) generated_data = None - for hfname in tlm_list: + for headerfile in tlm_list: # gather data using loadData - headerfile = os.path.join(dbload_dir, hfname) hdata = loadData(headerfile, headers=True) data_table = loadData(headerfile) # check path extraction generated_data = serialize_data(headerfile, hdata, data_table, dt_colnames=["r", "gr"], show_path=True) - assert headerfile == os.path.normpath(generated_data[hfname].pop("path")) + assert headerfile == Path(generated_data[headerfile.name].pop("path")) # rerun without path information and save to file generated_data = serialize_data( From 68841b97bd121139720de70cbc84eb7138387a77 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Tue, 12 Nov 2024 21:27:49 -0500 Subject: [PATCH 062/445] fixed pre-commit error --- tests/diffpy/utils/parsers/test_serialization.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/diffpy/utils/parsers/test_serialization.py b/tests/diffpy/utils/parsers/test_serialization.py index cc205c43..0ba397ad 100644 --- a/tests/diffpy/utils/parsers/test_serialization.py +++ b/tests/diffpy/utils/parsers/test_serialization.py @@ -1,4 +1,3 @@ -import os from pathlib import Path import numpy From 2e15a776ecec3ece9fe83849690adc1317f84ff8 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 14 Nov 2024 11:10:07 -0500 Subject: [PATCH 063/445] Modify tests for DiffractionObject --- .../scattering_objects/test_diffraction_objects.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index a155b95e..b2225db8 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -4,7 +4,7 @@ import pytest from freezegun import freeze_time -from diffpy.utils.scattering_objects.diffraction_objects import Diffraction_object +from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject params = [ ( # Default @@ -222,8 +222,8 @@ @pytest.mark.parametrize("inputs1, inputs2, expected", params) def test_diffraction_objects_equality(inputs1, inputs2, expected): - diffraction_object1 = Diffraction_object() - diffraction_object2 = Diffraction_object() + diffraction_object1 = DiffractionObject() + diffraction_object2 = DiffractionObject() diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] for i, attribute in enumerate(diffraction_object1_attributes): setattr(diffraction_object1, attribute, inputs1[i]) @@ -235,7 +235,7 @@ def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - test = Diffraction_object() + test = DiffractionObject() test.wavelength = 1.54 test.name = "test" test.scat_quantity = "x-ray" @@ -251,7 +251,7 @@ def test_dump(tmp_path, mocker): with open(file, "r") as f: actual = f.read() expected = ( - "[Diffraction_object]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" + "[DiffractionObject]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" "thing2 = thing2\npackage_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n" "creation_time = 2012-01-14 00:00:00\n\n" "#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" From 931dc7bd01fad5507bc54b8d91d0f2218fd045a5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 14 Nov 2024 11:10:23 -0500 Subject: [PATCH 064/445] Make DiffractionObject as a copy of diffraciton_object --- .../scattering_objects/diffraction_objects.py | 481 +++++++++++++++++- 1 file changed, 480 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 94d2831a..97b61fe2 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -1,6 +1,6 @@ import datetime from copy import deepcopy - +import warnings import numpy as np from diffpy.utils.tools import get_package_info @@ -18,6 +18,20 @@ class Diffraction_object: + """A class to represent and manipulate data associated with diffraction experiments. + + .. deprecated:: 3.5.1 + `Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0. It is replaced by + `DiffractionObject` to follow the class naming convention. + """ + + warnings.warn( + "Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0, It is replaced by " + "DiffractionObject` to follow the class naming convention.", + DeprecationWarning, + stacklevel=2, + ) + def __init__(self, name="", wavelength=None): self.name = name self.wavelength = wavelength @@ -461,3 +475,468 @@ def dump(self, filepath, xtype=None): f.write(f"{key} = {value}\n") f.write("\n#### start data\n") np.savetxt(f, data_to_save, delimiter=" ") + + +import datetime +from copy import deepcopy + +import numpy as np + +from diffpy.utils.tools import get_package_info + +QQUANTITIES = ["q"] +ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] +DQUANTITIES = ["d", "dspace"] +XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES +XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] + +x_grid_emsg = ( + "objects are not on the same x-grid. You may add them using the self.add method " + "and specifying how to handle the mismatch." +) + + +class DiffractionObject: + def __init__(self, name="", wavelength=None): + self.name = name + self.wavelength = wavelength + self.scat_quantity = "" + self.on_q = [np.empty(0), np.empty(0)] + self.on_tth = [np.empty(0), np.empty(0)] + self.on_d = [np.empty(0), np.empty(0)] + self._all_arrays = [self.on_q, self.on_tth] + self.metadata = {} + + def __eq__(self, other): + if not isinstance(other, DiffractionObject): + return NotImplemented + self_attributes = [key for key in self.__dict__ if not key.startswith("_")] + other_attributes = [key for key in other.__dict__ if not key.startswith("_")] + if not sorted(self_attributes) == sorted(other_attributes): + return False + for key in self_attributes: + value = getattr(self, key) + other_value = getattr(other, key) + if isinstance(value, float): + if ( + not (value is None and other_value is None) + and (value is None) + or (other_value is None) + or not np.isclose(value, other_value, rtol=1e-5) + ): + return False + elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): + if not all(np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): + return False + else: + if value != other_value: + return False + return True + + def __add__(self, other): + summed = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + summed.on_tth[1] = self.on_tth[1] + other + summed.on_q[1] = self.on_q[1] + other + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to sum two DiffractionObject objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] + summed.on_q[1] = self.on_q[1] + other.on_q[1] + return summed + + def __radd__(self, other): + summed = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + summed.on_tth[1] = self.on_tth[1] + other + summed.on_q[1] = self.on_q[1] + other + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to sum two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] + summed.on_q[1] = self.on_q[1] + other.on_q[1] + return summed + + def __sub__(self, other): + subtracted = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + subtracted.on_tth[1] = self.on_tth[1] - other + subtracted.on_q[1] = self.on_q[1] - other + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to subtract two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] + subtracted.on_q[1] = self.on_q[1] - other.on_q[1] + return subtracted + + def __rsub__(self, other): + subtracted = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + subtracted.on_tth[1] = other - self.on_tth[1] + subtracted.on_q[1] = other - self.on_q[1] + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to subtract two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] + subtracted.on_q[1] = other.on_q[1] - self.on_q[1] + return subtracted + + def __mul__(self, other): + multiplied = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + multiplied.on_tth[1] = other * self.on_tth[1] + multiplied.on_q[1] = other * self.on_q[1] + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to multiply two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] + multiplied.on_q[1] = self.on_q[1] * other.on_q[1] + return multiplied + + def __rmul__(self, other): + multiplied = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + multiplied.on_tth[1] = other * self.on_tth[1] + multiplied.on_q[1] = other * self.on_q[1] + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] + multiplied.on_q[1] = self.on_q[1] * other.on_q[1] + return multiplied + + def __truediv__(self, other): + divided = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + divided.on_tth[1] = other / self.on_tth[1] + divided.on_q[1] = other / self.on_q[1] + elif not isinstance(other, DiffractionObject): + raise TypeError("I only know how to multiply two Scattering_object objects") + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] + divided.on_q[1] = self.on_q[1] / other.on_q[1] + return divided + + def __rtruediv__(self, other): + divided = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + divided.on_tth[1] = other / self.on_tth[1] + divided.on_q[1] = other / self.on_q[1] + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_emsg) + else: + divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] + divided.on_q[1] = other.on_q[1] / self.on_q[1] + return divided + + def set_angles_from_list(self, angles_list): + self.angles = angles_list + self.n_steps = len(angles_list) - 1.0 + self.begin_angle = self.angles[0] + self.end_angle = self.angles[-1] + + def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None): + """ + create an array of linear spaced Q-values + + Parameters + ---------- + begin_q float + the beginning angle + end_q float + the ending angle + step_size float + the size of the step between points. Only specify step_size or n_steps, not both + n_steps integer + the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both + + Returns + ------- + Sets self.qs + self.qs array of floats + the q values in the independent array + + """ + self.qs = self._set_array_from_range(begin_q, end_q, step_size=step_size, n_steps=n_steps) + + def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps=None): + """ + create an array of linear spaced angle-values + + Parameters + ---------- + begin_angle float + the beginning angle + end_angle float + the ending angle + step_size float + the size of the step between points. Only specify step_size or n_steps, not both + n_steps integer + the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both + + Returns + ------- + Sets self.angles + self.angles array of floats + the q values in the independent array + + """ + self.angles = self._set_array_from_range(begin_angle, end_angle, step_size=step_size, n_steps=n_steps) + + def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): + if step_size is not None and n_steps is not None: + print( + "WARNING: both step_size and n_steps have been given. n_steps will be used and step_size will be " + "reset." + ) + array = np.linspace(begin, end, n_steps) + elif step_size is not None: + array = np.arange(begin, end, step_size) + elif n_steps is not None: + array = np.linspace(begin, end, n_steps) + return array + + def get_angle_index(self, angle): + count = 0 + for i, target in enumerate(self.angles): + if angle == target: + return i + else: + count += 1 + if count >= len(self.angles): + raise IndexError(f"WARNING: no angle {angle} found in angles list") + + def insert_scattering_quantity( + 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: dict + the metadata in the form of a dictionary of user-supplied key:value pairs + + Returns + ------- + + """ + self.input_xtype = xtype + # empty attributes have been defined in the __init__ method so only + # set the attributes that are not empty to avoid emptying them by mistake + 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 + if xtype.lower() in QQUANTITIES: + self.on_q = [np.array(xarray), np.array(yarray)] + elif xtype.lower() in ANGLEQUANTITIES: + self.on_tth = [np.array(xarray), np.array(yarray)] + elif xtype.lower() in DQUANTITIES: + self.on_tth = [np.array(xarray), np.array(yarray)] + self.set_all_arrays() + + def q_to_tth(self): + r""" + Helper function to convert q to two-theta. + + By definition the relationship is: + + .. math:: + + \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} + + thus + + .. math:: + + 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) + + Parameters + ---------- + q : array + An array of :math:`q` values + + wavelength : float + Wavelength of the incoming x-rays + + Function adapted from scikit-beam. Thanks to those developers + + Returns + ------- + two_theta : array + An array of :math:`2\theta` values in radians + """ + q = self.on_q[0] + q = np.asarray(q) + wavelength = float(self.wavelength) + pre_factor = wavelength / (4 * np.pi) + return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) + + def tth_to_q(self): + r""" + Helper function to convert two-theta to q + + By definition the relationship is + + .. math:: + + \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} + + thus + + .. math:: + + q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} + + + + Parameters + ---------- + two_theta : array + An array of :math:`2\theta` values in units of degrees + + wavelength : float + Wavelength of the incoming x-rays + + Function adapted from scikit-beam. Thanks to those developers. + + Returns + ------- + q : array + An array of :math:`q` values in the inverse of the units + of ``wavelength`` + """ + two_theta = np.asarray(np.deg2rad(self.on_tth[0])) + wavelength = float(self.wavelength) + pre_factor = (4 * np.pi) / wavelength + return pre_factor * np.sin(two_theta / 2) + + def set_all_arrays(self): + master_array, xtype = self._get_original_array() + if xtype == "q": + self.on_tth[0] = self.q_to_tth() + self.on_tth[1] = master_array[1] + if xtype == "tth": + self.on_q[0] = self.tth_to_q() + self.on_q[1] = master_array[1] + self.tthmin = self.on_tth[0][0] + self.tthmax = self.on_tth[0][-1] + self.qmin = self.on_q[0][0] + self.qmax = self.on_q[0][-1] + + def _get_original_array(self): + if self.input_xtype in QQUANTITIES: + return self.on_q, "q" + elif self.input_xtype in ANGLEQUANTITIES: + return self.on_tth, "tth" + elif self.input_xtype in DQUANTITIES: + return self.on_d, "d" + + def scale_to(self, target_diff_object, xtype=None, xvalue=None): + f""" + returns a new diffraction object which is the current object but recaled in y to the target + + Parameters + ---------- + target_diff_object: DiffractionObject + the diffractoin object you want to scale the current one on to + xtype: string, optional. Default is Q + the xtype, from {XQUANTITIES}, that you will specify a point from to scale to + xvalue: float. Default is the midpoint of the array + the y-value in the target at this x-value will be used as the factor to scale to. + The entire array is scaled be the factor that places on on top of the other at that point. + xvalue does not have to be in the x-array, the point closest to this point will be used for the scaling. + + Returns + ------- + the rescaled DiffractionObject as a new object + + """ + scaled = deepcopy(self) + if xtype is None: + xtype = "q" + + data = self.on_xtype(xtype) + target = target_diff_object.on_xtype(xtype) + if xvalue is None: + xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 + + xindex = (np.abs(data[0] - xvalue)).argmin() + ytarget = target[1][xindex] + yself = data[1][xindex] + scaled.on_tth[1] = data[1] * ytarget / yself + scaled.on_q[1] = data[1] * ytarget / yself + return scaled + + def on_xtype(self, xtype): + """ + return a 2D np array with x in the first column and y in the second for x of type type + Parameters + ---------- + xtype + + Returns + ------- + + """ + if xtype.lower() in ANGLEQUANTITIES: + return self.on_tth + elif xtype.lower() in QQUANTITIES: + return self.on_q + elif xtype.lower() in DQUANTITIES: + return self.on_d + pass + + def dump(self, filepath, xtype=None): + if xtype is None: + xtype = " q" + if xtype == "q": + data_to_save = np.column_stack((self.on_q[0], self.on_q[1])) + elif xtype == "tth": + data_to_save = np.column_stack((self.on_tth[0], self.on_tth[1])) + elif xtype == "d": + data_to_save = np.column_stack((self.on_d[0], self.on_d[1])) + else: + print(f"WARNING: cannot handle the xtype '{xtype}'") + self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) + self.metadata["creation_time"] = datetime.datetime.now() + + with open(filepath, "w") as f: + f.write( + f"[DiffractionObject]\nname = {self.name}\nwavelength = {self.wavelength}\n" + f"scat_quantity = {self.scat_quantity}\n" + ) + for key, value in self.metadata.items(): + f.write(f"{key} = {value}\n") + f.write("\n#### start data\n") + np.savetxt(f, data_to_save, delimiter=" ") From 21520964ef89658ece0a898d51a1df38c9b85840 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 14 Nov 2024 11:13:24 -0500 Subject: [PATCH 065/445] Remove redundant imports for new DiffractionObject --- .../scattering_objects/diffraction_objects.py | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 97b61fe2..926ed8b2 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -1,6 +1,7 @@ import datetime -from copy import deepcopy import warnings +from copy import deepcopy + import numpy as np from diffpy.utils.tools import get_package_info @@ -24,14 +25,14 @@ class Diffraction_object: `Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0. It is replaced by `DiffractionObject` to follow the class naming convention. """ - + warnings.warn( "Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0, It is replaced by " "DiffractionObject` to follow the class naming convention.", DeprecationWarning, stacklevel=2, ) - + def __init__(self, name="", wavelength=None): self.name = name self.wavelength = wavelength @@ -477,25 +478,6 @@ def dump(self, filepath, xtype=None): np.savetxt(f, data_to_save, delimiter=" ") -import datetime -from copy import deepcopy - -import numpy as np - -from diffpy.utils.tools import get_package_info - -QQUANTITIES = ["q"] -ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] -DQUANTITIES = ["d", "dspace"] -XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES -XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] - -x_grid_emsg = ( - "objects are not on the same x-grid. You may add them using the self.add method " - "and specifying how to handle the mismatch." -) - - class DiffractionObject: def __init__(self, name="", wavelength=None): self.name = name From e3ac7a9f283f07b8afc675cf7bf4e9be75dfba02 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 14 Nov 2024 11:16:15 -0500 Subject: [PATCH 066/445] Add news --- news/deprecate.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/deprecate.rst diff --git a/news/deprecate.rst b/news/deprecate.rst new file mode 100644 index 00000000..31dd10e5 --- /dev/null +++ b/news/deprecate.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* Diffraction_object class, renamed to DiffractionObject + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 7841ff298b14246e92fb9ed528691d9c4c4fc184 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 14 Nov 2024 23:08:06 -0500 Subject: [PATCH 067/445] fix docstring and add error messages --- .../scattering_objects/diffraction_objects.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 926ed8b2..17a687f5 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -763,25 +763,29 @@ def q_to_tth(self): 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) + Function adapted from scikit-beam. Thanks to those developers + Parameters ---------- q : array - An array of :math:`q` values + The array of :math:`q` values wavelength : float Wavelength of the incoming x-rays - Function adapted from scikit-beam. Thanks to those developers - Returns ------- two_theta : array - An array of :math:`2\theta` values in radians + The array of :math:`2\theta` values in radians """ q = self.on_q[0] q = np.asarray(q) wavelength = float(self.wavelength) pre_factor = wavelength / (4 * np.pi) + if np.any(np.abs(q * pre_factor) > 1): + raise ValueError( + "Invalid input for arcsin: some values exceed the range [-1, 1]. Check wavelength or q values." + ) return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) def tth_to_q(self): @@ -800,25 +804,25 @@ def tth_to_q(self): q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} - + Function adapted from scikit-beam. Thanks to those developers. Parameters ---------- two_theta : array - An array of :math:`2\theta` values in units of degrees + The array of :math:`2\theta` values in units of degrees wavelength : float Wavelength of the incoming x-rays - Function adapted from scikit-beam. Thanks to those developers. - Returns ------- q : array - An array of :math:`q` values in the inverse of the units + The array of :math:`q` values in the inverse of the units of ``wavelength`` """ two_theta = np.asarray(np.deg2rad(self.on_tth[0])) + if np.any(two_theta > np.pi): + raise ValueError("Two theta exceeds 180 degrees.") wavelength = float(self.wavelength) pre_factor = (4 * np.pi) / wavelength return pre_factor * np.sin(two_theta / 2) From 1b0fa19e59f65f45aa1e034e81b4124877127fea Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 14 Nov 2024 23:11:08 -0500 Subject: [PATCH 068/445] add tests --- .../test_diffraction_objects.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index b2225db8..57e6babf 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -231,6 +231,42 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert (diffraction_object1 == diffraction_object2) == expected +def test_q_to_tth(): + # valid q values including edge cases when q=0 or 1 + # expected tth values are 2*arcsin(q) + actual = DiffractionObject(wavelength=4 * np.pi) + setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) + actual_tth = actual.q_to_tth() + expected_tth = [0, 23.07392, 47.15636, 73.73980, 106.26020, 180] + assert np.allclose(actual_tth, expected_tth) + + +def test_q_to_tth_bad(): + # invalid q values when arcsin value is not in the range of [-1, 1] + actual = DiffractionObject(wavelength=4 * np.pi) + setattr(actual, "on_q", [[0.6, 0.8, 1, 1.2], [1, 2, 3, 4]]) + with pytest.raises(ValueError): + actual.q_to_tth() + + +def test_tth_to_q(): + # valid tth values including edge cases when tth=0 or 180 degree + # expected q vales are sin15, sin30, sin45, sin60, sin90 + actual = DiffractionObject(wavelength=4 * np.pi) + setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) + actual_q = actual.tth_to_q() + expected_q = [0, 0.258819, 0.5, 0.707107, 0.866025, 1] + assert np.allclose(actual_q, expected_q) + + +def test_tth_to_q_bad(): + # invalid tth value of > 180 degree + actual = DiffractionObject(wavelength=4 * np.pi) + setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]]) + with pytest.raises(ValueError): + actual.tth_to_q() + + def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) From 1c4d1fc9ef4d2b9d2ab0d2372aafdf2b3947cd17 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 15 Nov 2024 22:37:23 -0500 Subject: [PATCH 069/445] Use pytest for test_loaddata --- tests/diffpy/utils/parsers/test_loaddata.py | 128 ++++++++++---------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/tests/diffpy/utils/parsers/test_loaddata.py b/tests/diffpy/utils/parsers/test_loaddata.py index 3d775c74..45874fab 100644 --- a/tests/diffpy/utils/parsers/test_loaddata.py +++ b/tests/diffpy/utils/parsers/test_loaddata.py @@ -3,71 +3,73 @@ """Unit tests for diffpy.utils.parsers.loaddata """ -import unittest - -import numpy +import numpy as np import pytest from diffpy.utils.parsers.loaddata import loadData -############################################################################## -class TestLoadData(unittest.TestCase): - @pytest.fixture(autouse=True) - def prepare_fixture(self, datafile): - self.datafile = datafile - - def test_loadData_default(self): - """check loadData() with default options""" - loaddata01 = self.datafile("loaddata01.txt") - d2c = numpy.array([[3, 31], [4, 32], [5, 33]]) - self.assertRaises(IOError, loadData, "doesnotexist") - # the default minrows=10 makes it read from the third line - d = loadData(loaddata01) - self.assertTrue(numpy.array_equal(d2c, d)) - # the usecols=(0, 1) would make it read from the third line - d = loadData(loaddata01, minrows=1, usecols=(0, 1)) - self.assertTrue(numpy.array_equal(d2c, d)) - # check the effect of usecols effect - d = loadData(loaddata01, usecols=(0,)) - self.assertTrue(numpy.array_equal(d2c[:, 0], d)) - d = loadData(loaddata01, usecols=(1,)) - self.assertTrue(numpy.array_equal(d2c[:, 1], d)) - return - - def test_loadData_1column(self): - """check loading of one-column data.""" - loaddata01 = self.datafile("loaddata01.txt") - d1c = numpy.arange(1, 6) - d = loadData(loaddata01, usecols=[0], minrows=1) - self.assertTrue(numpy.array_equal(d1c, d)) - d = loadData(loaddata01, usecols=[0], minrows=2) - self.assertTrue(numpy.array_equal(d1c, d)) - d = loadData(loaddata01, usecols=[0], minrows=3) - self.assertFalse(numpy.array_equal(d1c, d)) - return - - def test_loadData_headers(self): - """check loadData() with headers options enabled""" - loaddatawithheaders = self.datafile("loaddatawithheaders.txt") - hignore = ["# ", "// ", "["] # ignore lines beginning with these strings - delimiter = ": " # what our data should be separated by - hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) - # only fourteen lines of data are formatted properly - assert len(hdata) == 14 - # check the following are floats - vfloats = ["wavelength", "qmaxinst", "qmin", "qmax", "bgscale"] - for name in vfloats: - assert isinstance(hdata.get(name), float) - # check the following are NOT floats - vnfloats = ["composition", "rmax", "rmin", "rstep", "rpoly"] - for name in vnfloats: - assert not isinstance(hdata.get(name), float) - - -# End of class TestRoutines - -if __name__ == "__main__": - unittest.main() - -# End of file +def test_loadData_default(datafile): + """check loadData() with default options""" + loaddata01 = datafile("loaddata01.txt") + d2c = np.array([[3, 31], [4, 32], [5, 33]]) + + with pytest.raises(IOError): + loadData("doesnotexist") + + # The default minrows=10 makes it read from the third line + d = loadData(loaddata01) + assert np.array_equal(d2c, d) + + # The usecols=(0, 1) would make it read from the third line + d = loadData(loaddata01, minrows=1, usecols=(0, 1)) + assert np.array_equal(d2c, d) + + # Check the effect of usecols effect + d = loadData(loaddata01, usecols=(0,)) + assert np.array_equal(d2c[:, 0], d) + + d = loadData(loaddata01, usecols=(1,)) + assert np.array_equal(d2c[:, 1], d) + + +def test_loadData_1column(datafile): + """check loading of one-column data.""" + loaddata01 = datafile("loaddata01.txt") + d1c = np.arange(1, 6) + + # Assertions using pytest's assert + d = loadData(loaddata01, usecols=[0], minrows=1) + assert np.array_equal(d1c, d) + + d = loadData(loaddata01, usecols=[0], minrows=2) + assert np.array_equal(d1c, d) + + d = loadData(loaddata01, usecols=[0], minrows=3) + assert not np.array_equal(d1c, d) + + return + + +def test_loadData_headers(datafile): + """check loadData() with headers options enabled""" + loaddatawithheaders = datafile("loaddatawithheaders.txt") + hignore = ["# ", "// ", "["] # ignore lines beginning with these strings + delimiter = ": " # what our data should be separated by + + # Load data with headers + hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) + + # Assertions using pytest + # Only fourteen lines of data are formatted properly + assert len(hdata) == 14 + + # Check the following are floats + vfloats = ["wavelength", "qmaxinst", "qmin", "qmax", "bgscale"] + for name in vfloats: + assert isinstance(hdata.get(name), float) + + # Check the following are NOT floats + vnfloats = ["composition", "rmax", "rmin", "rstep", "rpoly"] + for name in vnfloats: + assert not isinstance(hdata.get(name), float) From bf88396cf1a9f703c066dccafb1026003f886d2d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 15 Nov 2024 22:40:13 -0500 Subject: [PATCH 070/445] Add news --- news/test-refactor.rst | 23 +++++++++++++++++++++ tests/diffpy/utils/parsers/test_loaddata.py | 3 --- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 news/test-refactor.rst diff --git a/news/test-refactor.rst b/news/test-refactor.rst new file mode 100644 index 00000000..095df231 --- /dev/null +++ b/news/test-refactor.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Unittest to Pytest migration for test_loaddata.py + +**Security:** + +* diff --git a/tests/diffpy/utils/parsers/test_loaddata.py b/tests/diffpy/utils/parsers/test_loaddata.py index 45874fab..8ee80bf5 100644 --- a/tests/diffpy/utils/parsers/test_loaddata.py +++ b/tests/diffpy/utils/parsers/test_loaddata.py @@ -48,8 +48,6 @@ def test_loadData_1column(datafile): d = loadData(loaddata01, usecols=[0], minrows=3) assert not np.array_equal(d1c, d) - return - def test_loadData_headers(datafile): """check loadData() with headers options enabled""" @@ -60,7 +58,6 @@ def test_loadData_headers(datafile): # Load data with headers hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) - # Assertions using pytest # Only fourteen lines of data are formatted properly assert len(hdata) == 14 From 0f4c5400869c4daf3aad6e26fff57300ec69b427 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 16 Nov 2024 23:11:00 -0500 Subject: [PATCH 071/445] Use actual data to test headers and test error IO msg --- tests/diffpy/utils/parsers/test_loaddata.py | 34 ++++++++++++--------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/tests/diffpy/utils/parsers/test_loaddata.py b/tests/diffpy/utils/parsers/test_loaddata.py index 8ee80bf5..a747be8a 100644 --- a/tests/diffpy/utils/parsers/test_loaddata.py +++ b/tests/diffpy/utils/parsers/test_loaddata.py @@ -14,8 +14,9 @@ def test_loadData_default(datafile): loaddata01 = datafile("loaddata01.txt") d2c = np.array([[3, 31], [4, 32], [5, 33]]) - with pytest.raises(IOError): + with pytest.raises(IOError) as err: loadData("doesnotexist") + assert "No such file or directory" in str(err.value) # The default minrows=10 makes it read from the third line d = loadData(loaddata01) @@ -51,22 +52,27 @@ def test_loadData_1column(datafile): def test_loadData_headers(datafile): """check loadData() with headers options enabled""" + expected = { + "wavelength": 0.1, + "dataformat": "Qnm", + "inputfile": "darkSub_rh20_C_01.chi", + "mode": "xray", + "bgscale": 1.2998929285, + "composition": "0.800.20", + "outputtype": "gr", + "qmaxinst": 25.0, + "qmin": 0.1, + "qmax": 25.0, + "rmax": "100.0r", + "rmin": "0.0r", + "rstep": "0.01r", + "rpoly": "0.9r", + } + loaddatawithheaders = datafile("loaddatawithheaders.txt") hignore = ["# ", "// ", "["] # ignore lines beginning with these strings delimiter = ": " # what our data should be separated by # Load data with headers hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) - - # Only fourteen lines of data are formatted properly - assert len(hdata) == 14 - - # Check the following are floats - vfloats = ["wavelength", "qmaxinst", "qmin", "qmax", "bgscale"] - for name in vfloats: - assert isinstance(hdata.get(name), float) - - # Check the following are NOT floats - vnfloats = ["composition", "rmax", "rmin", "rstep", "rpoly"] - for name in vnfloats: - assert not isinstance(hdata.get(name), float) + assert hdata == expected From 4b055189e8927793943ad10266086208ae37cafd Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 17 Nov 2024 11:37:31 -0500 Subject: [PATCH 072/445] Add custom IOError msg for file not found in loaddata --- src/diffpy/utils/parsers/loaddata.py | 6 ++++++ tests/diffpy/utils/parsers/test_loaddata.py | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 18375d90..870e6016 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -13,6 +13,8 @@ # ############################################################################## +import os + import numpy @@ -99,6 +101,10 @@ def countcolumnsvalues(line): nc = nv = 0 return nc, nv + # Check if file exists before trying to open + if not os.path.exists(filename): + raise IOError(f"File {filename} cannot be found. Please rerun the program specifying a valid filename.") + # make sure fid gets cleaned up with open(filename, "rb") as fid: # search for the start of datablock diff --git a/tests/diffpy/utils/parsers/test_loaddata.py b/tests/diffpy/utils/parsers/test_loaddata.py index a747be8a..a7e273e7 100644 --- a/tests/diffpy/utils/parsers/test_loaddata.py +++ b/tests/diffpy/utils/parsers/test_loaddata.py @@ -15,8 +15,11 @@ def test_loadData_default(datafile): d2c = np.array([[3, 31], [4, 32], [5, 33]]) with pytest.raises(IOError) as err: - loadData("doesnotexist") - assert "No such file or directory" in str(err.value) + loadData("doesnotexist.txt") + assert ( + str(err.value) + == "File doesnotexist.txt cannot be found. Please rerun the program specifying a valid filename." + ) # The default minrows=10 makes it read from the third line d = loadData(loaddata01) From 4e85b0a215d1dd344689616d3a6d1a4d73c923f4 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 20 Nov 2024 12:00:54 -0500 Subject: [PATCH 073/445] edit error message --- .../utils/scattering_objects/diffraction_objects.py | 9 +++++---- .../utils/scattering_objects/test_diffraction_objects.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 17a687f5..0ff9f0ce 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -783,9 +783,7 @@ def q_to_tth(self): wavelength = float(self.wavelength) pre_factor = wavelength / (4 * np.pi) if np.any(np.abs(q * pre_factor) > 1): - raise ValueError( - "Invalid input for arcsin: some values exceed the range [-1, 1]. Check wavelength or q values." - ) + raise ValueError("Please check if you entered an incorrect wavelength or q value.") return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) def tth_to_q(self): @@ -822,7 +820,10 @@ def tth_to_q(self): """ two_theta = np.asarray(np.deg2rad(self.on_tth[0])) if np.any(two_theta > np.pi): - raise ValueError("Two theta exceeds 180 degrees.") + raise ValueError( + "Two theta exceeds 180 degrees. Please check if invalid values were entered " + "or if degrees were incorrectly specified as radians." + ) wavelength = float(self.wavelength) pre_factor = (4 * np.pi) / wavelength return pre_factor * np.sin(two_theta / 2) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 57e6babf..4caa61ae 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -232,7 +232,7 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_q_to_tth(): - # valid q values including edge cases when q=0 or 1 + # valid q values including edge cases when q=0 or tth=180 after converting # expected tth values are 2*arcsin(q) actual = DiffractionObject(wavelength=4 * np.pi) setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) @@ -242,7 +242,7 @@ def test_q_to_tth(): def test_q_to_tth_bad(): - # invalid q values when arcsin value is not in the range of [-1, 1] + # invalid wavelength or q values when arcsin value is not in the range of [-1, 1] actual = DiffractionObject(wavelength=4 * np.pi) setattr(actual, "on_q", [[0.6, 0.8, 1, 1.2], [1, 2, 3, 4]]) with pytest.raises(ValueError): From a33482b240dd276c508700936c5c07a31e2eb5ed Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 21 Nov 2024 18:19:43 -0500 Subject: [PATCH 074/445] add more tests --- .../test_diffraction_objects.py | 79 ++++++++++++++++--- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 4caa61ae..36aace8f 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -232,8 +232,8 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_q_to_tth(): - # valid q values including edge cases when q=0 or tth=180 after converting - # expected tth values are 2*arcsin(q) + # Valid q values that should result in 0-180 tth values after conversion + # expected tth values are 2*arcsin(q) in degrees actual = DiffractionObject(wavelength=4 * np.pi) setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) actual_tth = actual.q_to_tth() @@ -241,16 +241,43 @@ def test_q_to_tth(): assert np.allclose(actual_tth, expected_tth) -def test_q_to_tth_bad(): - # invalid wavelength or q values when arcsin value is not in the range of [-1, 1] - actual = DiffractionObject(wavelength=4 * np.pi) - setattr(actual, "on_q", [[0.6, 0.8, 1, 1.2], [1, 2, 3, 4]]) +params_q_to_tth_bad = [ + # UC1: user did not specify wavelength + ( + [None, [0, 0.2, 0.4, 0.6, 0.8, 1]], + "Wavelength is not specified. Please provide a valid wavelength, " + "e.g., DiffractionObject(wavelength=0.71).", + ), + # UC2: user specified invalid q values that result in tth > 180 degrees + ( + [4 * np.pi, [0.2, 0.4, 0.6, 0.8, 1, 1.2]], + "Wavelength * q > 4 * pi. Please check if you entered an incorrect wavelength or q value.", + ), + # UC3: user specified a wrong wavelength that result in tth > 180 degrees + ( + [100, [0, 0.2, 0.4, 0.6, 0.8, 1]], + "Wavelength * q > 4 * pi. Please check if you entered an incorrect wavelength or q value.", + ), + # UC4: user specified an empty q array + ([4 * np.pi, []], "Q array is empty. Please provide valid q values."), + # UC5: user specified a non-numeric value in q array + ( + [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, "invalid"]], + "Invalid value found in q array. Please ensure all values are numeric.", + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) +def test_q_to_tth_bad(inputs, expected): + actual = DiffractionObject(wavelength=inputs[0]) + setattr(actual, "on_q", [inputs[1], [1, 2, 3, 4, 5, 6]]) with pytest.raises(ValueError): actual.q_to_tth() def test_tth_to_q(): - # valid tth values including edge cases when tth=0 or 180 degree + # Valid tth values between 0-180 degrees # expected q vales are sin15, sin30, sin45, sin60, sin90 actual = DiffractionObject(wavelength=4 * np.pi) setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) @@ -259,11 +286,39 @@ def test_tth_to_q(): assert np.allclose(actual_q, expected_q) -def test_tth_to_q_bad(): - # invalid tth value of > 180 degree - actual = DiffractionObject(wavelength=4 * np.pi) - setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]]) - with pytest.raises(ValueError): +params_tth_to_q_bad = [ + # UC1: user did not specify wavelength + ( + [None, [0, 30, 60, 90, 120, 180]], + "Wavelength is not specified. Please provide a valid wavelength, " + "e.g., DiffractionObject(wavelength=0.71).", + ), + # UC2: user specified an invalid tth value of > 180 degrees + ( + [4 * np.pi, [0, 30, 60, 90, 120, 181]], + "Two theta exceeds 180 degrees. Please check the input values for errors.", + ), + # UC3: user did not specify wavelength and specified invalid tth values + ( + [None, [0, 30, 60, 90, 120, 181]], + "Wavelength is not specified. Please provide a valid wavelength, " + "e.g., DiffractionObject(wavelength=0.71).", + ), + # UC4: user specified an empty two theta array + ([4 * np.pi, []], "Two theta array is empty. Please provide valid two theta values."), + # UC5: user specified a non-numeric value in two theta array + ( + [4 * np.pi, [0, 30, 60, 90, 120, "invalid"]], + "Invalid value found in two theta array. Please ensure all values are numeric.", + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) +def test_tth_to_q_bad(inputs, expected): + actual = DiffractionObject(wavelength=inputs[0]) + setattr(actual, "on_tth", [inputs[1], [1, 2, 3, 4, 5, 6]]) + with pytest.raises(ValueError, match=expected): actual.tth_to_q() From 3efde28d9e40ef68a97796e569471e646df24827 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 25 Nov 2024 09:54:09 -0500 Subject: [PATCH 075/445] add warning msg for wavelength, edit error msg --- .../test_diffraction_objects.py | 183 +++++++++++++----- 1 file changed, 134 insertions(+), 49 deletions(-) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 36aace8f..07674a5c 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -231,39 +231,95 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert (diffraction_object1 == diffraction_object2) == expected -def test_q_to_tth(): - # Valid q values that should result in 0-180 tth values after conversion +def _test_valid_diffraction_objects(actual_diffraction_object, function, expected_array): + """Checks the behavior of the DiffractionObject: + when there is no wavelength, we expect the correct warning message and output, + otherwise, we only check the output matches the expected array.""" + if actual_diffraction_object.wavelength is None: + with pytest.warns(UserWarning) as warn_record: + getattr(actual_diffraction_object, function)() + assert str(warn_record[0].message) == ( + "INFO: no wavelength has been specified. You can continue " + "to use the DiffractionObject but some of its powerful features " + "will not be available. To specify a wavelength, you can use " + "DiffractionObject(wavelength=0.71)." + ) + actual_array = getattr(actual_diffraction_object, function)() + return np.allclose(actual_array, expected_array) + + +params_q_to_tth = [ + # UC1: User specified empty q values (without wavelength) + ( + [None, [], []], + [[]], + ), + # UC2: User specified empty q values (with wavelength) + ( + [4 * np.pi, [], []], + [[]], + ), + # UC3: User specified valid q values (without wavelength) # expected tth values are 2*arcsin(q) in degrees - actual = DiffractionObject(wavelength=4 * np.pi) - setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) - actual_tth = actual.q_to_tth() - expected_tth = [0, 23.07392, 47.15636, 73.73980, 106.26020, 180] - assert np.allclose(actual_tth, expected_tth) + ( + [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], + [[]], + ), + # UC4: User specified valid q values (with wavelength) + # expected tth values are 2*arcsin(q) in degrees + ( + [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], + [[0, 23.07392, 47.15636, 73.73980, 106.26020, 180]], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_tth) +def test_q_to_tth(inputs, expected): + actual = DiffractionObject(wavelength=inputs[0]) + actual.on_q = [inputs[1], inputs[2]] + expected_tth = expected[0] + assert _test_valid_diffraction_objects(actual, "q_to_tth", expected_tth) params_q_to_tth_bad = [ - # UC1: user did not specify wavelength + # UC1: user specified invalid q values that result in tth > 180 degrees ( - [None, [0, 0.2, 0.4, 0.6, 0.8, 1]], - "Wavelength is not specified. Please provide a valid wavelength, " - "e.g., DiffractionObject(wavelength=0.71).", + [4 * np.pi, [0.2, 0.4, 0.6, 0.8, 1, 1.2], [1, 2, 3, 4, 5, 6]], + [ + ValueError, + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject.", + ], ), - # UC2: user specified invalid q values that result in tth > 180 degrees + # UC2: user specified a wrong wavelength that result in tth > 180 degrees ( - [4 * np.pi, [0.2, 0.4, 0.6, 0.8, 1, 1.2]], - "Wavelength * q > 4 * pi. Please check if you entered an incorrect wavelength or q value.", + [100, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], + [ + ValueError, + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject.", + ], ), - # UC3: user specified a wrong wavelength that result in tth > 180 degrees + # UC3: user specified a q array that does not match the length of intensity array (without wavelength) ( - [100, [0, 0.2, 0.4, 0.6, 0.8, 1]], - "Wavelength * q > 4 * pi. Please check if you entered an incorrect wavelength or q value.", + [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], + [IndexError, "Please ensure q array and intensity array are the same length."], ), - # UC4: user specified an empty q array - ([4 * np.pi, []], "Q array is empty. Please provide valid q values."), - # UC5: user specified a non-numeric value in q array + # UC4: user specified a q array that does not match the length of intensity array (with wavelength) ( - [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, "invalid"]], - "Invalid value found in q array. Please ensure all values are numeric.", + [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], + [IndexError, "Please ensure q array and intensity array are the same length."], + ), + # UC5: user specified a non-numeric value in q array (without wavelength) + ( + [None, [0, 0.2, 0.4, 0.6, 0.8, "invalid"], [1, 2, 3, 4, 5, 6]], + [TypeError, "Invalid value found in q array. Please ensure all values are numeric."], + ), + # UC5: user specified a non-numeric value in q array (with wavelength) + ( + [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, "invalid"], [1, 2, 3, 4, 5, 6]], + [TypeError, "Invalid value found in q array. Please ensure all values are numeric."], ), ] @@ -271,45 +327,74 @@ def test_q_to_tth(): @pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) def test_q_to_tth_bad(inputs, expected): actual = DiffractionObject(wavelength=inputs[0]) - setattr(actual, "on_q", [inputs[1], [1, 2, 3, 4, 5, 6]]) - with pytest.raises(ValueError): + actual.on_q = [inputs[1], inputs[2]] + with pytest.raises(expected[0], match=expected[1]): actual.q_to_tth() -def test_tth_to_q(): - # Valid tth values between 0-180 degrees +params_tth_to_q = [ + # UC1: User specified empty tth values (without wavelength) + ( + [None, [], []], + [[]], + ), + # UC2: User specified empty tth values (with wavelength) + ( + [4 * np.pi, [], []], + [[]], + ), + # UC3: User specified valid tth values between 0-180 degrees (without wavelength) + ( + [None, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], + [[]], + ), + # UC4: User specified valid tth values between 0-180 degrees (with wavelength) # expected q vales are sin15, sin30, sin45, sin60, sin90 - actual = DiffractionObject(wavelength=4 * np.pi) - setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) - actual_q = actual.tth_to_q() - expected_q = [0, 0.258819, 0.5, 0.707107, 0.866025, 1] - assert np.allclose(actual_q, expected_q) + ( + [4 * np.pi, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], + [[0, 0.258819, 0.5, 0.707107, 0.866025, 1]], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_q) +def test_tth_to_q(inputs, expected): + actual = DiffractionObject(wavelength=inputs[0]) + actual.on_tth = [inputs[1], inputs[2]] + expected_q = expected[0] + assert _test_valid_diffraction_objects(actual, "tth_to_q", expected_q) params_tth_to_q_bad = [ - # UC1: user did not specify wavelength + # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) + ( + [None, [0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), + # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) + ( + [4 * np.pi, [0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), + # UC3: user specified a two theta array that does not match the length of intensity array (without wavelength) ( - [None, [0, 30, 60, 90, 120, 180]], - "Wavelength is not specified. Please provide a valid wavelength, " - "e.g., DiffractionObject(wavelength=0.71).", + [None, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], + [IndexError, "Please ensure two theta array and intensity array are the same length."], ), - # UC2: user specified an invalid tth value of > 180 degrees + # UC4: user specified a two theta array that does not match the length of intensity array (with wavelength) ( - [4 * np.pi, [0, 30, 60, 90, 120, 181]], - "Two theta exceeds 180 degrees. Please check the input values for errors.", + [4 * np.pi, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], + [IndexError, "Please ensure two theta array and intensity array are the same length."], ), - # UC3: user did not specify wavelength and specified invalid tth values + # UC5: user specified a non-numeric value in two theta array (without wavelength) ( - [None, [0, 30, 60, 90, 120, 181]], - "Wavelength is not specified. Please provide a valid wavelength, " - "e.g., DiffractionObject(wavelength=0.71).", + [None, [0, 30, 60, 90, 120, "invalid"], [1, 2, 3, 4, 5, 6]], + [TypeError, "Invalid value found in two theta array. Please ensure all values are numeric."], ), - # UC4: user specified an empty two theta array - ([4 * np.pi, []], "Two theta array is empty. Please provide valid two theta values."), - # UC5: user specified a non-numeric value in two theta array + # UC6: user specified a non-numeric value in two theta array (with wavelength) ( - [4 * np.pi, [0, 30, 60, 90, 120, "invalid"]], - "Invalid value found in two theta array. Please ensure all values are numeric.", + [4 * np.pi, [0, 30, 60, 90, 120, "invalid"], [1, 2, 3, 4, 5, 6]], + [TypeError, "Invalid value found in two theta array. Please ensure all values are numeric."], ), ] @@ -317,8 +402,8 @@ def test_tth_to_q(): @pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) def test_tth_to_q_bad(inputs, expected): actual = DiffractionObject(wavelength=inputs[0]) - setattr(actual, "on_tth", [inputs[1], [1, 2, 3, 4, 5, 6]]) - with pytest.raises(ValueError, match=expected): + actual.on_tth = [inputs[1], inputs[2]] + with pytest.raises(expected[0], match=expected[1]): actual.tth_to_q() From a53461f5865818a0d0db6dfd838759436e734f25 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 25 Nov 2024 14:36:27 -0500 Subject: [PATCH 076/445] edit warning msg for wavelength, change index error to runtime error --- .../test_diffraction_objects.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 07674a5c..59d7cafb 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -241,8 +241,10 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte assert str(warn_record[0].message) == ( "INFO: no wavelength has been specified. You can continue " "to use the DiffractionObject but some of its powerful features " - "will not be available. To specify a wavelength, you can use " - "DiffractionObject(wavelength=0.71)." + "will not be available. To specify a wavelength, set " + "diffraction_object.wavelength = [number], " + "where diffraction_object is the variable name of you Diffraction Object, " + "and number is the wavelength in angstroms." ) actual_array = getattr(actual_diffraction_object, function)() return np.allclose(actual_array, expected_array) @@ -289,7 +291,7 @@ def test_q_to_tth(inputs, expected): [ ValueError, "The supplied q-array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject.", + "Please check these values and re-instantiate the DiffractionObject with correct values.", ], ), # UC2: user specified a wrong wavelength that result in tth > 180 degrees @@ -304,12 +306,12 @@ def test_q_to_tth(inputs, expected): # UC3: user specified a q array that does not match the length of intensity array (without wavelength) ( [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [IndexError, "Please ensure q array and intensity array are the same length."], + [RuntimeError, "Please ensure q array and intensity array are the same length."], ), # UC4: user specified a q array that does not match the length of intensity array (with wavelength) ( [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [IndexError, "Please ensure q array and intensity array are the same length."], + [RuntimeError, "Please ensure q array and intensity array are the same length."], ), # UC5: user specified a non-numeric value in q array (without wavelength) ( @@ -379,12 +381,12 @@ def test_tth_to_q(inputs, expected): # UC3: user specified a two theta array that does not match the length of intensity array (without wavelength) ( [None, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [IndexError, "Please ensure two theta array and intensity array are the same length."], + [RuntimeError, "Please ensure two theta array and intensity array are the same length."], ), # UC4: user specified a two theta array that does not match the length of intensity array (with wavelength) ( [4 * np.pi, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [IndexError, "Please ensure two theta array and intensity array are the same length."], + [RuntimeError, "Please ensure two theta array and intensity array are the same length."], ), # UC5: user specified a non-numeric value in two theta array (without wavelength) ( From 2a5a819e55b8c14d3fc7e327d4088fbe65aea14f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 25 Nov 2024 15:15:10 -0500 Subject: [PATCH 077/445] add functionality for handling error messages --- .../scattering_objects/diffraction_objects.py | 40 ++++++++++++-- .../test_diffraction_objects.py | 52 +++++-------------- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 0ff9f0ce..2017b926 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -17,6 +17,23 @@ "and specifying how to handle the mismatch." ) +wavelength_warning_emsg = ( + "INFO: no wavelength has been specified. You can continue " + "to use the DiffractionObject but some of its powerful features " + "will not be available. To specify a wavelength, set " + "diffraction_object.wavelength = [number], " + "where diffraction_object is the variable name of you Diffraction Object, " + "and number is the wavelength in angstroms." +) + +length_mismatch_emsg = "Please ensure {array_name} array and intensity array are of the same length." +non_numeric_value_emsg = "Invalid value found in {array_name} array. Please ensure all values are numeric." +invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." +invalid_q_or_wavelength_emsg = ( + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values." +) + class Diffraction_object: """A class to represent and manipulate data associated with diffraction experiments. @@ -778,12 +795,20 @@ def q_to_tth(self): two_theta : array The array of :math:`2\theta` values in radians """ + for i, value in enumerate(self.on_q[0]): + if not isinstance(value, (int, float)): + raise TypeError(non_numeric_value_emsg.format(array_name="q")) + if len(self.on_q[0]) != len(self.on_q[1]): + raise RuntimeError(length_mismatch_emsg.format(array_name="q")) + if self.wavelength is None: + warnings.warn(wavelength_warning_emsg, UserWarning) + return np.empty(0) q = self.on_q[0] q = np.asarray(q) wavelength = float(self.wavelength) pre_factor = wavelength / (4 * np.pi) if np.any(np.abs(q * pre_factor) > 1): - raise ValueError("Please check if you entered an incorrect wavelength or q value.") + raise ValueError(invalid_q_or_wavelength_emsg) return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) def tth_to_q(self): @@ -818,12 +843,17 @@ def tth_to_q(self): The array of :math:`q` values in the inverse of the units of ``wavelength`` """ + for i, value in enumerate(self.on_tth[0]): + if not isinstance(value, (int, float)): + raise TypeError(non_numeric_value_emsg.format(array_name="two theta")) + if len(self.on_tth[0]) != len(self.on_tth[1]): + raise RuntimeError(length_mismatch_emsg.format(array_name="two theta")) two_theta = np.asarray(np.deg2rad(self.on_tth[0])) if np.any(two_theta > np.pi): - raise ValueError( - "Two theta exceeds 180 degrees. Please check if invalid values were entered " - "or if degrees were incorrectly specified as radians." - ) + raise ValueError(invalid_tth_emsg) + if self.wavelength is None: + warnings.warn(wavelength_warning_emsg, UserWarning) + return np.empty(0) wavelength = float(self.wavelength) pre_factor = (4 * np.pi) / wavelength return pre_factor * np.sin(two_theta / 2) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 59d7cafb..7fd1ef96 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -4,7 +4,7 @@ import pytest from freezegun import freeze_time -from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject +from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject, wavelength_warning_emsg params = [ ( # Default @@ -238,35 +238,18 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte if actual_diffraction_object.wavelength is None: with pytest.warns(UserWarning) as warn_record: getattr(actual_diffraction_object, function)() - assert str(warn_record[0].message) == ( - "INFO: no wavelength has been specified. You can continue " - "to use the DiffractionObject but some of its powerful features " - "will not be available. To specify a wavelength, set " - "diffraction_object.wavelength = [number], " - "where diffraction_object is the variable name of you Diffraction Object, " - "and number is the wavelength in angstroms." - ) + assert str(warn_record[0].message) == wavelength_warning_emsg actual_array = getattr(actual_diffraction_object, function)() return np.allclose(actual_array, expected_array) params_q_to_tth = [ # UC1: User specified empty q values (without wavelength) - ( - [None, [], []], - [[]], - ), + ([None, [], []], [[]]), # UC2: User specified empty q values (with wavelength) - ( - [4 * np.pi, [], []], - [[]], - ), + ([4 * np.pi, [], []], [[]]), # UC3: User specified valid q values (without wavelength) - # expected tth values are 2*arcsin(q) in degrees - ( - [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], - [[]], - ), + ([None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], [[]]), # UC4: User specified valid q values (with wavelength) # expected tth values are 2*arcsin(q) in degrees ( @@ -300,18 +283,18 @@ def test_q_to_tth(inputs, expected): [ ValueError, "The supplied q-array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject.", + "Please check these values and re-instantiate the DiffractionObject with correct values.", ], ), # UC3: user specified a q array that does not match the length of intensity array (without wavelength) ( [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [RuntimeError, "Please ensure q array and intensity array are the same length."], + [RuntimeError, "Please ensure q array and intensity array are of the same length."], ), # UC4: user specified a q array that does not match the length of intensity array (with wavelength) ( [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [RuntimeError, "Please ensure q array and intensity array are the same length."], + [RuntimeError, "Please ensure q array and intensity array are of the same length."], ), # UC5: user specified a non-numeric value in q array (without wavelength) ( @@ -336,15 +319,9 @@ def test_q_to_tth_bad(inputs, expected): params_tth_to_q = [ # UC1: User specified empty tth values (without wavelength) - ( - [None, [], []], - [[]], - ), + ([None, [], []], [[]]), # UC2: User specified empty tth values (with wavelength) - ( - [4 * np.pi, [], []], - [[]], - ), + ([4 * np.pi, [], []], [[]]), # UC3: User specified valid tth values between 0-180 degrees (without wavelength) ( [None, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], @@ -352,10 +329,7 @@ def test_q_to_tth_bad(inputs, expected): ), # UC4: User specified valid tth values between 0-180 degrees (with wavelength) # expected q vales are sin15, sin30, sin45, sin60, sin90 - ( - [4 * np.pi, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], - [[0, 0.258819, 0.5, 0.707107, 0.866025, 1]], - ), + ([4 * np.pi, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], [[0, 0.258819, 0.5, 0.707107, 0.866025, 1]]), ] @@ -381,12 +355,12 @@ def test_tth_to_q(inputs, expected): # UC3: user specified a two theta array that does not match the length of intensity array (without wavelength) ( [None, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [RuntimeError, "Please ensure two theta array and intensity array are the same length."], + [RuntimeError, "Please ensure two theta array and intensity array are of the same length."], ), # UC4: user specified a two theta array that does not match the length of intensity array (with wavelength) ( [4 * np.pi, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [RuntimeError, "Please ensure two theta array and intensity array are the same length."], + [RuntimeError, "Please ensure two theta array and intensity array are of the same length."], ), # UC5: user specified a non-numeric value in two theta array (without wavelength) ( From 64d8693747357a889d922f1c5451717ff2967148 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 25 Nov 2024 20:38:48 -0500 Subject: [PATCH 078/445] remove docstring from private test functions --- .../utils/scattering_objects/test_diffraction_objects.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 7fd1ef96..3bdc22d4 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -232,9 +232,6 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def _test_valid_diffraction_objects(actual_diffraction_object, function, expected_array): - """Checks the behavior of the DiffractionObject: - when there is no wavelength, we expect the correct warning message and output, - otherwise, we only check the output matches the expected array.""" if actual_diffraction_object.wavelength is None: with pytest.warns(UserWarning) as warn_record: getattr(actual_diffraction_object, function)() From 728ff36ba12dbda4e12415d695144c33ce5ac587 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 26 Nov 2024 08:20:02 -0500 Subject: [PATCH 079/445] add news --- news/tth-q.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/tth-q.rst diff --git a/news/tth-q.rst b/news/tth-q.rst new file mode 100644 index 00000000..c512bf9b --- /dev/null +++ b/news/tth-q.rst @@ -0,0 +1,23 @@ +**Added:** + +* functionality to raise useful warning and error messages during angular conversion between two theta and q + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 490486c25d692cd3de07ab9f44ae3ac7b3caad56 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 26 Nov 2024 13:21:42 -0500 Subject: [PATCH 080/445] add example and utility --- .../examples/diffractionobjectsexample.rst | 41 +++++++++++++++++++ .../utilities/diffractionobjectsutility.rst | 16 ++++++++ 2 files changed, 57 insertions(+) create mode 100644 doc/source/examples/diffractionobjectsexample.rst create mode 100644 doc/source/utilities/diffractionobjectsutility.rst diff --git a/doc/source/examples/diffractionobjectsexample.rst b/doc/source/examples/diffractionobjectsexample.rst new file mode 100644 index 00000000..f9def3f4 --- /dev/null +++ b/doc/source/examples/diffractionobjectsexample.rst @@ -0,0 +1,41 @@ +.. _Diffraction Objects Example: + +:tocdepth: -1 + +Diffraction Objects Example +########################### + +This example will demonstrate how to use the ``DiffractionObject`` class in the +``diffpy.utils.scattering_objects.diffraction_objects`` module to process and analyze diffraction data. + +1) We have the function ``q_to_tth`` to convert q to two theta values in degrees, and ``tth_to_q`` to do the reverse. + You can use these functions with a pre-defined ``DiffractionObject``. :: + + # convert q to tth + from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject + test = DiffractionObject(wavelength=1.54) + test.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] + test.q_to_tth() + + This function will convert your provided q array and return a two theta array in degrees. + To load the converted array, you can either call ``test.q_to_tth()`` or ``test.on_q[0]``. + + Similarly, use the function ``tth_to_q`` to convert two theta values in degrees to q values. :: + + # convert tth to q + from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject + test = DiffractionObject(wavelength=1.54) + test.on_tth = [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]] + test.tth_to_q() + + To load the converted array, you can either call ``test.tth_to_q()`` or ``test.on_tth[0]``. + +2) You can use these functions without specifying a wavelength. However, if so, the function will return an empty array, + so we strongly encourage you to specify a wavelength when using these functions. :: + + from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject + test = DiffractionObject() + test.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] + test.q_to_tth() + + In this case, the function will return an empty array on two theta. diff --git a/doc/source/utilities/diffractionobjectsutility.rst b/doc/source/utilities/diffractionobjectsutility.rst new file mode 100644 index 00000000..da9ddac6 --- /dev/null +++ b/doc/source/utilities/diffractionobjectsutility.rst @@ -0,0 +1,16 @@ +.. _Diffraction Objects Utility: + +Diffraction Objects Utility +=========================== + +The ``diffpy.utils.scattering_objects.diffraction_objects`` module provides functions +for managing and analyzing diffraction data, including angle-space conversions +and interactions between diffraction data. + +- ``q_to_tth()``: Converts an array of q values to their corresponding two theta values, based on specified wavelength. +- ``tth_to_q()``: Converts an array of two theta values to their corresponding q values, based on specified wavelength. + + These functions help developers standardize diffraction data and update the arrays + in the associated ``DiffractionObject``, enabling easier analysis and further processing. + +For a more in-depth tutorial for how to use these functions, click :ref:`here `. From d481253f09ea0ca698f2175f6fb6d9ac4f3e8714 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 26 Nov 2024 18:39:06 -0500 Subject: [PATCH 081/445] edits on docs --- .../examples/diffractionobjectsexample.rst | 39 ++++++++----------- .../utilities/diffractionobjectsutility.rst | 7 +--- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/doc/source/examples/diffractionobjectsexample.rst b/doc/source/examples/diffractionobjectsexample.rst index f9def3f4..f3114ab0 100644 --- a/doc/source/examples/diffractionobjectsexample.rst +++ b/doc/source/examples/diffractionobjectsexample.rst @@ -8,34 +8,27 @@ Diffraction Objects Example This example will demonstrate how to use the ``DiffractionObject`` class in the ``diffpy.utils.scattering_objects.diffraction_objects`` module to process and analyze diffraction data. -1) We have the function ``q_to_tth`` to convert q to two theta values in degrees, and ``tth_to_q`` to do the reverse. - You can use these functions with a pre-defined ``DiffractionObject``. :: +1) Assuming we have created a ``DiffractionObject`` called my_diffraction_pattern from a measured diffraction pattern, + and we have specified the wavelength (see Section ??, to be added), + we can use the ``q_to_tth`` and ``tth_to_q`` functions to convert between q and two-theta. :: - # convert q to tth - from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject - test = DiffractionObject(wavelength=1.54) - test.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] - test.q_to_tth() + # Example: convert q to tth + my_diffraction_pattern.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] + my_diffraction_pattern.q_to_tth() This function will convert your provided q array and return a two theta array in degrees. - To load the converted array, you can either call ``test.q_to_tth()`` or ``test.on_q[0]``. - - Similarly, use the function ``tth_to_q`` to convert two theta values in degrees to q values. :: + To load the converted array, you can either call ``test.q_to_tth()`` or ``test.on_q[0]``. :: - # convert tth to q + # Example: convert tth to q from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject - test = DiffractionObject(wavelength=1.54) - test.on_tth = [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]] - test.tth_to_q() + my_diffraction_pattern.on_tth = [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]] + my_diffraction_pattern.tth_to_q() - To load the converted array, you can either call ``test.tth_to_q()`` or ``test.on_tth[0]``. + Similarly, to load the converted array, you can either call ``test.tth_to_q()`` or ``test.on_tth[0]``. -2) You can use these functions without specifying a wavelength. However, if so, the function will return an empty array, - so we strongly encourage you to specify a wavelength when using these functions. :: - - from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject - test = DiffractionObject() - test.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] - test.q_to_tth() +2) Both functions require a wavelength to perform conversions. Without a wavelength, they will return empty arrays. + Therefore, we strongly encourage you to specify a wavelength when using these functions. :: - In this case, the function will return an empty array on two theta. + # Example: without wavelength specified + my_diffraction_pattern.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] + my_diffraction_pattern.q_to_tth() # returns an empty array diff --git a/doc/source/utilities/diffractionobjectsutility.rst b/doc/source/utilities/diffractionobjectsutility.rst index da9ddac6..d388e856 100644 --- a/doc/source/utilities/diffractionobjectsutility.rst +++ b/doc/source/utilities/diffractionobjectsutility.rst @@ -7,10 +7,7 @@ The ``diffpy.utils.scattering_objects.diffraction_objects`` module provides func for managing and analyzing diffraction data, including angle-space conversions and interactions between diffraction data. -- ``q_to_tth()``: Converts an array of q values to their corresponding two theta values, based on specified wavelength. -- ``tth_to_q()``: Converts an array of two theta values to their corresponding q values, based on specified wavelength. - - These functions help developers standardize diffraction data and update the arrays - in the associated ``DiffractionObject``, enabling easier analysis and further processing. +These functions help developers standardize diffraction data and update the arrays +in the associated ``DiffractionObject``, enabling easier analysis and further processing. For a more in-depth tutorial for how to use these functions, click :ref:`here `. From 114cb65046dea151d9e49394250f79a28d12b2a7 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 1 Dec 2024 08:44:59 -0500 Subject: [PATCH 082/445] refactor the transforms out of DiffractionObjects to make them more wiedely available in general --- news/private_f.rst | 24 ++ .../scattering_objects/diffraction_objects.py | 230 ++---------------- src/diffpy/utils/transforms.py | 122 ++++++++++ .../test_diffraction_objects.py | 151 +----------- tests/test_transforms.py | 99 ++++++++ 5 files changed, 271 insertions(+), 355 deletions(-) create mode 100644 news/private_f.rst create mode 100644 src/diffpy/utils/transforms.py create mode 100644 tests/test_transforms.py diff --git a/news/private_f.rst b/news/private_f.rst new file mode 100644 index 00000000..b67df7b7 --- /dev/null +++ b/news/private_f.rst @@ -0,0 +1,24 @@ +**Added:** + +* + +**Changed:** + +* refactor `q_to_tth()` and `tth_to_q()` into `diffpy.utils.transforms` to make them available outside + DiffractionObject + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 2017b926..4351c537 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -5,6 +5,7 @@ import numpy as np from diffpy.utils.tools import get_package_info +from diffpy.utils.transforms import q_to_tth, tth_to_q QQUANTITIES = ["q"] ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] @@ -17,23 +18,6 @@ "and specifying how to handle the mismatch." ) -wavelength_warning_emsg = ( - "INFO: no wavelength has been specified. You can continue " - "to use the DiffractionObject but some of its powerful features " - "will not be available. To specify a wavelength, set " - "diffraction_object.wavelength = [number], " - "where diffraction_object is the variable name of you Diffraction Object, " - "and number is the wavelength in angstroms." -) - -length_mismatch_emsg = "Please ensure {array_name} array and intensity array are of the same length." -non_numeric_value_emsg = "Invalid value found in {array_name} array. Please ensure all values are numeric." -invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." -invalid_q_or_wavelength_emsg = ( - "The supplied q-array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values." -) - class Diffraction_object: """A class to represent and manipulate data associated with diffraction experiments. @@ -54,9 +38,9 @@ def __init__(self, name="", wavelength=None): self.name = name self.wavelength = wavelength self.scat_quantity = "" - self.on_q = [np.empty(0), np.empty(0)] - self.on_tth = [np.empty(0), np.empty(0)] - self.on_d = [np.empty(0), np.empty(0)] + self.on_q = np.array([np.empty(0), np.empty(0)]) + self.on_tth = np.array([np.empty(0), np.empty(0)]) + self.on_d = np.array([np.empty(0), np.empty(0)]) self._all_arrays = [self.on_q, self.on_tth] self.metadata = {} @@ -311,97 +295,19 @@ def insert_scattering_quantity( if wavelength is not None: self.wavelength = wavelength if xtype.lower() in QQUANTITIES: - self.on_q = [np.array(xarray), np.array(yarray)] + self.on_q = np.array([xarray, yarray]) elif xtype.lower() in ANGLEQUANTITIES: - self.on_tth = [np.array(xarray), np.array(yarray)] + self.on_tth = np.array([xarray, yarray]) elif xtype.lower() in DQUANTITIES: - self.on_tth = [np.array(xarray), np.array(yarray)] + self.on_tth = np.array([xarray, yarray]) self.set_all_arrays() - def q_to_tth(self): - r""" - Helper function to convert q to two-theta. - - By definition the relationship is: - - .. math:: - - \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} - - thus - - .. math:: - - 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) - - Parameters - ---------- - q : array - An array of :math:`q` values - - wavelength : float - Wavelength of the incoming x-rays - - Function adapted from scikit-beam. Thanks to those developers - - Returns - ------- - two_theta : array - An array of :math:`2\theta` values in radians - """ - q = self.on_q[0] - q = np.asarray(q) - wavelength = float(self.wavelength) - pre_factor = wavelength / (4 * np.pi) - return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) - - def tth_to_q(self): - r""" - Helper function to convert two-theta to q - - By definition the relationship is - - .. math:: - - \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} - - thus - - .. math:: - - q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} - - - - Parameters - ---------- - two_theta : array - An array of :math:`2\theta` values in units of degrees - - wavelength : float - Wavelength of the incoming x-rays - - Function adapted from scikit-beam. Thanks to those developers. - - Returns - ------- - q : array - An array of :math:`q` values in the inverse of the units - of ``wavelength`` - """ - two_theta = np.asarray(np.deg2rad(self.on_tth[0])) - wavelength = float(self.wavelength) - pre_factor = (4 * np.pi) / wavelength - return pre_factor * np.sin(two_theta / 2) - def set_all_arrays(self): master_array, xtype = self._get_original_array() if xtype == "q": - self.on_tth[0] = self.q_to_tth() - self.on_tth[1] = master_array[1] - if xtype == "tth": - self.on_q[0] = self.tth_to_q() - self.on_q[1] = master_array[1] + self.on_tth = q_to_tth(self.on_q, self.wavelength) + elif xtype == "tth": + self.on_q = tth_to_q(self.on_tth, self.wavelength) self.tthmin = self.on_tth[0][0] self.tthmax = self.on_tth[0][-1] self.qmin = self.on_q[0][0] @@ -500,9 +406,9 @@ def __init__(self, name="", wavelength=None): self.name = name self.wavelength = wavelength self.scat_quantity = "" - self.on_q = [np.empty(0), np.empty(0)] - self.on_tth = [np.empty(0), np.empty(0)] - self.on_d = [np.empty(0), np.empty(0)] + self.on_q = np.empty((2, 0), dtype=np.float64) + self.on_tth = np.empty((2, 0), dtype=np.float64) + self.on_d = np.empty((2, 0), dtype=np.float64) self._all_arrays = [self.on_q, self.on_tth] self.metadata = {} @@ -757,115 +663,19 @@ def insert_scattering_quantity( if wavelength is not None: self.wavelength = wavelength if xtype.lower() in QQUANTITIES: - self.on_q = [np.array(xarray), np.array(yarray)] + self.on_q = np.array([xarray, yarray]) elif xtype.lower() in ANGLEQUANTITIES: - self.on_tth = [np.array(xarray), np.array(yarray)] - elif xtype.lower() in DQUANTITIES: - self.on_tth = [np.array(xarray), np.array(yarray)] + self.on_tth = np.array([xarray, yarray]) + elif xtype.lower() in DQUANTITIES: # Fixme when d is implemented. This here as a placeholder + self.on_tth = np.array([xarray, yarray]) self.set_all_arrays() - def q_to_tth(self): - r""" - Helper function to convert q to two-theta. - - By definition the relationship is: - - .. math:: - - \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} - - thus - - .. math:: - - 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) - - Function adapted from scikit-beam. Thanks to those developers - - Parameters - ---------- - q : array - The array of :math:`q` values - - wavelength : float - Wavelength of the incoming x-rays - - Returns - ------- - two_theta : array - The array of :math:`2\theta` values in radians - """ - for i, value in enumerate(self.on_q[0]): - if not isinstance(value, (int, float)): - raise TypeError(non_numeric_value_emsg.format(array_name="q")) - if len(self.on_q[0]) != len(self.on_q[1]): - raise RuntimeError(length_mismatch_emsg.format(array_name="q")) - if self.wavelength is None: - warnings.warn(wavelength_warning_emsg, UserWarning) - return np.empty(0) - q = self.on_q[0] - q = np.asarray(q) - wavelength = float(self.wavelength) - pre_factor = wavelength / (4 * np.pi) - if np.any(np.abs(q * pre_factor) > 1): - raise ValueError(invalid_q_or_wavelength_emsg) - return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) - - def tth_to_q(self): - r""" - Helper function to convert two-theta to q - - By definition the relationship is - - .. math:: - - \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} - - thus - - .. math:: - - q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} - - Function adapted from scikit-beam. Thanks to those developers. - - Parameters - ---------- - two_theta : array - The array of :math:`2\theta` values in units of degrees - - wavelength : float - Wavelength of the incoming x-rays - - Returns - ------- - q : array - The array of :math:`q` values in the inverse of the units - of ``wavelength`` - """ - for i, value in enumerate(self.on_tth[0]): - if not isinstance(value, (int, float)): - raise TypeError(non_numeric_value_emsg.format(array_name="two theta")) - if len(self.on_tth[0]) != len(self.on_tth[1]): - raise RuntimeError(length_mismatch_emsg.format(array_name="two theta")) - two_theta = np.asarray(np.deg2rad(self.on_tth[0])) - if np.any(two_theta > np.pi): - raise ValueError(invalid_tth_emsg) - if self.wavelength is None: - warnings.warn(wavelength_warning_emsg, UserWarning) - return np.empty(0) - wavelength = float(self.wavelength) - pre_factor = (4 * np.pi) / wavelength - return pre_factor * np.sin(two_theta / 2) - def set_all_arrays(self): master_array, xtype = self._get_original_array() if xtype == "q": - self.on_tth[0] = self.q_to_tth() - self.on_tth[1] = master_array[1] - if xtype == "tth": - self.on_q[0] = self.tth_to_q() - self.on_q[1] = master_array[1] + self.on_tth = q_to_tth(self.on_q, self.wavelength) + elif xtype == "tth": + self.on_q = tth_to_q(self.on_tth, self.wavelength) self.tthmin = self.on_tth[0][0] self.tthmax = self.on_tth[0][-1] self.qmin = self.on_q[0][0] diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py new file mode 100644 index 00000000..08be3786 --- /dev/null +++ b/src/diffpy/utils/transforms.py @@ -0,0 +1,122 @@ +import warnings +from copy import copy + +import numpy as np + +wavelength_warning_emsg = ( + "INFO: no wavelength has been specified. You can continue " + "to use the DiffractionObject but some of its powerful features " + "will not be available. To specify a wavelength, set " + "diffraction_object.wavelength = [number], " + "where diffraction_object is the variable name of you Diffraction Object, " + "and number is the wavelength in angstroms." +) +invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." +invalid_q_or_wavelength_emsg = ( + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values." +) + + +def _validate_inputs(on_q, wavelength): + if wavelength is None: + warnings.warn(wavelength_warning_emsg, UserWarning) + return np.empty(0) + pre_factor = wavelength / (4 * np.pi) + if np.any(np.abs(on_q[0] * pre_factor) > 1.0): + raise ValueError(invalid_q_or_wavelength_emsg) + + +def q_to_tth(on_q, wavelength): + r""" + Helper function to convert q to two-theta. + + If wavelength is missing, returns x-values that are integer indexes + + By definition the relationship is: + + .. math:: + + \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} + + thus + + .. math:: + + 2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) + + Parameters + ---------- + on_q : 2D array + The array of :math:`q` values and :math: 'i' intensity values, np.array([[qs], [is]]). + This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_q + The units of q must be reciprocal of the units of wavelength. + + wavelength : float + Wavelength of the incoming x-rays/neutrons/electrons + + Returns + ------- + on_tth : 2D array + The array of :math:`2\theta` values in degrees and :math: 'i' intensity values unchanged, + np.array([[tths], [is]]). + This is the correct format for loading into diffpy.utils.DiffractionOject.on_tth + """ + _validate_inputs(on_q, wavelength) + on_q.astype(np.float64) + on_tth = copy(on_q) # initialize output array of same shape + if wavelength is not None: + on_tth[0] = np.rad2deg(2.0 * np.arcsin(on_q[0] * wavelength / (4 * np.pi))) + else: # return intensities vs. an x-array that is just the index + for i, _ in enumerate(on_q[0]): + on_tth[0][i] = i + return on_tth + + +def tth_to_q(on_tth, wavelength): + r""" + Helper function to convert two-theta to q on independent variable axis. + + If wavelength is missing, returns independent variable axis as integer indexes. + + By definition the relationship is: + + .. math:: + + \sin\left(\frac{2\theta}{2}\right) = \frac{\lambda q}{4 \pi} + + thus + + .. math:: + + q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} + + Parameters + ---------- + on_tth : 2D array + The array of :math:`2\theta` values and :math: 'i' intensity values, np.array([[tths], [is]]). + This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_tth + The units of tth are expected in degrees. + + wavelength : float + Wavelength of the incoming x-rays/neutrons/electrons + + Returns + ------- + on_q : 2D array + The array of :math:`q` values and :math: 'i' intensity values unchanged, + np.array([[qs], [is]]). + The units for the q-values are the inverse of the units of the provided wavelength. + This is the correct format for loading into diffpy.utils.DiffractionOject.on_q + """ + on_tth.astype(np.float64) + if np.any(np.deg2rad(on_tth[0]) > np.pi): + raise ValueError(invalid_tth_emsg) + on_q = copy(on_tth) + if wavelength is not None: + pre_factor = (4.0 * np.pi) / wavelength + on_q[0] = pre_factor * np.sin(np.deg2rad(on_tth[0] / 2)) + else: # return intensities vs. an x-array that is just the index + for i, _ in enumerate(on_q[0]): + on_q[0][i] = i + return on_q diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index 3bdc22d4..ada14ff1 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -4,7 +4,8 @@ import pytest from freezegun import freeze_time -from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject, wavelength_warning_emsg +from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject +from diffpy.utils.transforms import wavelength_warning_emsg params = [ ( # Default @@ -240,146 +241,6 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte return np.allclose(actual_array, expected_array) -params_q_to_tth = [ - # UC1: User specified empty q values (without wavelength) - ([None, [], []], [[]]), - # UC2: User specified empty q values (with wavelength) - ([4 * np.pi, [], []], [[]]), - # UC3: User specified valid q values (without wavelength) - ([None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], [[]]), - # UC4: User specified valid q values (with wavelength) - # expected tth values are 2*arcsin(q) in degrees - ( - [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], - [[0, 23.07392, 47.15636, 73.73980, 106.26020, 180]], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_q_to_tth) -def test_q_to_tth(inputs, expected): - actual = DiffractionObject(wavelength=inputs[0]) - actual.on_q = [inputs[1], inputs[2]] - expected_tth = expected[0] - assert _test_valid_diffraction_objects(actual, "q_to_tth", expected_tth) - - -params_q_to_tth_bad = [ - # UC1: user specified invalid q values that result in tth > 180 degrees - ( - [4 * np.pi, [0.2, 0.4, 0.6, 0.8, 1, 1.2], [1, 2, 3, 4, 5, 6]], - [ - ValueError, - "The supplied q-array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], - ), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees - ( - [100, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]], - [ - ValueError, - "The supplied q-array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], - ), - # UC3: user specified a q array that does not match the length of intensity array (without wavelength) - ( - [None, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [RuntimeError, "Please ensure q array and intensity array are of the same length."], - ), - # UC4: user specified a q array that does not match the length of intensity array (with wavelength) - ( - [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5]], - [RuntimeError, "Please ensure q array and intensity array are of the same length."], - ), - # UC5: user specified a non-numeric value in q array (without wavelength) - ( - [None, [0, 0.2, 0.4, 0.6, 0.8, "invalid"], [1, 2, 3, 4, 5, 6]], - [TypeError, "Invalid value found in q array. Please ensure all values are numeric."], - ), - # UC5: user specified a non-numeric value in q array (with wavelength) - ( - [4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, "invalid"], [1, 2, 3, 4, 5, 6]], - [TypeError, "Invalid value found in q array. Please ensure all values are numeric."], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) -def test_q_to_tth_bad(inputs, expected): - actual = DiffractionObject(wavelength=inputs[0]) - actual.on_q = [inputs[1], inputs[2]] - with pytest.raises(expected[0], match=expected[1]): - actual.q_to_tth() - - -params_tth_to_q = [ - # UC1: User specified empty tth values (without wavelength) - ([None, [], []], [[]]), - # UC2: User specified empty tth values (with wavelength) - ([4 * np.pi, [], []], [[]]), - # UC3: User specified valid tth values between 0-180 degrees (without wavelength) - ( - [None, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], - [[]], - ), - # UC4: User specified valid tth values between 0-180 degrees (with wavelength) - # expected q vales are sin15, sin30, sin45, sin60, sin90 - ([4 * np.pi, [0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]], [[0, 0.258819, 0.5, 0.707107, 0.866025, 1]]), -] - - -@pytest.mark.parametrize("inputs, expected", params_tth_to_q) -def test_tth_to_q(inputs, expected): - actual = DiffractionObject(wavelength=inputs[0]) - actual.on_tth = [inputs[1], inputs[2]] - expected_q = expected[0] - assert _test_valid_diffraction_objects(actual, "tth_to_q", expected_q) - - -params_tth_to_q_bad = [ - # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) - ( - [None, [0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], - ), - # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) - ( - [4 * np.pi, [0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], - ), - # UC3: user specified a two theta array that does not match the length of intensity array (without wavelength) - ( - [None, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [RuntimeError, "Please ensure two theta array and intensity array are of the same length."], - ), - # UC4: user specified a two theta array that does not match the length of intensity array (with wavelength) - ( - [4 * np.pi, [0, 30, 60, 90, 120], [1, 2, 3, 4, 5, 6]], - [RuntimeError, "Please ensure two theta array and intensity array are of the same length."], - ), - # UC5: user specified a non-numeric value in two theta array (without wavelength) - ( - [None, [0, 30, 60, 90, 120, "invalid"], [1, 2, 3, 4, 5, 6]], - [TypeError, "Invalid value found in two theta array. Please ensure all values are numeric."], - ), - # UC6: user specified a non-numeric value in two theta array (with wavelength) - ( - [4 * np.pi, [0, 30, 60, 90, 120, "invalid"], [1, 2, 3, 4, 5, 6]], - [TypeError, "Invalid value found in two theta array. Please ensure all values are numeric."], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) -def test_tth_to_q_bad(inputs, expected): - actual = DiffractionObject(wavelength=inputs[0]) - actual.on_tth = [inputs[1], inputs[2]] - with pytest.raises(expected[0], match=expected[1]): - actual.tth_to_q() - - def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) @@ -389,14 +250,14 @@ def test_dump(tmp_path, mocker): test.name = "test" test.scat_quantity = "x-ray" test.insert_scattering_quantity( - x, y, "q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}} + np.array(x), + np.array(y), + "q", + metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, ) - mocker.patch("importlib.metadata.version", return_value="3.3.0") - with freeze_time("2012-01-14"): test.dump(file, "q") - with open(file, "r") as f: actual = f.read() expected = ( diff --git a/tests/test_transforms.py b/tests/test_transforms.py new file mode 100644 index 00000000..d5c5ccfa --- /dev/null +++ b/tests/test_transforms.py @@ -0,0 +1,99 @@ +import numpy as np +import pytest + +from diffpy.utils.transforms import q_to_tth, tth_to_q + +params_q_to_tth = [ + # UC1: Empty q values, no wavelength, return empty arrays + ([None, np.empty((2, 0))], np.empty((2, 0))), + # UC2: Empty q values, wavelength specified, return empty arrays + ([4 * np.pi, np.empty((2, 0))], np.empty((2, 0))), + # UC3: User specified valid q values, no wavelength, return empty arrays + ( + [None, np.array([[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]])], + np.array([[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]), + ), + # UC4: User specified valid q values (with wavelength) + # expected tth values are 2*arcsin(q) in degrees + ([4 * np.pi, np.array([[0, 1 / np.sqrt(2), 1.0], [1, 2, 3]])], np.array([[0, 90.0, 180.0], [1, 2, 3]])), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_tth) +def test_q_to_tth(inputs, expected): + actual = q_to_tth(inputs[1], inputs[0]) + assert np.allclose(expected[0], actual[0]) + assert np.allclose(expected[1], actual[1]) + + +params_q_to_tth_bad = [ + # UC1: user specified invalid q values that result in tth > 180 degrees + ( + [4 * np.pi, np.array([[0.2, 0.4, 0.6, 0.8, 1, 1.2], [1, 2, 3, 4, 5, 6]])], + [ + ValueError, + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", + ], + ), + # UC2: user specified a wrong wavelength that result in tth > 180 degrees + ( + [100, np.array([[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]])], + [ + ValueError, + "The supplied q-array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", + ], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) +def test_q_to_tth_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + q_to_tth(inputs[1], inputs[0]) + + +params_tth_to_q = [ + # UC0: User specified empty tth values (without wavelength) + ([None, np.array([[], []])], np.array([[], []])), + # UC1: User specified empty tth values (with wavelength) + ([4 * np.pi, np.array([[], []])], np.array([[], []])), + # UC2: User specified valid tth values between 0-180 degrees (without wavelength) + ( + [None, np.array([[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]])], + np.array([[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]), + ), + # UC3: User specified valid tth values between 0-180 degrees (with wavelength) + # expected q vales are sin15, sin30, sin45, sin60, sin90 + ( + [4 * np.pi, np.array([[0, 30.0, 60.0, 90.0, 120.0, 180.0], [1, 2, 3, 4, 5, 6]])], + np.array([[0, 0.258819, 0.5, 0.707107, 0.866025, 1], [1, 2, 3, 4, 5, 6]]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_q) +def test_tth_to_q(inputs, expected): + actual = tth_to_q(inputs[1], inputs[0]) + assert np.allclose(actual, expected) + + +params_tth_to_q_bad = [ + # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) + ( + [None, np.array([[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]])], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), + # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) + ( + [4 * np.pi, np.array([[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]])], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) +def test_tth_to_q_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + tth_to_q(inputs[1], inputs[0]) From e55e44d44929620747eeef1941b6702482c4ad07 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 2 Dec 2024 14:25:18 -0500 Subject: [PATCH 083/445] public functions now operate on 1D x-arrays only for ease of user use --- src/diffpy/utils/transforms.py | 49 +++++++++++++++++----------------- tests/test_transforms.py | 33 +++++++++++------------ 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 08be3786..773aa607 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -18,16 +18,16 @@ ) -def _validate_inputs(on_q, wavelength): +def _validate_inputs(q, wavelength): if wavelength is None: warnings.warn(wavelength_warning_emsg, UserWarning) return np.empty(0) pre_factor = wavelength / (4 * np.pi) - if np.any(np.abs(on_q[0] * pre_factor) > 1.0): + if np.any(np.abs(q * pre_factor) > 1.0): raise ValueError(invalid_q_or_wavelength_emsg) -def q_to_tth(on_q, wavelength): +def q_to_tth(q, wavelength): r""" Helper function to convert q to two-theta. @@ -47,9 +47,8 @@ def q_to_tth(on_q, wavelength): Parameters ---------- - on_q : 2D array - The array of :math:`q` values and :math: 'i' intensity values, np.array([[qs], [is]]). - This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_q + q : 1D array + The array of :math:`q` values numpy.array([qs]). The units of q must be reciprocal of the units of wavelength. wavelength : float @@ -57,24 +56,24 @@ def q_to_tth(on_q, wavelength): Returns ------- - on_tth : 2D array - The array of :math:`2\theta` values in degrees and :math: 'i' intensity values unchanged, - np.array([[tths], [is]]). + tth : 1D array + The array of :math:`2\theta` values in degrees numpy.array([tths]). This is the correct format for loading into diffpy.utils.DiffractionOject.on_tth """ - _validate_inputs(on_q, wavelength) - on_q.astype(np.float64) - on_tth = copy(on_q) # initialize output array of same shape + _validate_inputs(q, wavelength) + q.astype(np.float64) + tth = copy(q) # initialize output array of same shape if wavelength is not None: - on_tth[0] = np.rad2deg(2.0 * np.arcsin(on_q[0] * wavelength / (4 * np.pi))) + tth = np.rad2deg(2.0 * np.arcsin(q * wavelength / (4 * np.pi))) else: # return intensities vs. an x-array that is just the index - for i, _ in enumerate(on_q[0]): - on_tth[0][i] = i - return on_tth + for i, _ in enumerate(q): + tth[i] = i + return tth -def tth_to_q(on_tth, wavelength): +def tth_to_q(tth, wavelength): r""" + Helper function to convert two-theta to q on independent variable axis. If wavelength is missing, returns independent variable axis as integer indexes. @@ -93,7 +92,7 @@ def tth_to_q(on_tth, wavelength): Parameters ---------- - on_tth : 2D array + tth : 2D array The array of :math:`2\theta` values and :math: 'i' intensity values, np.array([[tths], [is]]). This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_tth The units of tth are expected in degrees. @@ -109,14 +108,14 @@ def tth_to_q(on_tth, wavelength): The units for the q-values are the inverse of the units of the provided wavelength. This is the correct format for loading into diffpy.utils.DiffractionOject.on_q """ - on_tth.astype(np.float64) - if np.any(np.deg2rad(on_tth[0]) > np.pi): + tth.astype(np.float64) + if np.any(np.deg2rad(tth) > np.pi): raise ValueError(invalid_tth_emsg) - on_q = copy(on_tth) + q = copy(tth) if wavelength is not None: pre_factor = (4.0 * np.pi) / wavelength - on_q[0] = pre_factor * np.sin(np.deg2rad(on_tth[0] / 2)) + q = pre_factor * np.sin(np.deg2rad(tth / 2)) else: # return intensities vs. an x-array that is just the index - for i, _ in enumerate(on_q[0]): - on_q[0][i] = i - return on_q + for i, _ in enumerate(q): + q[i] = i + return q diff --git a/tests/test_transforms.py b/tests/test_transforms.py index d5c5ccfa..7e3eae3f 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,31 +5,30 @@ params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays - ([None, np.empty((2, 0))], np.empty((2, 0))), + ([None, np.empty((1,))], np.empty((1,))), # UC2: Empty q values, wavelength specified, return empty arrays - ([4 * np.pi, np.empty((2, 0))], np.empty((2, 0))), + ([4 * np.pi, np.empty((1,))], np.empty((1,))), # UC3: User specified valid q values, no wavelength, return empty arrays ( - [None, np.array([[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]])], - np.array([[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]), + [None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + np.array([0, 1, 2, 3, 4, 5]), ), # UC4: User specified valid q values (with wavelength) # expected tth values are 2*arcsin(q) in degrees - ([4 * np.pi, np.array([[0, 1 / np.sqrt(2), 1.0], [1, 2, 3]])], np.array([[0, 90.0, 180.0], [1, 2, 3]])), + ([4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0])], np.array([0, 90.0, 180.0])), ] @pytest.mark.parametrize("inputs, expected", params_q_to_tth) def test_q_to_tth(inputs, expected): actual = q_to_tth(inputs[1], inputs[0]) - assert np.allclose(expected[0], actual[0]) - assert np.allclose(expected[1], actual[1]) + assert np.allclose(expected, actual) params_q_to_tth_bad = [ # UC1: user specified invalid q values that result in tth > 180 degrees ( - [4 * np.pi, np.array([[0.2, 0.4, 0.6, 0.8, 1, 1.2], [1, 2, 3, 4, 5, 6]])], + [4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])], [ ValueError, "The supplied q-array and wavelength will result in an impossible two-theta. " @@ -38,7 +37,7 @@ def test_q_to_tth(inputs, expected): ), # UC2: user specified a wrong wavelength that result in tth > 180 degrees ( - [100, np.array([[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]])], + [100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], [ ValueError, "The supplied q-array and wavelength will result in an impossible two-theta. " @@ -56,19 +55,19 @@ def test_q_to_tth_bad(inputs, expected): params_tth_to_q = [ # UC0: User specified empty tth values (without wavelength) - ([None, np.array([[], []])], np.array([[], []])), + ([None, np.array([])], np.array([])), # UC1: User specified empty tth values (with wavelength) - ([4 * np.pi, np.array([[], []])], np.array([[], []])), + ([4 * np.pi, np.array([])], np.array([])), # UC2: User specified valid tth values between 0-180 degrees (without wavelength) ( - [None, np.array([[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]])], - np.array([[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]), + [None, np.array([0, 30, 60, 90, 120, 180])], + np.array([0, 1, 2, 3, 4, 5]), ), # UC3: User specified valid tth values between 0-180 degrees (with wavelength) # expected q vales are sin15, sin30, sin45, sin60, sin90 ( - [4 * np.pi, np.array([[0, 30.0, 60.0, 90.0, 120.0, 180.0], [1, 2, 3, 4, 5, 6]])], - np.array([[0, 0.258819, 0.5, 0.707107, 0.866025, 1], [1, 2, 3, 4, 5, 6]]), + [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], + np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), ), ] @@ -82,12 +81,12 @@ def test_tth_to_q(inputs, expected): params_tth_to_q_bad = [ # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) ( - [None, np.array([[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]])], + [None, np.array([0, 30, 60, 90, 120, 181])], [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], ), # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) ( - [4 * np.pi, np.array([[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]])], + [4 * np.pi, np.array([0, 30, 60, 90, 120, 181])], [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], ), ] From d624faab8d7f16d5d87fdab83a094bdc445ce9f7 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 2 Dec 2024 15:25:20 -0500 Subject: [PATCH 084/445] Remove layer for scattering objects --- .../diffraction_objects.py | 0 .../utils/scattering_objects/__init__.py | 19 ------------------- .../{scattering_objects => }/user_config.py | 0 .../test_diffraction_objects.py | 0 4 files changed, 19 deletions(-) rename src/diffpy/utils/{scattering_objects => }/diffraction_objects.py (100%) delete mode 100644 src/diffpy/utils/scattering_objects/__init__.py rename src/diffpy/utils/{scattering_objects => }/user_config.py (100%) rename tests/diffpy/utils/{scattering_objects => }/test_diffraction_objects.py (100%) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py similarity index 100% rename from src/diffpy/utils/scattering_objects/diffraction_objects.py rename to src/diffpy/utils/diffraction_objects.py diff --git a/src/diffpy/utils/scattering_objects/__init__.py b/src/diffpy/utils/scattering_objects/__init__.py deleted file mode 100644 index 08069c20..00000000 --- a/src/diffpy/utils/scattering_objects/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.utils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Simon Billinge -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. -# -############################################################################## - -"""Scattering objects -""" - -# End of file diff --git a/src/diffpy/utils/scattering_objects/user_config.py b/src/diffpy/utils/user_config.py similarity index 100% rename from src/diffpy/utils/scattering_objects/user_config.py rename to src/diffpy/utils/user_config.py diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/test_diffraction_objects.py similarity index 100% rename from tests/diffpy/utils/scattering_objects/test_diffraction_objects.py rename to tests/diffpy/utils/test_diffraction_objects.py From 4132f1407c22e6ad1105a284e8ab06a46fcd07ff Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 2 Dec 2024 15:27:22 -0500 Subject: [PATCH 085/445] Add news for removing scattering objects layer --- news/scattering_obt.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scattering_obt.rst diff --git a/news/scattering_obt.rst b/news/scattering_obt.rst new file mode 100644 index 00000000..0090b88e --- /dev/null +++ b/news/scattering_obt.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* scattering_objects layer in importing diffraction_objects + +**Fixed:** + +* + +**Security:** + +* From 1442c7c7ae1df2c71a6b6b0e91d7c97bd5a9cf2d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 2 Dec 2024 15:32:51 -0500 Subject: [PATCH 086/445] Update docs without scttering objects layer --- doc/source/api/diffpy.utils.rst | 17 +++++++++++ .../api/diffpy.utils.scattering_objects.rst | 28 ------------------- .../examples/diffractionobjectsexample.rst | 4 +-- .../utilities/diffractionobjectsutility.rst | 2 +- .../diffpy/utils/test_diffraction_objects.py | 2 +- 5 files changed, 21 insertions(+), 32 deletions(-) delete mode 100644 doc/source/api/diffpy.utils.scattering_objects.rst diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index b873c333..868f546b 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -36,3 +36,20 @@ diffpy.utils.resampler module :members: :undoc-members: :show-inheritance: + + +diffpy.utils.user_config module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.user_config + :members: + :undoc-members: + :show-inheritance: + +diffpy.utils.diffraction_objects module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.diffraction_objects + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/doc/source/api/diffpy.utils.scattering_objects.rst b/doc/source/api/diffpy.utils.scattering_objects.rst deleted file mode 100644 index 4607ad98..00000000 --- a/doc/source/api/diffpy.utils.scattering_objects.rst +++ /dev/null @@ -1,28 +0,0 @@ -:tocdepth: -1 - -diffpy.utils.scattering_objects package -======================================= - -.. automodule:: diffpy.utils.scattering_objects - :members: - :undoc-members: - :show-inheritance: - -Submodules ----------- - -diffpy.utils.scattering_objects.diffraction_objects module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.scattering_objects.diffraction_objects - :members: - :undoc-members: - :show-inheritance: - -diffpy.utils.scattering_objects.user_config module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.scattering_objects.user_config - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/examples/diffractionobjectsexample.rst b/doc/source/examples/diffractionobjectsexample.rst index f3114ab0..48409432 100644 --- a/doc/source/examples/diffractionobjectsexample.rst +++ b/doc/source/examples/diffractionobjectsexample.rst @@ -6,7 +6,7 @@ Diffraction Objects Example ########################### This example will demonstrate how to use the ``DiffractionObject`` class in the -``diffpy.utils.scattering_objects.diffraction_objects`` module to process and analyze diffraction data. +``diffpy.utils.diffraction_objects`` module to process and analyze diffraction data. 1) Assuming we have created a ``DiffractionObject`` called my_diffraction_pattern from a measured diffraction pattern, and we have specified the wavelength (see Section ??, to be added), @@ -20,7 +20,7 @@ This example will demonstrate how to use the ``DiffractionObject`` class in the To load the converted array, you can either call ``test.q_to_tth()`` or ``test.on_q[0]``. :: # Example: convert tth to q - from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject + from diffpy.utils.diffraction_objects import DiffractionObject my_diffraction_pattern.on_tth = [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]] my_diffraction_pattern.tth_to_q() diff --git a/doc/source/utilities/diffractionobjectsutility.rst b/doc/source/utilities/diffractionobjectsutility.rst index d388e856..94cb1308 100644 --- a/doc/source/utilities/diffractionobjectsutility.rst +++ b/doc/source/utilities/diffractionobjectsutility.rst @@ -3,7 +3,7 @@ Diffraction Objects Utility =========================== -The ``diffpy.utils.scattering_objects.diffraction_objects`` module provides functions +The ``diffpy.utils.diffraction_objects`` module provides functions for managing and analyzing diffraction data, including angle-space conversions and interactions between diffraction data. diff --git a/tests/diffpy/utils/test_diffraction_objects.py b/tests/diffpy/utils/test_diffraction_objects.py index ada14ff1..1bc5220e 100644 --- a/tests/diffpy/utils/test_diffraction_objects.py +++ b/tests/diffpy/utils/test_diffraction_objects.py @@ -4,7 +4,7 @@ import pytest from freezegun import freeze_time -from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject +from diffpy.utils.diffraction_objects import DiffractionObject from diffpy.utils.transforms import wavelength_warning_emsg params = [ From 16937e74f0b2b90ae33a54520fc9fb5ecf4f82d2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:33:44 +0000 Subject: [PATCH 087/445] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/api/diffpy.utils.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 868f546b..2513e821 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -52,4 +52,4 @@ diffpy.utils.diffraction_objects module .. automodule:: diffpy.utils.diffraction_objects :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: From 5a32de45f573b423066222af49555eda26e8efb5 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 2 Dec 2024 18:13:17 -0500 Subject: [PATCH 088/445] initial commit, raise value error if xtype is invalid --- .../scattering_objects/diffraction_objects.py | 3 +- .../test_diffraction_objects.py | 51 +++++++++++++++---- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 4351c537..a4909af0 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -742,7 +742,8 @@ def on_xtype(self, xtype): return self.on_q elif xtype.lower() in DQUANTITIES: return self.on_d - pass + else: + raise ValueError(f"Unknown xtype: {xtype}. Allowed xtypes are {*XQUANTITIES, }.") def dump(self, filepath, xtype=None): if xtype is None: diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index ada14ff1..570404fe 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -1,11 +1,17 @@ +import re from pathlib import Path import numpy as np import pytest from freezegun import freeze_time -from diffpy.utils.scattering_objects.diffraction_objects import DiffractionObject -from diffpy.utils.transforms import wavelength_warning_emsg +from diffpy.utils.scattering_objects.diffraction_objects import ( + ANGLEQUANTITIES, + DQUANTITIES, + QQUANTITIES, + XQUANTITIES, + DiffractionObject, +) params = [ ( # Default @@ -232,13 +238,40 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert (diffraction_object1 == diffraction_object2) == expected -def _test_valid_diffraction_objects(actual_diffraction_object, function, expected_array): - if actual_diffraction_object.wavelength is None: - with pytest.warns(UserWarning) as warn_record: - getattr(actual_diffraction_object, function)() - assert str(warn_record[0].message) == wavelength_warning_emsg - actual_array = getattr(actual_diffraction_object, function)() - return np.allclose(actual_array, expected_array) +params_on_xtype = [ + ( + [ + np.array([1, 2, 3, 4, 5, 6]), # intensity array + np.array([0, 30, 60, 90, 120, 180]), # tth array + np.array([1, 2, 3, 4, 5, 6]), # q array + np.array([10, 20, 30, 40, 50, 60]), # d array + ], + [ + np.array([[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]), # expected on_tth + np.array([[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6]]), # expected on_q + np.array([[10, 20, 30, 40, 50, 60], [1, 2, 3, 4, 5, 6]]), # expected on_d + ], + ) +] + + +@pytest.mark.parametrize("inputs, expected", params_on_xtype) +def test_on_xtype(inputs, expected): + test = DiffractionObject() + test.on_tth = np.array([inputs[1], inputs[0]]) + test.on_q = np.array([inputs[2], inputs[0]]) + test.on_d = np.array([inputs[3], inputs[0]]) + for xtype_list, expected_value in zip([ANGLEQUANTITIES, QQUANTITIES, DQUANTITIES], expected): + for xtype in xtype_list: + assert np.allclose(test.on_xtype(xtype), expected_value) + + +def test_on_xtype_bad(): + test = DiffractionObject() + with pytest.raises( + ValueError, match=re.escape(f"Unknown xtype: invalid. Allowed xtypes are {*XQUANTITIES, }.") + ): + test.on_xtype("invalid") def test_dump(tmp_path, mocker): From b0cb3d8a3cb256dc97bddcaeacb0c2e0a164e447 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 2 Dec 2024 21:36:07 -0500 Subject: [PATCH 089/445] move tests to top level directory --- tests/{diffpy/utils => }/test_diffraction_objects.py | 0 tests/{diffpy/utils/parsers => }/test_loaddata.py | 0 tests/{diffpy/utils => }/test_resample.py | 0 tests/{diffpy/utils/parsers => }/test_serialization.py | 0 tests/{diffpy/utils => }/test_tools.py | 0 tests/{diffpy/utils => }/test_version.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tests/{diffpy/utils => }/test_diffraction_objects.py (100%) rename tests/{diffpy/utils/parsers => }/test_loaddata.py (100%) rename tests/{diffpy/utils => }/test_resample.py (100%) rename tests/{diffpy/utils/parsers => }/test_serialization.py (100%) rename tests/{diffpy/utils => }/test_tools.py (100%) rename tests/{diffpy/utils => }/test_version.py (100%) diff --git a/tests/diffpy/utils/test_diffraction_objects.py b/tests/test_diffraction_objects.py similarity index 100% rename from tests/diffpy/utils/test_diffraction_objects.py rename to tests/test_diffraction_objects.py diff --git a/tests/diffpy/utils/parsers/test_loaddata.py b/tests/test_loaddata.py similarity index 100% rename from tests/diffpy/utils/parsers/test_loaddata.py rename to tests/test_loaddata.py diff --git a/tests/diffpy/utils/test_resample.py b/tests/test_resample.py similarity index 100% rename from tests/diffpy/utils/test_resample.py rename to tests/test_resample.py diff --git a/tests/diffpy/utils/parsers/test_serialization.py b/tests/test_serialization.py similarity index 100% rename from tests/diffpy/utils/parsers/test_serialization.py rename to tests/test_serialization.py diff --git a/tests/diffpy/utils/test_tools.py b/tests/test_tools.py similarity index 100% rename from tests/diffpy/utils/test_tools.py rename to tests/test_tools.py diff --git a/tests/diffpy/utils/test_version.py b/tests/test_version.py similarity index 100% rename from tests/diffpy/utils/test_version.py rename to tests/test_version.py From e4b78fdd58e1dd03c0aa4af037cac9614216e8a1 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 3 Dec 2024 13:51:34 -0500 Subject: [PATCH 090/445] initial commit --- .../scattering_objects/diffraction_objects.py | 20 ++++++++++++++----- .../test_diffraction_objects.py | 18 +++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 4351c537..c881a5b5 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -614,14 +614,24 @@ def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): return array def get_angle_index(self, angle): - count = 0 + """ + returns the index of a given angle in the angles list + + Parameters + ---------- + angle float + the angle to search for + + Returns + ------- + the index of the angle in the angles list + """ + if not hasattr(self, "angles"): + self.angles = np.array([]) for i, target in enumerate(self.angles): if angle == target: return i - else: - count += 1 - if count >= len(self.angles): - raise IndexError(f"WARNING: no angle {angle} found in angles list") + raise IndexError(f"WARNING: no angle {angle} found in angles list.") def insert_scattering_quantity( self, diff --git a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py index ada14ff1..9fe4ef26 100644 --- a/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py +++ b/tests/diffpy/utils/scattering_objects/test_diffraction_objects.py @@ -241,6 +241,24 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte return np.allclose(actual_array, expected_array) +def test_get_angle_index(): + test = DiffractionObject() + test.angles = np.array([10, 20, 30, 40, 50, 60]) + actual_angle_index = test.get_angle_index(angle=10) + assert actual_angle_index == 0 + + +def test_get_angle_index_bad(): + test = DiffractionObject() + # empty angles list + with pytest.raises(IndexError, match="WARNING: no angle 11 found in angles list."): + test.get_angle_index(angle=11) + # pre-defined angles list + test.angles = np.array([10, 20, 30, 40, 50, 60]) + with pytest.raises(IndexError, match="WARNING: no angle 11 found in angles list."): + test.get_angle_index(angle=11) + + def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) From bf5207878b186e52e11c67259a9ff2fe3795860f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 3 Dec 2024 14:28:27 -0500 Subject: [PATCH 091/445] change test intensity array to avoid duplication with x-arrays --- tests/test_diffraction_objects.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 3271023b..a7e4ea04 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -241,15 +241,15 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): params_on_xtype = [ ( [ - np.array([1, 2, 3, 4, 5, 6]), # intensity array + np.array([100, 200, 300, 400, 500, 600]), # intensity array np.array([0, 30, 60, 90, 120, 180]), # tth array np.array([1, 2, 3, 4, 5, 6]), # q array np.array([10, 20, 30, 40, 50, 60]), # d array ], [ - np.array([[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]), # expected on_tth - np.array([[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6]]), # expected on_q - np.array([[10, 20, 30, 40, 50, 60], [1, 2, 3, 4, 5, 6]]), # expected on_d + np.array([[0, 30, 60, 90, 120, 180], [100, 200, 300, 400, 500, 600]]), # expected on_tth + np.array([[1, 2, 3, 4, 5, 6], [100, 200, 300, 400, 500, 600]]), # expected on_q + np.array([[10, 20, 30, 40, 50, 60], [100, 200, 300, 400, 500, 600]]), # expected on_d ], ) ] From 90be271f0061a3ee00813d2c61dc224bc47aee42 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Tue, 3 Dec 2024 23:14:10 -0500 Subject: [PATCH 092/445] DiffractionObject can now be instantiated directlyand basic data structure is a 2D array with everything in it --- news/constructor.rst | 25 ++ src/diffpy/utils/diffraction_objects.py | 475 +++--------------------- src/diffpy/utils/transforms.py | 22 +- tests/test_diffraction_objects.py | 466 +++++++++++++---------- tests/test_transforms.py | 4 +- 5 files changed, 361 insertions(+), 631 deletions(-) create mode 100644 news/constructor.rst diff --git a/news/constructor.rst b/news/constructor.rst new file mode 100644 index 00000000..9738c7a8 --- /dev/null +++ b/news/constructor.rst @@ -0,0 +1,25 @@ +**Added:** + +* + +**Changed:** + +* arrays and attributes now can be inserted when a DiffractionObject is instantiated +* data are now stored as a (len(x),4) numpy array with intensity in column 0, the q, then tth, then d +* `DiffractionObject.on_q`, on_tth and on_d are now methods and called as DiffractionObject.on_q() etc.` + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 4351c537..e628beed 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -1,11 +1,10 @@ import datetime -import warnings from copy import deepcopy import numpy as np from diffpy.utils.tools import get_package_info -from diffpy.utils.transforms import q_to_tth, tth_to_q +from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q QQUANTITIES = ["q"] ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] @@ -19,398 +18,17 @@ ) -class Diffraction_object: - """A class to represent and manipulate data associated with diffraction experiments. - - .. deprecated:: 3.5.1 - `Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0. It is replaced by - `DiffractionObject` to follow the class naming convention. - """ - - warnings.warn( - "Diffraction_object` is deprecated and will be removed in diffpy.utils 3.6.0, It is replaced by " - "DiffractionObject` to follow the class naming convention.", - DeprecationWarning, - stacklevel=2, - ) - - def __init__(self, name="", wavelength=None): - self.name = name - self.wavelength = wavelength - self.scat_quantity = "" - self.on_q = np.array([np.empty(0), np.empty(0)]) - self.on_tth = np.array([np.empty(0), np.empty(0)]) - self.on_d = np.array([np.empty(0), np.empty(0)]) - self._all_arrays = [self.on_q, self.on_tth] - self.metadata = {} - - def __eq__(self, other): - if not isinstance(other, Diffraction_object): - return NotImplemented - self_attributes = [key for key in self.__dict__ if not key.startswith("_")] - other_attributes = [key for key in other.__dict__ if not key.startswith("_")] - if not sorted(self_attributes) == sorted(other_attributes): - return False - for key in self_attributes: - value = getattr(self, key) - other_value = getattr(other, key) - if isinstance(value, float): - if ( - not (value is None and other_value is None) - and (value is None) - or (other_value is None) - or not np.isclose(value, other_value, rtol=1e-5) - ): - return False - elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): - if not all(np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): - return False - else: - if value != other_value: - return False - return True - - def __add__(self, other): - summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - summed.on_tth[1] = self.on_tth[1] + other - summed.on_q[1] = self.on_q[1] + other - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to sum two Diffraction_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] - summed.on_q[1] = self.on_q[1] + other.on_q[1] - return summed - - def __radd__(self, other): - summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - summed.on_tth[1] = self.on_tth[1] + other - summed.on_q[1] = self.on_q[1] + other - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to sum two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] - summed.on_q[1] = self.on_q[1] + other.on_q[1] - return summed - - def __sub__(self, other): - subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - subtracted.on_tth[1] = self.on_tth[1] - other - subtracted.on_q[1] = self.on_q[1] - other - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to subtract two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] - subtracted.on_q[1] = self.on_q[1] - other.on_q[1] - return subtracted - - def __rsub__(self, other): - subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - subtracted.on_tth[1] = other - self.on_tth[1] - subtracted.on_q[1] = other - self.on_q[1] - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to subtract two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] - subtracted.on_q[1] = other.on_q[1] - self.on_q[1] - return subtracted - - def __mul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to multiply two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied - - def __rmul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied - - def __truediv__(self, other): - divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - divided.on_tth[1] = other / self.on_tth[1] - divided.on_q[1] = other / self.on_q[1] - elif not isinstance(other, Diffraction_object): - raise TypeError("I only know how to multiply two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] - divided.on_q[1] = self.on_q[1] / other.on_q[1] - return divided - - def __rtruediv__(self, other): - divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - divided.on_tth[1] = other / self.on_tth[1] - divided.on_q[1] = other / self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] - divided.on_q[1] = other.on_q[1] / self.on_q[1] - return divided - - def set_angles_from_list(self, angles_list): - self.angles = angles_list - self.n_steps = len(angles_list) - 1.0 - self.begin_angle = self.angles[0] - self.end_angle = self.angles[-1] - - def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None): - """ - create an array of linear spaced Q-values - - Parameters - ---------- - begin_q float - the beginning angle - end_q float - the ending angle - step_size float - the size of the step between points. Only specify step_size or n_steps, not both - n_steps integer - the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both - - Returns - ------- - Sets self.qs - self.qs array of floats - the q values in the independent array - - """ - self.qs = self._set_array_from_range(begin_q, end_q, step_size=step_size, n_steps=n_steps) - - def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps=None): - """ - create an array of linear spaced angle-values - - Parameters - ---------- - begin_angle float - the beginning angle - end_angle float - the ending angle - step_size float - the size of the step between points. Only specify step_size or n_steps, not both - n_steps integer - the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both - - Returns - ------- - Sets self.angles - self.angles array of floats - the q values in the independent array - - """ - self.angles = self._set_array_from_range(begin_angle, end_angle, step_size=step_size, n_steps=n_steps) - - def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): - if step_size is not None and n_steps is not None: - print( - "WARNING: both step_size and n_steps have been given. n_steps will be used and step_size will be " - "reset." - ) - array = np.linspace(begin, end, n_steps) - elif step_size is not None: - array = np.arange(begin, end, step_size) - elif n_steps is not None: - array = np.linspace(begin, end, n_steps) - return array - - def get_angle_index(self, angle): - count = 0 - for i, target in enumerate(self.angles): - if angle == target: - return i - else: - count += 1 - if count >= len(self.angles): - raise IndexError(f"WARNING: no angle {angle} found in angles list") - - def insert_scattering_quantity( - 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: dict - the metadata in the form of a dictionary of user-supplied key:value pairs - - Returns - ------- - - """ - self.input_xtype = xtype - # empty attributes have been defined in the __init__ method so only - # set the attributes that are not empty to avoid emptying them by mistake - 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 - if xtype.lower() in QQUANTITIES: - self.on_q = np.array([xarray, yarray]) - elif xtype.lower() in ANGLEQUANTITIES: - self.on_tth = np.array([xarray, yarray]) - elif xtype.lower() in DQUANTITIES: - self.on_tth = np.array([xarray, yarray]) - self.set_all_arrays() - - def set_all_arrays(self): - master_array, xtype = self._get_original_array() - if xtype == "q": - self.on_tth = q_to_tth(self.on_q, self.wavelength) - elif xtype == "tth": - self.on_q = tth_to_q(self.on_tth, self.wavelength) - self.tthmin = self.on_tth[0][0] - self.tthmax = self.on_tth[0][-1] - self.qmin = self.on_q[0][0] - self.qmax = self.on_q[0][-1] - - def _get_original_array(self): - if self.input_xtype in QQUANTITIES: - return self.on_q, "q" - elif self.input_xtype in ANGLEQUANTITIES: - return self.on_tth, "tth" - elif self.input_xtype in DQUANTITIES: - return self.on_d, "d" - - def scale_to(self, target_diff_object, xtype=None, xvalue=None): - f""" - returns a new diffraction object which is the current object but recaled in y to the target - - Parameters - ---------- - target_diff_object: Diffraction_object - the diffractoin object you want to scale the current one on to - xtype: string, optional. Default is Q - the xtype, from {XQUANTITIES}, that you will specify a point from to scale to - xvalue: float. Default is the midpoint of the array - the y-value in the target at this x-value will be used as the factor to scale to. - The entire array is scaled be the factor that places on on top of the other at that point. - xvalue does not have to be in the x-array, the point closest to this point will be used for the scaling. - - Returns - ------- - the rescaled Diffraction_object as a new object - - """ - scaled = deepcopy(self) - if xtype is None: - xtype = "q" - - data = self.on_xtype(xtype) - target = target_diff_object.on_xtype(xtype) - if xvalue is None: - xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 - - xindex = (np.abs(data[0] - xvalue)).argmin() - ytarget = target[1][xindex] - yself = data[1][xindex] - scaled.on_tth[1] = data[1] * ytarget / yself - scaled.on_q[1] = data[1] * ytarget / yself - return scaled - - def on_xtype(self, xtype): - """ - return a 2D np array with x in the first column and y in the second for x of type type - Parameters - ---------- - xtype - - Returns - ------- - - """ - if xtype.lower() in ANGLEQUANTITIES: - return self.on_tth - elif xtype.lower() in QQUANTITIES: - return self.on_q - elif xtype.lower() in DQUANTITIES: - return self.on_d - pass - - def dump(self, filepath, xtype=None): - if xtype is None: - xtype = " q" - if xtype == "q": - data_to_save = np.column_stack((self.on_q[0], self.on_q[1])) - elif xtype == "tth": - data_to_save = np.column_stack((self.on_tth[0], self.on_tth[1])) - elif xtype == "d": - data_to_save = np.column_stack((self.on_d[0], self.on_d[1])) - else: - print(f"WARNING: cannot handle the xtype '{xtype}'") - self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) - self.metadata["creation_time"] = datetime.datetime.now() - - with open(filepath, "w") as f: - f.write( - f"[Diffraction_object]\nname = {self.name}\nwavelength = {self.wavelength}\n" - f"scat_quantity = {self.scat_quantity}\n" - ) - for key, value in self.metadata.items(): - f.write(f"{key} = {value}\n") - f.write("\n#### start data\n") - np.savetxt(f, data_to_save, delimiter=" ") - - class DiffractionObject: - def __init__(self, name="", wavelength=None): - self.name = name - self.wavelength = wavelength - self.scat_quantity = "" - self.on_q = np.empty((2, 0), dtype=np.float64) - self.on_tth = np.empty((2, 0), dtype=np.float64) - self.on_d = np.empty((2, 0), dtype=np.float64) - self._all_arrays = [self.on_q, self.on_tth] - self.metadata = {} + def __init__( + self, name="", wavelength=None, scat_quantity="", metadata={}, xarray=None, yarray=None, xtype="" + ): + if xarray is None: + xarray = np.empty(0) + if yarray is None: + yarray = np.empty(0) + self.insert_scattering_quantity( + xarray, yarray, xtype, metadata=metadata, scat_quantity=scat_quantity, name=name, wavelength=wavelength + ) def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -629,7 +247,7 @@ def insert_scattering_quantity( yarray, xtype, metadata={}, - scat_quantity=None, + scat_quantity="", name=None, wavelength=None, ): @@ -652,42 +270,47 @@ def insert_scattering_quantity( """ self.input_xtype = xtype - # empty attributes have been defined in the __init__ method so only - # set the attributes that are not empty to avoid emptying them by mistake - 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 + self.metadata = metadata + self.scat_quantity = scat_quantity + self.name = name + self.wavelength = wavelength + self.all_arrays = np.empty(shape=(len(yarray), 4)) + self.all_arrays[:, 0] = yarray if xtype.lower() in QQUANTITIES: - self.on_q = np.array([xarray, yarray]) + self.all_arrays[:, 1] = xarray + self.all_arrays[:, 2] = q_to_tth(xarray, wavelength) + self.all_arrays[:, 3] = q_to_d(xarray) elif xtype.lower() in ANGLEQUANTITIES: - self.on_tth = np.array([xarray, yarray]) - elif xtype.lower() in DQUANTITIES: # Fixme when d is implemented. This here as a placeholder - self.on_tth = np.array([xarray, yarray]) - self.set_all_arrays() - - def set_all_arrays(self): - master_array, xtype = self._get_original_array() - if xtype == "q": - self.on_tth = q_to_tth(self.on_q, self.wavelength) - elif xtype == "tth": - self.on_q = tth_to_q(self.on_tth, self.wavelength) - self.tthmin = self.on_tth[0][0] - self.tthmax = self.on_tth[0][-1] - self.qmin = self.on_q[0][0] - self.qmax = self.on_q[0][-1] + self.all_arrays[:, 2] = xarray + self.all_arrays[:, 1] = tth_to_q(xarray, wavelength) + self.all_arrays[:, 3] = tth_to_d(xarray, wavelength) + elif xtype.lower() in DQUANTITIES: + self.all_arrays[:, 3] = xarray + self.all_arrays[:, 1] = d_to_q(xarray) + self.all_arrays[:, 2] = d_to_tth(xarray, wavelength) + 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 _get_original_array(self): if self.input_xtype in QQUANTITIES: - return self.on_q, "q" + return self.on_q(), "q" elif self.input_xtype in ANGLEQUANTITIES: - return self.on_tth, "tth" + return self.on_tth(), "tth" elif self.input_xtype in DQUANTITIES: - return self.on_d, "d" + return self.on_d(), "d" + + def on_q(self): + return [self.all_arrays[:, 1], self.all_arrays[:, 0]] + + def on_tth(self): + return [self.all_arrays[:, 2], self.all_arrays[:, 0]] + + def on_d(self): + return [self.all_arrays[:, 3], self.all_arrays[:, 0]] def scale_to(self, target_diff_object, xtype=None, xvalue=None): f""" @@ -748,11 +371,11 @@ def dump(self, filepath, xtype=None): if xtype is None: xtype = " q" if xtype == "q": - data_to_save = np.column_stack((self.on_q[0], self.on_q[1])) + data_to_save = np.column_stack((self.on_q()[0], self.on_q()[1])) elif xtype == "tth": - data_to_save = np.column_stack((self.on_tth[0], self.on_tth[1])) + data_to_save = np.column_stack((self.on_tth()[0], self.on_tth()[1])) elif xtype == "d": - data_to_save = np.column_stack((self.on_d[0], self.on_d[1])) + data_to_save = np.column_stack((self.on_d()[0], self.on_d()[1])) else: print(f"WARNING: cannot handle the xtype '{xtype}'") self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 773aa607..71e50599 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -61,7 +61,7 @@ def q_to_tth(q, wavelength): This is the correct format for loading into diffpy.utils.DiffractionOject.on_tth """ _validate_inputs(q, wavelength) - q.astype(np.float64) + q.astype(float) tth = copy(q) # initialize output array of same shape if wavelength is not None: tth = np.rad2deg(2.0 * np.arcsin(q * wavelength / (4 * np.pi))) @@ -108,7 +108,7 @@ def tth_to_q(tth, wavelength): The units for the q-values are the inverse of the units of the provided wavelength. This is the correct format for loading into diffpy.utils.DiffractionOject.on_q """ - tth.astype(np.float64) + tth.astype(float) if np.any(np.deg2rad(tth) > np.pi): raise ValueError(invalid_tth_emsg) q = copy(tth) @@ -119,3 +119,21 @@ def tth_to_q(tth, wavelength): for i, _ in enumerate(q): q[i] = i return q + + +def q_to_d(qarray): + return 2.0 * np.pi / copy(qarray) + + +def tth_to_d(ttharray, wavelength): + qarray = tth_to_q(ttharray, wavelength) + return 2.0 * np.pi / copy(qarray) + + +def d_to_q(darray): + return 2.0 * np.pi / copy(darray) + + +def d_to_tth(darray, wavelength): + qarray = d_to_q(darray) + return q_to_tth(qarray, wavelength) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 1bc5220e..d04f5284 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -7,215 +7,179 @@ from diffpy.utils.diffraction_objects import DiffractionObject from diffpy.utils.transforms import wavelength_warning_emsg + +def compare_dicts(dict1, dict2): + assert dict1.keys() == dict2.keys(), "Keys mismatch" + for key in dict1: + val1, val2 = dict1[key], dict2[key] + if isinstance(val1, np.ndarray) and isinstance(val2, np.ndarray): + assert np.allclose(val1, val2), f"Arrays for key '{key}' differ" + elif isinstance(val1, np.float64) and isinstance(val2, np.float64): + assert np.isclose(val1, val2), f"Float64 values for key '{key}' differ" + else: + assert val1 == val2, f"Values for key '{key}' differ: {val1} != {val2}" + + +def dicts_equal(dict1, dict2): + equal = True + if not dict1.keys() == dict2.keys(): + equal = False + for key in dict1: + val1, val2 = dict1[key], dict2[key] + if isinstance(val1, np.ndarray) and isinstance(val2, np.ndarray): + if not np.allclose(val1, val2): + equal = False + elif isinstance(val1, np.float64) and isinstance(val2, np.float64): + if not np.isclose(val1, val2): + equal = False + else: + print(key, val1, val2) + if not val1 == val2: + equal = False + return equal + + params = [ ( # Default - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], + {}, + {}, True, ), ( # Compare same attributes - [ - "test", - 0.71, - "x-ray", - [np.array([1, 2]), np.array([3, 4])], - [np.array([1, 2]), np.array([3, 4])], - [np.array([1, 2]), np.array([3, 4])], - {"thing1": 1, "thing2": "thing2"}, - ], - [ - "test", - 0.7100001, - "x-ray", - [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], - [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], - [np.array([1.00001, 2.00001]), np.array([3.00001, 4.00001])], - {"thing1": 1, "thing2": "thing2"}, - ], + { + "name": "same", + "scat_quantity": "x-ray", + "wavelength": 0.71, + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1}, + }, + { + "name": "same", + "scat_quantity": "x-ray", + "wavelength": 0.71, + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1}, + }, True, ), ( # Different names - [ - "test1", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "test2", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], + { + "name": "something", + "scat_quantity": "", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "name": "something else", + "scat_quantity": "", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ( # Different wavelengths - [ - "", - 0.71, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - 0.711, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], + { + "scat_quantity": "", + "wavelength": 0.71, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "scat_quantity": "", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ( # Different wavelengths - [ - "", - 0.71, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], + { + "scat_quantity": "", + "wavelength": 0.71, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "scat_quantity": "", + "wavelength": 0.711, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ( # Different scat_quantity - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - None, - "x-ray", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], + { + "scat_quantity": "x-ray", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "scat_quantity": "neutron", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ( # Different on_q - [ - "", - None, - "", - [np.array([1, 2]), np.array([3, 4])], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - None, - "", - [np.array([1.01, 2]), np.array([3, 4])], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {}, - ], - False, - ), - ( # Different on_tth - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.array([1, 2]), np.array([3, 4])], - [np.empty(0), np.empty(0)], - {}, - ], - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.array([1.01, 2]), np.array([3, 4])], - [np.empty(0), np.empty(0)], - {}, - ], - False, - ), - ( # Different on_d - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.array([1, 2]), np.array([3, 4])], - {}, - ], - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.array([1.01, 2]), np.array([3, 4])], - {}, - ], + { + "scat_quantity": "", + "wavelength": None, + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {}, + }, + { + "scat_quantity": "", + "wavelength": None, + "xtype": "q", + "xarray": np.array([3.0, 4.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ( # Different metadata - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {"thing1": 0, "thing2": "thing2"}, - ], - [ - "", - None, - "", - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - [np.empty(0), np.empty(0)], - {"thing1": 1, "thing2": "thing2"}, - ], + { + "scat_quantity": "", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 0, "thing2": "thing2"}, + }, + { + "scat_quantity": "", + "wavelength": None, + "xtype": "", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, False, ), ] @@ -223,13 +187,13 @@ @pytest.mark.parametrize("inputs1, inputs2, expected", params) def test_diffraction_objects_equality(inputs1, inputs2, expected): - diffraction_object1 = DiffractionObject() - diffraction_object2 = DiffractionObject() - diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] - for i, attribute in enumerate(diffraction_object1_attributes): - setattr(diffraction_object1, attribute, inputs1[i]) - setattr(diffraction_object2, attribute, inputs2[i]) - assert (diffraction_object1 == diffraction_object2) == expected + diffraction_object1 = DiffractionObject(inputs1) + diffraction_object2 = DiffractionObject(inputs2) + # diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] + # for i, attribute in enumerate(diffraction_object1_attributes): + # setattr(diffraction_object1, attribute, inputs1[i]) + # setattr(diffraction_object2, attribute, inputs2[i]) + assert dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__) == expected def _test_valid_diffraction_objects(actual_diffraction_object, function, expected_array): @@ -245,14 +209,13 @@ def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - test = DiffractionObject() - test.wavelength = 1.54 - test.name = "test" - test.scat_quantity = "x-ray" - test.insert_scattering_quantity( - np.array(x), - np.array(y), - "q", + test = DiffractionObject( + wavelength=1.54, + name="test", + scat_quantity="x-ray", + xarray=np.array(x), + yarray=np.array(y), + xtype="q", metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, ) mocker.patch("importlib.metadata.version", return_value="3.3.0") @@ -273,3 +236,104 @@ def test_dump(tmp_path, mocker): ) assert actual == expected + + +tc_params = [ + ( + {}, + { + "all_arrays": np.empty(shape=(0, 4)), # instantiate empty + "metadata": {}, + "input_xtype": "", + "name": "", + "scat_quantity": "", + "qmin": np.float64(np.inf), + "qmax": np.float64(0.0), + "tthmin": np.float64(np.inf), + "tthmax": np.float64(0.0), + "dmin": np.float64(np.inf), + "dmax": np.float64(0.0), + "wavelength": None, + }, + ), + ( # instantiate just non-array attributes + {"name": "test", "scat_quantity": "x-ray", "metadata": {"thing": "1", "another": "2"}}, + { + "all_arrays": np.empty(shape=(0, 4)), + "metadata": {"thing": "1", "another": "2"}, + "input_xtype": "", + "name": "test", + "scat_quantity": "x-ray", + "qmin": np.float64(np.inf), + "qmax": np.float64(0.0), + "tthmin": np.float64(np.inf), + "tthmax": np.float64(0.0), + "dmin": np.float64(np.inf), + "dmax": np.float64(0.0), + "wavelength": None, + }, + ), + ( # instantiate just array attributes + { + "xarray": np.array([0.0, 90.0, 180.0]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "tth", + "wavelength": 4.0 * np.pi, + }, + { + "all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "input_xtype": "tth", + "name": "", + "scat_quantity": "", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ( # instantiate just array attributes + { + "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "d", + "wavelength": 4.0 * np.pi, + "scat_quantity": "x-ray", + }, + { + "all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "input_xtype": "d", + "name": "", + "scat_quantity": "x-ray", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), +] + + +@pytest.mark.parametrize("inputs, expected", tc_params) +def test_constructor(inputs, expected): + actualdo = DiffractionObject(**inputs) + compare_dicts(actualdo.__dict__, expected) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 7e3eae3f..e8b15492 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,9 +5,9 @@ params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays - ([None, np.empty((1,))], np.empty((1,))), + ([None, np.empty((0))], np.empty((0))), # UC2: Empty q values, wavelength specified, return empty arrays - ([4 * np.pi, np.empty((1,))], np.empty((1,))), + ([4 * np.pi, np.empty((0))], np.empty(0)), # UC3: User specified valid q values, no wavelength, return empty arrays ( [None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], From 828b2a69e696dc357061f9610ddb9a145a188f0a Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 4 Dec 2024 08:18:33 -0500 Subject: [PATCH 093/445] now passing tests --- src/diffpy/utils/diffraction_objects.py | 98 ++++++++++++++----------- tests/test_diffraction_objects.py | 16 ++-- 2 files changed, 65 insertions(+), 49 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index e628beed..1daa62d0 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -1,4 +1,5 @@ import datetime +import warnings from copy import deepcopy import numpy as np @@ -18,17 +19,31 @@ ) +def _xtype_wmsg(xtype): + return ( + f"WARNING: I don't know how to handle the xtype, '{xtype}'. Please rerun specifying and " + f"xtype from {*XQUANTITIES, }" + ) + + class DiffractionObject: def __init__( - self, name="", wavelength=None, scat_quantity="", metadata={}, xarray=None, yarray=None, xtype="" + self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype="" ): + if name is None: + name = "" + self.name = name + if metadata is None: + metadata = {} + self.metadata = metadata + 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.insert_scattering_quantity( - xarray, yarray, xtype, metadata=metadata, scat_quantity=scat_quantity, name=name, wavelength=wavelength - ) + self.insert_scattering_quantity(xarray, yarray, xtype) def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -241,15 +256,32 @@ def get_angle_index(self, angle): if count >= len(self.angles): raise IndexError(f"WARNING: no angle {angle} found in angles list") + def _set_xarrays(self, xarray, xtype): + self.all_arrays = np.empty(shape=(len(xarray), 4)) + if xtype.lower() in QQUANTITIES: + self.all_arrays[:, 1] = xarray + self.all_arrays[:, 2] = q_to_tth(xarray, self.wavelength) + self.all_arrays[:, 3] = q_to_d(xarray) + elif xtype.lower() in ANGLEQUANTITIES: + self.all_arrays[:, 2] = xarray + self.all_arrays[:, 1] = tth_to_q(xarray, self.wavelength) + self.all_arrays[:, 3] = tth_to_d(xarray, self.wavelength) + elif xtype.lower() in DQUANTITIES: + self.all_arrays[:, 3] = xarray + self.all_arrays[:, 1] = d_to_q(xarray) + self.all_arrays[:, 2] = d_to_tth(xarray, self.wavelength) + 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 insert_scattering_quantity( self, xarray, yarray, xtype, - metadata={}, - scat_quantity="", - name=None, - wavelength=None, ): f""" insert a new scattering quantity into the scattering object @@ -262,38 +294,14 @@ def insert_scattering_quantity( the dependent variable array xtype string the type of quantity for the independent variable from {*XQUANTITIES, } - metadata: dict - the metadata in the form of a dictionary of user-supplied key:value pairs Returns ------- """ - self.input_xtype = xtype - self.metadata = metadata - self.scat_quantity = scat_quantity - self.name = name - self.wavelength = wavelength - self.all_arrays = np.empty(shape=(len(yarray), 4)) + self._set_xarrays(xarray, xtype) self.all_arrays[:, 0] = yarray - if xtype.lower() in QQUANTITIES: - self.all_arrays[:, 1] = xarray - self.all_arrays[:, 2] = q_to_tth(xarray, wavelength) - self.all_arrays[:, 3] = q_to_d(xarray) - elif xtype.lower() in ANGLEQUANTITIES: - self.all_arrays[:, 2] = xarray - self.all_arrays[:, 1] = tth_to_q(xarray, wavelength) - self.all_arrays[:, 3] = tth_to_d(xarray, wavelength) - elif xtype.lower() in DQUANTITIES: - self.all_arrays[:, 3] = xarray - self.all_arrays[:, 1] = d_to_q(xarray) - self.all_arrays[:, 2] = d_to_tth(xarray, wavelength) - 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) + self.input_xtype = xtype def _get_original_array(self): if self.input_xtype in QQUANTITIES: @@ -319,7 +327,7 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): Parameters ---------- target_diff_object: DiffractionObject - the diffractoin object you want to scale the current one on to + the diffraction object you want to scale the current one on to xtype: string, optional. Default is Q the xtype, from {XQUANTITIES}, that you will specify a point from to scale to xvalue: float. Default is the midpoint of the array @@ -351,6 +359,7 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): def on_xtype(self, xtype): """ return a 2D np array with x in the first column and y in the second for x of type type + Parameters ---------- xtype @@ -360,24 +369,25 @@ def on_xtype(self, xtype): """ if xtype.lower() in ANGLEQUANTITIES: - return self.on_tth + return self.on_tth() elif xtype.lower() in QQUANTITIES: - return self.on_q + return self.on_q() elif xtype.lower() in DQUANTITIES: - return self.on_d - pass + return self.on_d() + else: + warnings.warn(_xtype_wmsg(xtype)) def dump(self, filepath, xtype=None): if xtype is None: - xtype = " q" - if xtype == "q": + xtype = "q" + if xtype in QQUANTITIES: data_to_save = np.column_stack((self.on_q()[0], self.on_q()[1])) - elif xtype == "tth": + elif xtype in ANGLEQUANTITIES: data_to_save = np.column_stack((self.on_tth()[0], self.on_tth()[1])) - elif xtype == "d": + elif xtype in DQUANTITIES: data_to_save = np.column_stack((self.on_d()[0], self.on_d()[1])) else: - print(f"WARNING: cannot handle the xtype '{xtype}'") + warnings.warn(_xtype_wmsg(xtype)) self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) self.metadata["creation_time"] = datetime.datetime.now() diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index d04f5284..9db6932e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -22,6 +22,9 @@ def compare_dicts(dict1, dict2): def dicts_equal(dict1, dict2): equal = True + print("") + print(dict1) + print(dict2) if not dict1.keys() == dict2.keys(): equal = False for key in dict1: @@ -29,11 +32,13 @@ def dicts_equal(dict1, dict2): if isinstance(val1, np.ndarray) and isinstance(val2, np.ndarray): if not np.allclose(val1, val2): equal = False + elif isinstance(val1, list) and isinstance(val2, list): + if not val1.all() == val2.all(): + equal = False elif isinstance(val1, np.float64) and isinstance(val2, np.float64): if not np.isclose(val1, val2): equal = False else: - print(key, val1, val2) if not val1 == val2: equal = False return equal @@ -187,12 +192,13 @@ def dicts_equal(dict1, dict2): @pytest.mark.parametrize("inputs1, inputs2, expected", params) def test_diffraction_objects_equality(inputs1, inputs2, expected): - diffraction_object1 = DiffractionObject(inputs1) - diffraction_object2 = DiffractionObject(inputs2) + diffraction_object1 = DiffractionObject(**inputs1) + diffraction_object2 = DiffractionObject(**inputs2) # diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] # for i, attribute in enumerate(diffraction_object1_attributes): # setattr(diffraction_object1, attribute, inputs1[i]) # setattr(diffraction_object2, attribute, inputs2[i]) + print(dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__), expected) assert dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__) == expected @@ -246,7 +252,7 @@ def test_dump(tmp_path, mocker): "metadata": {}, "input_xtype": "", "name": "", - "scat_quantity": "", + "scat_quantity": None, "qmin": np.float64(np.inf), "qmax": np.float64(0.0), "tthmin": np.float64(np.inf), @@ -291,7 +297,7 @@ def test_dump(tmp_path, mocker): "metadata": {}, "input_xtype": "tth", "name": "", - "scat_quantity": "", + "scat_quantity": None, "qmin": np.float64(0.0), "qmax": np.float64(1.0), "tthmin": np.float64(0.0), From daa9c3f1b311c5dfdd0216c39ff094d00d5be445 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 4 Dec 2024 08:40:04 -0500 Subject: [PATCH 094/445] small tweaks and typo fixes --- news/constructor.rst | 2 +- src/diffpy/utils/diffraction_objects.py | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/news/constructor.rst b/news/constructor.rst index 9738c7a8..39ffa77f 100644 --- a/news/constructor.rst +++ b/news/constructor.rst @@ -6,7 +6,7 @@ * arrays and attributes now can be inserted when a DiffractionObject is instantiated * data are now stored as a (len(x),4) numpy array with intensity in column 0, the q, then tth, then d -* `DiffractionObject.on_q`, on_tth and on_d are now methods and called as DiffractionObject.on_q() etc.` +* `DiffractionObject.on_q`, `...on_tth` and `...on_d` are now methods and called as `DiffractionObject.on_q()` etc.` **Deprecated:** diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 1daa62d0..54374fa7 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -21,14 +21,14 @@ def _xtype_wmsg(xtype): return ( - f"WARNING: I don't know how to handle the xtype, '{xtype}'. Please rerun specifying and " + f"WARNING: I don't know how to handle the xtype, '{xtype}'. Please rerun specifying an " f"xtype from {*XQUANTITIES, }" ) class DiffractionObject: def __init__( - self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype="" + self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype=None ): if name is None: name = "" @@ -36,6 +36,8 @@ def __init__( if metadata is None: metadata = {} self.metadata = metadata + if xtype is None: + xtype = "" self.scat_quantity = scat_quantity self.wavelength = wavelength @@ -282,6 +284,10 @@ def insert_scattering_quantity( xarray, yarray, xtype, + metadata={}, + scat_quantity=None, + name=None, + wavelength=None, ): f""" insert a new scattering quantity into the scattering object @@ -294,14 +300,27 @@ def insert_scattering_quantity( 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. """ 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 def _get_original_array(self): if self.input_xtype in QQUANTITIES: From 8342f2ac2d56cae13dac3f155fa69eb55a425587 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 4 Dec 2024 09:53:01 -0500 Subject: [PATCH 095/445] Use private _all_arrays to set --- src/diffpy/utils/diffraction_objects.py | 45 +++++++++++++++---------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 54374fa7..66b840e5 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -181,6 +181,17 @@ def __rtruediv__(self, other): divided.on_q[1] = other.on_q[1] / self.on_q[1] return divided + @property + def all_arrays(self): + return self._all_arrays + + @all_arrays.setter + def all_arrays(self, value): + raise AttributeError( + "Direct modification of 'all_arrays' is not allowed." + "Please use 'insert_scattering_quantity' to modify it." + ) + def set_angles_from_list(self, angles_list): self.angles = angles_list self.n_steps = len(angles_list) - 1.0 @@ -259,25 +270,25 @@ def get_angle_index(self, angle): raise IndexError(f"WARNING: no angle {angle} found in angles list") def _set_xarrays(self, xarray, xtype): - self.all_arrays = np.empty(shape=(len(xarray), 4)) + self._all_arrays = np.empty(shape=(len(xarray), 4)) if xtype.lower() in QQUANTITIES: - self.all_arrays[:, 1] = xarray - self.all_arrays[:, 2] = q_to_tth(xarray, self.wavelength) - self.all_arrays[:, 3] = q_to_d(xarray) + self._all_arrays[:, 1] = xarray + self._all_arrays[:, 2] = q_to_tth(xarray, self.wavelength) + self._all_arrays[:, 3] = q_to_d(xarray) elif xtype.lower() in ANGLEQUANTITIES: - self.all_arrays[:, 2] = xarray - self.all_arrays[:, 1] = tth_to_q(xarray, self.wavelength) - self.all_arrays[:, 3] = tth_to_d(xarray, self.wavelength) + self._all_arrays[:, 2] = xarray + self._all_arrays[:, 1] = tth_to_q(xarray, self.wavelength) + self._all_arrays[:, 3] = tth_to_d(xarray, self.wavelength) elif xtype.lower() in DQUANTITIES: - self.all_arrays[:, 3] = xarray - self.all_arrays[:, 1] = d_to_q(xarray) - self.all_arrays[:, 2] = d_to_tth(xarray, self.wavelength) - 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) + self._all_arrays[:, 3] = xarray + self._all_arrays[:, 1] = d_to_q(xarray) + self._all_arrays[:, 2] = d_to_tth(xarray, self.wavelength) + 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 insert_scattering_quantity( self, @@ -309,7 +320,7 @@ def insert_scattering_quantity( """ self._set_xarrays(xarray, xtype) - self.all_arrays[:, 0] = yarray + 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 From 6278a8ebfdd195821e06d8960e65b3c16202ffee Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 4 Dec 2024 09:53:24 -0500 Subject: [PATCH 096/445] Update tests with actual object info --- tests/test_diffraction_objects.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9db6932e..4651273e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -341,5 +341,27 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize("inputs, expected", tc_params) def test_constructor(inputs, expected): - actualdo = DiffractionObject(**inputs) - compare_dicts(actualdo.__dict__, expected) + actual_do = DiffractionObject(**inputs) + actual_dict = { + "all_arrays": actual_do.all_arrays, + "metadata": actual_do.metadata, + "input_xtype": actual_do.input_xtype, + "name": actual_do.name, + "scat_quantity": actual_do.scat_quantity, + "qmin": actual_do.qmin, + "qmax": actual_do.qmax, + "tthmin": actual_do.tthmin, + "tthmax": actual_do.tthmax, + "dmin": actual_do.dmin, + "dmax": actual_do.dmax, + "wavelength": actual_do.wavelength, + } + compare_dicts(actual_dict, expected) + + +def test_all_array_setter(): + actual_do = DiffractionObject() + + # Attempt to directly modify the property + with pytest.raises(AttributeError, match="Direct modification of 'all_arrays' is not allowed."): + actual_do.all_arrays = np.empty((4, 4)) From 9772e1ce034f2ed51aed218540237edacd056f1e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 4 Dec 2024 09:53:33 -0500 Subject: [PATCH 097/445] Add news --- news/setter-property.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/setter-property.rst diff --git a/news/setter-property.rst b/news/setter-property.rst new file mode 100644 index 00000000..8b2ddc97 --- /dev/null +++ b/news/setter-property.rst @@ -0,0 +1,23 @@ +**Added:** + +* prevent direct modification of `all_arrays` using `@property` + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 5257ee227b26acb2137f219fbf3ccdbdd28b8103 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 4 Dec 2024 16:27:43 -0500 Subject: [PATCH 098/445] add more bad tests --- src/diffpy/utils/diffraction_objects.py | 22 ++++++----- tests/test_diffraction_objects.py | 49 ++++++++++++++++++------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index e9b15696..88c4452e 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -248,25 +248,27 @@ def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): array = np.linspace(begin, end, n_steps) return array - def get_angle_index(self, angle): + def get_array_index(self, xtype, value): """ - returns the index of a given angle in the angles list + returns the index of a given value in the array associated with the specified xtype Parameters ---------- - angle float - the angle to search for + xtype str + the xtype used to access the array + value float + the target value to search for Returns ------- - the index of the angle in the angles list + the index of the value in the array """ - if not hasattr(self, "angles"): - self.angles = np.array([]) - for i, target in enumerate(self.angles): - if angle == target: + if self.on_xtype(xtype) is None: + raise ValueError(_xtype_wmsg(xtype)) + for i, target in enumerate(self.on_xtype(xtype)[0]): + if value == target: return i - raise IndexError(f"WARNING: no angle {angle} found in angles list.") + raise IndexError(f"WARNING: no matching value {value} found in the {xtype} array.") def _set_xarrays(self, xarray, xtype): self.all_arrays = np.empty(shape=(len(xarray), 4)) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index c1ee6f15..045c7cf2 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -1,10 +1,11 @@ +import re from pathlib import Path import numpy as np import pytest from freezegun import freeze_time -from diffpy.utils.diffraction_objects import DiffractionObject +from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject from diffpy.utils.transforms import wavelength_warning_emsg @@ -212,21 +213,41 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte def test_get_angle_index(): - test = DiffractionObject() - test.angles = np.array([10, 20, 30, 40, 50, 60]) - actual_angle_index = test.get_angle_index(angle=10) - assert actual_angle_index == 0 + test = DiffractionObject( + wavelength=0.71, xarray=np.array([30, 60, 90]), yarray=np.array([1, 2, 3]), xtype="tth" + ) + actual_index = test.get_array_index(xtype="tth", value=30) + assert actual_index == 0 + + +params_index_bad = [ + # UC1: empty array + ( + [0.71, np.array([]), np.array([]), "tth", "tth", 10], + [IndexError, "WARNING: no matching value 10 found in the tth array."], + ), + # UC2: invalid xtype + ( + [None, np.array([]), np.array([]), "tth", "invalid", 10], + [ + ValueError, + f"WARNING: I don't know how to handle the xtype, 'invalid'. " + f"Please rerun specifying an xtype from {*XQUANTITIES, }", + ], + ), + # UC3: pre-defined array with non-matching value + ( + [0.71, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "q", 30], + [IndexError, "WARNING: no matching value 30 found in the q array."], + ), +] -def test_get_angle_index_bad(): - test = DiffractionObject() - # empty angles list - with pytest.raises(IndexError, match="WARNING: no angle 11 found in angles list."): - test.get_angle_index(angle=11) - # pre-defined angles list - test.angles = np.array([10, 20, 30, 40, 50, 60]) - with pytest.raises(IndexError, match="WARNING: no angle 11 found in angles list."): - test.get_angle_index(angle=11) +@pytest.mark.parametrize("inputs, expected", params_index_bad) +def test_get_angle_index_bad(inputs, expected): + test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3]) + with pytest.raises(expected[0], match=re.escape(expected[1])): + test.get_array_index(xtype=inputs[4], value=inputs[5]) def test_dump(tmp_path, mocker): From 1f9f142f3e7f00e0da46d84fd4c8b8d442ba9900 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 4 Dec 2024 16:44:37 -0500 Subject: [PATCH 099/445] edit tests --- src/diffpy/utils/diffraction_objects.py | 2 +- tests/test_diffraction_objects.py | 45 ++++++------------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 54374fa7..c213b5df 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -394,7 +394,7 @@ def on_xtype(self, xtype): elif xtype.lower() in DQUANTITIES: return self.on_d() else: - warnings.warn(_xtype_wmsg(xtype)) + raise ValueError(_xtype_wmsg(xtype)) def dump(self, filepath, xtype=None): if xtype is None: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 09506fec..beecab56 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -5,13 +5,7 @@ import pytest from freezegun import freeze_time -from diffpy.utils.diffraction_objects import ( - ANGLEQUANTITIES, - DQUANTITIES, - QQUANTITIES, - XQUANTITIES, - DiffractionObject, -) +from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject def compare_dicts(dict1, dict2): @@ -208,38 +202,21 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__) == expected -params_on_xtype = [ - ( - [ - np.array([100, 200, 300, 400, 500, 600]), # intensity array - np.array([0, 30, 60, 90, 120, 180]), # tth array - np.array([1, 2, 3, 4, 5, 6]), # q array - np.array([10, 20, 30, 40, 50, 60]), # d array - ], - [ - np.array([[0, 30, 60, 90, 120, 180], [100, 200, 300, 400, 500, 600]]), # expected on_tth - np.array([[1, 2, 3, 4, 5, 6], [100, 200, 300, 400, 500, 600]]), # expected on_q - np.array([[10, 20, 30, 40, 50, 60], [100, 200, 300, 400, 500, 600]]), # expected on_d - ], - ) -] - - -@pytest.mark.parametrize("inputs, expected", params_on_xtype) -def test_on_xtype(inputs, expected): - test = DiffractionObject() - test.on_tth = np.array([inputs[1], inputs[0]]) - test.on_q = np.array([inputs[2], inputs[0]]) - test.on_d = np.array([inputs[3], inputs[0]]) - for xtype_list, expected_value in zip([ANGLEQUANTITIES, QQUANTITIES, DQUANTITIES], expected): - for xtype in xtype_list: - assert np.allclose(test.on_xtype(xtype), expected_value) +def test_on_xtype(): + test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") + assert np.allclose(test.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])]) + assert np.allclose(test.on_xtype("q"), [np.array([0.51763809, 1]), np.array([1, 2])]) + assert np.allclose(test.on_xtype("d"), [np.array([12.13818192, 6.28318531]), np.array([1, 2])]) def test_on_xtype_bad(): test = DiffractionObject() with pytest.raises( - ValueError, match=re.escape(f"Unknown xtype: invalid. Allowed xtypes are {*XQUANTITIES, }.") + ValueError, + match=re.escape( + f"WARNING: I don't know how to handle the xtype, 'invalid'. Please rerun specifying an " + f"xtype from {*XQUANTITIES, }" + ), ): test.on_xtype("invalid") From 11a15ee8f939b7df8dcdb18112c4624a985e1516 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 4 Dec 2024 19:15:17 -0500 Subject: [PATCH 100/445] add docstring and news --- news/xtype.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/diffraction_objects.py | 7 ++++--- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 news/xtype.rst diff --git a/news/xtype.rst b/news/xtype.rst new file mode 100644 index 00000000..24a78758 --- /dev/null +++ b/news/xtype.rst @@ -0,0 +1,23 @@ +**Added:** + +* functionality to return the 2D array based on the specified xtype + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index c213b5df..b1ed5a78 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -376,16 +376,17 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): return scaled def on_xtype(self, xtype): - """ + f""" return a 2D np array with x in the first column and y in the second for x of type type Parameters ---------- - xtype + xtype str + the type of quantity for the independent variable from {*XQUANTITIES, } Returns ------- - + a 2D np array with x and y data """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth() From 56e72788bedea4f90ddcc8985fcc9c3b34e848a8 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 5 Dec 2024 10:55:27 -0500 Subject: [PATCH 101/445] add more tests --- src/diffpy/utils/diffraction_objects.py | 34 ++++++++++---- tests/test_diffraction_objects.py | 61 +++++++++++++++++-------- 2 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 88c4452e..3a0f88bc 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -248,9 +248,9 @@ def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): array = np.linspace(begin, end, n_steps) return array - def get_array_index(self, xtype, value): + def get_array_index(self, value, xtype=None): """ - returns the index of a given value in the array associated with the specified xtype + returns the index of the closest value in the array associated with the specified xtype Parameters ---------- @@ -263,12 +263,30 @@ def get_array_index(self, xtype, value): ------- the index of the value in the array """ - if self.on_xtype(xtype) is None: - raise ValueError(_xtype_wmsg(xtype)) - for i, target in enumerate(self.on_xtype(xtype)[0]): - if value == target: - return i - raise IndexError(f"WARNING: no matching value {value} found in the {xtype} array.") + + if xtype is None: + xtype = self.input_xtype + if self.on_xtype(xtype) is None or len(self.on_xtype(xtype)[0]) == 0: + raise ValueError( + f"The '{xtype}' array is empty. " "Please ensure it is initialized and the correct xtype is used." + ) + array = self.on_xtype(xtype)[0] + i = (np.abs(array - value)).argmin() + nearest_value = np.abs(array[i] - value) + distance = min(np.abs(value - array.min()), np.abs(value - array.max())) + threshold = 0.5 * (array.max() - array.min()) + + if nearest_value != 0 and (array.min() <= value <= array.max() or distance <= threshold): + warnings.warn( + f"WARNING: The value {value} is not an exact match of the '{xtype}' array. " + f"Returning the index of the closest value." + ) + elif distance > threshold: + raise IndexError( + f"The value {value} is too far from any value in the '{xtype}' array. " + f"Please check if you have specified the correct xtype. " + ) + return i def _set_xarrays(self, xarray, xtype): self.all_arrays = np.empty(shape=(len(xarray), 4)) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 045c7cf2..1bf3a459 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -5,7 +5,7 @@ import pytest from freezegun import freeze_time -from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject +from diffpy.utils.diffraction_objects import DiffractionObject from diffpy.utils.transforms import wavelength_warning_emsg @@ -212,42 +212,65 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte return np.allclose(actual_array, expected_array) -def test_get_angle_index(): - test = DiffractionObject( - wavelength=0.71, xarray=np.array([30, 60, 90]), yarray=np.array([1, 2, 3]), xtype="tth" - ) - actual_index = test.get_array_index(xtype="tth", value=30) - assert actual_index == 0 +params_index = [ + # UC1: exact match + ([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]), + # UC2: target value lies in the array, returns the (first) closest index + ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45], [0]), + ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25], [0]), + # UC3: target value out of the range but within reasonable distance, returns the closest index + ([4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1], [0]), + ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63], [1]), +] + + +@pytest.mark.parametrize("inputs, expected", params_index) +def test_get_array_index(inputs, expected): + test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3]) + actual = test.get_array_index(value=inputs[5], xtype=inputs[4]) + assert actual == expected[0] params_index_bad = [ - # UC1: empty array + # UC0: empty array ( - [0.71, np.array([]), np.array([]), "tth", "tth", 10], - [IndexError, "WARNING: no matching value 10 found in the tth array."], + [2 * np.pi, np.array([]), np.array([]), "tth", "tth", 30], + [ValueError, "The 'tth' array is empty. Please ensure it is initialized and the correct xtype is used."], ), - # UC2: invalid xtype + # UC1: empty array (because of invalid xtype) ( - [None, np.array([]), np.array([]), "tth", "invalid", 10], + [2 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "invalid", 30], [ ValueError, - f"WARNING: I don't know how to handle the xtype, 'invalid'. " - f"Please rerun specifying an xtype from {*XQUANTITIES, }", + "The 'invalid' array is empty. Please ensure it is initialized and the correct xtype is used.", ], ), - # UC3: pre-defined array with non-matching value + # UC3: value is too far from any element in the array ( - [0.71, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "q", 30], - [IndexError, "WARNING: no matching value 30 found in the q array."], + [2 * np.pi, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "tth", 140], + [ + IndexError, + "The value 140 is too far from any value in the 'tth' array. " + "Please check if you have specified the correct xtype.", + ], + ), + # UC4: value is too far from any element in the array (because of wrong xtype) + ( + [2 * np.pi, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "q", 30], + [ + IndexError, + "The value 30 is too far from any value in the 'q' array. " + "Please check if you have specified the correct xtype.", + ], ), ] @pytest.mark.parametrize("inputs, expected", params_index_bad) -def test_get_angle_index_bad(inputs, expected): +def test_get_array_index_bad(inputs, expected): test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3]) with pytest.raises(expected[0], match=re.escape(expected[1])): - test.get_array_index(xtype=inputs[4], value=inputs[5]) + test.get_array_index(value=inputs[5], xtype=inputs[4]) def test_dump(tmp_path, mocker): From 57465e2563090b88807f46762b43ca9deaf53b7a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 5 Dec 2024 11:08:12 -0500 Subject: [PATCH 102/445] edit tests --- tests/test_diffraction_objects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index beecab56..95848111 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -205,8 +205,9 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_on_xtype(): test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") assert np.allclose(test.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])]) - assert np.allclose(test.on_xtype("q"), [np.array([0.51763809, 1]), np.array([1, 2])]) - assert np.allclose(test.on_xtype("d"), [np.array([12.13818192, 6.28318531]), np.array([1, 2])]) + assert np.allclose(test.on_xtype("2theta"), [np.array([30, 60]), np.array([1, 2])]) + assert np.allclose(test.on_xtype("q"), [np.array([0.51764, 1]), np.array([1, 2])]) + assert np.allclose(test.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) def test_on_xtype_bad(): From 3ba3038f6531a4c0951a193f5f1cf0548085afcc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 5 Dec 2024 13:38:39 -0500 Subject: [PATCH 103/445] Use deepdiff to compare do.__dict__ --- news/scattering_obt.rst | 2 -- requirements/test.txt | 1 + tests/test_diffraction_objects.py | 44 ++++++++++++++++++------------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/news/scattering_obt.rst b/news/scattering_obt.rst index 0090b88e..e5177fde 100644 --- a/news/scattering_obt.rst +++ b/news/scattering_obt.rst @@ -19,5 +19,3 @@ * **Security:** - -* diff --git a/requirements/test.txt b/requirements/test.txt index 9966e09d..a392bd23 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -6,3 +6,4 @@ pytest-env pytest-mock pytest-cov freezegun +DeepDiff diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 4651273e..c80f15b8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -2,6 +2,7 @@ import numpy as np import pytest +from deepdiff import DeepDiff from freezegun import freeze_time from diffpy.utils.diffraction_objects import DiffractionObject @@ -248,7 +249,7 @@ def test_dump(tmp_path, mocker): ( {}, { - "all_arrays": np.empty(shape=(0, 4)), # instantiate empty + "_all_arrays": np.empty(shape=(0, 4)), # instantiate empty "metadata": {}, "input_xtype": "", "name": "", @@ -265,7 +266,7 @@ def test_dump(tmp_path, mocker): ( # instantiate just non-array attributes {"name": "test", "scat_quantity": "x-ray", "metadata": {"thing": "1", "another": "2"}}, { - "all_arrays": np.empty(shape=(0, 4)), + "_all_arrays": np.empty(shape=(0, 4)), "metadata": {"thing": "1", "another": "2"}, "input_xtype": "", "name": "test", @@ -287,7 +288,7 @@ def test_dump(tmp_path, mocker): "wavelength": 4.0 * np.pi, }, { - "all_arrays": np.array( + "_all_arrays": np.array( [ [1.0, 0.0, 0.0, np.float64(np.inf)], [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], @@ -316,7 +317,7 @@ def test_dump(tmp_path, mocker): "scat_quantity": "x-ray", }, { - "all_arrays": np.array( + "_all_arrays": np.array( [ [1.0, 0.0, 0.0, np.float64(np.inf)], [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], @@ -342,21 +343,26 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize("inputs, expected", tc_params) def test_constructor(inputs, expected): actual_do = DiffractionObject(**inputs) - actual_dict = { - "all_arrays": actual_do.all_arrays, - "metadata": actual_do.metadata, - "input_xtype": actual_do.input_xtype, - "name": actual_do.name, - "scat_quantity": actual_do.scat_quantity, - "qmin": actual_do.qmin, - "qmax": actual_do.qmax, - "tthmin": actual_do.tthmin, - "tthmax": actual_do.tthmax, - "dmin": actual_do.dmin, - "dmax": actual_do.dmax, - "wavelength": actual_do.wavelength, - } - compare_dicts(actual_dict, expected) + diff = DeepDiff(actual_do.__dict__, expected, ignore_order=True, significant_digits=4) + # Ensure there is no difference + assert diff == {} + + +def test_all_array_getter(): + actual_do = DiffractionObject( + xarray=np.array([0.0, 90.0, 180.0]), + yarray=np.array([1.0, 2.0, 3.0]), + xtype="tth", + wavelength=4.0 * np.pi, + ) + expected_all_arrays = np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ) + assert np.array_equal(actual_do.all_arrays, expected_all_arrays) def test_all_array_setter(): From 71fcb6b366320a7a0f91e9c5c3848f5f8d94c40d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 5 Dec 2024 13:41:12 -0500 Subject: [PATCH 104/445] Re-design error msg on how to modify all_arrays --- src/diffpy/utils/diffraction_objects.py | 4 ++-- tests/test_diffraction_objects.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 66b840e5..82ad9cf4 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -188,8 +188,8 @@ def all_arrays(self): @all_arrays.setter def all_arrays(self, value): raise AttributeError( - "Direct modification of 'all_arrays' is not allowed." - "Please use 'insert_scattering_quantity' to modify it." + "Direct modification of attribute 'all_arrays' is not allowed." + "Please use 'insert_scattering_quantity' to modify `all_arrays`." ) def set_angles_from_list(self, angles_list): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index c80f15b8..a484de39 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -369,5 +369,9 @@ def test_all_array_setter(): actual_do = DiffractionObject() # Attempt to directly modify the property - with pytest.raises(AttributeError, match="Direct modification of 'all_arrays' is not allowed."): + with pytest.raises( + AttributeError, + match="Direct modification of attribute 'all_arrays' is not allowed." + "Please use 'insert_scattering_quantity' to modify `all_arrays`.", + ): actual_do.all_arrays = np.empty((4, 4)) From 03eaa877afffa54a16d5dac86d3d2c63be661312 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 5 Dec 2024 14:20:14 -0500 Subject: [PATCH 105/445] initial commit, add docstring and tests --- src/diffpy/utils/transforms.py | 57 ++++++++++++++++++++------ tests/test_transforms.py | 74 +++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 14 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 71e50599..f13632f8 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,6 +16,9 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) +invalid_input_emsg = ( + "Input values have resulted in an infinite output. Please ensure there are no zeros in the input." +) def _validate_inputs(q, wavelength): @@ -58,7 +61,7 @@ def q_to_tth(q, wavelength): ------- tth : 1D array The array of :math:`2\theta` values in degrees numpy.array([tths]). - This is the correct format for loading into diffpy.utils.DiffractionOject.on_tth + This is the correct format for loading into diffpy.utils.DiffractionObject.on_tth. """ _validate_inputs(q, wavelength) q.astype(float) @@ -92,9 +95,8 @@ def tth_to_q(tth, wavelength): Parameters ---------- - tth : 2D array - The array of :math:`2\theta` values and :math: 'i' intensity values, np.array([[tths], [is]]). - This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_tth + tth : 1D array + The array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. wavelength : float @@ -102,11 +104,10 @@ def tth_to_q(tth, wavelength): Returns ------- - on_q : 2D array - The array of :math:`q` values and :math: 'i' intensity values unchanged, - np.array([[qs], [is]]). + q : 1D array + The array of :math:`q` values np.array([qs]). The units for the q-values are the inverse of the units of the provided wavelength. - This is the correct format for loading into diffpy.utils.DiffractionOject.on_q + This is the correct format for loading into diffpy.utils.DiffractionObject.on_q. """ tth.astype(float) if np.any(np.deg2rad(tth) > np.pi): @@ -121,8 +122,24 @@ def tth_to_q(tth, wavelength): return q -def q_to_d(qarray): - return 2.0 * np.pi / copy(qarray) +def q_to_d(q): + r""" + Helper function to convert q to d on independent variable axis, using :math:`d = \frac{2 \pi}{q}`. + + Parameters + ---------- + q : 1D array + The array of :math:`q` values np.array([qs]). + The units of q must be reciprocal of the units of wavelength. + + Returns + ------- + d : 1D array + The array of :math:`d` values np.array([ds]). + """ + if 0 in q: + raise ValueError(invalid_input_emsg) + return 2.0 * np.pi / copy(q)[::-1] def tth_to_d(ttharray, wavelength): @@ -130,8 +147,24 @@ def tth_to_d(ttharray, wavelength): return 2.0 * np.pi / copy(qarray) -def d_to_q(darray): - return 2.0 * np.pi / copy(darray) +def d_to_q(d): + r""" + Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`. + + Parameters + ---------- + d : 1D array + The array of :math:`d` values np.array([ds]). + + Returns + ------- + q : 1D array + The array of :math:`q` values np.array([qs]). + The units of q must be reciprocal of the units of wavelength. + """ + if 0 in d: + raise ValueError(invalid_input_emsg) + return 2.0 * np.pi / copy(d)[::-1] def d_to_tth(darray, wavelength): diff --git a/tests/test_transforms.py b/tests/test_transforms.py index e8b15492..db35d1ef 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from diffpy.utils.transforms import q_to_tth, tth_to_q +from diffpy.utils.transforms import d_to_q, q_to_d, q_to_tth, tth_to_q params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays @@ -64,7 +64,7 @@ def test_q_to_tth_bad(inputs, expected): np.array([0, 1, 2, 3, 4, 5]), ), # UC3: User specified valid tth values between 0-180 degrees (with wavelength) - # expected q vales are sin15, sin30, sin45, sin60, sin90 + # expected q values are sin15, sin30, sin45, sin60, sin90 ( [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), @@ -96,3 +96,73 @@ def test_tth_to_q(inputs, expected): def test_tth_to_q_bad(inputs, expected): with pytest.raises(expected[0], match=expected[1]): tth_to_q(inputs[1], inputs[0]) + + +params_q_to_d = [ + # UC1: User specified empty q values + ([np.array([])], np.array([])), + # UC2: User specified valid q values + ( + [np.array([np.pi / 6, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], + np.array([0.4, 0.5, 0.66667, 1, 2, 12]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_d) +def test_q_to_d(inputs, expected): + actual = q_to_d(inputs[0]) + assert np.allclose(actual, expected) + + +params_q_to_d_bad = [ + # UC1: user specified an invalid q value that results in an infinite d value + ( + [np.array([0, 1, 2, 3, 4])], + [ + ValueError, + "Input values have resulted in an infinite output. Please ensure there are no zeros in the input.", + ], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_d_bad) +def test_q_to_d_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + q_to_d(inputs[0]) + + +params_d_to_q = [ + # UC1: User specified empty d values + ([np.array([])], np.array([])), + # UC2: User specified valid d values + ( + [np.array([np.pi / 6, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], + np.array([0.4, 0.5, 0.66667, 1, 2, 12]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_d_to_q) +def test_d_to_q(inputs, expected): + actual = d_to_q(inputs[0]) + assert np.allclose(actual, expected) + + +params_d_to_q_bad = [ + # UC1: user specified an invalid d value that results in an infinite q value + ( + [np.array([0, 1, 2, 3, 4])], + [ + ValueError, + "Input values have resulted in an infinite output. Please ensure there are no zeros in the input.", + ], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_d_to_q_bad) +def test_d_to_q_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + d_to_q(inputs[0]) From f635f4d8b9c89709ca2728327aa1e5b3e77bae21 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 5 Dec 2024 16:26:51 -0500 Subject: [PATCH 106/445] Add sig fig to 13 and remove comment not needed --- tests/test_diffraction_objects.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index a484de39..f0c6c2be 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -343,8 +343,7 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize("inputs, expected", tc_params) def test_constructor(inputs, expected): actual_do = DiffractionObject(**inputs) - diff = DeepDiff(actual_do.__dict__, expected, ignore_order=True, significant_digits=4) - # Ensure there is no difference + diff = DeepDiff(actual_do.__dict__, expected, ignore_order=True, significant_digits=13) assert diff == {} From b7fa23e8c850f52421890b5394a21c3f1dd01a08 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 5 Dec 2024 16:30:45 -0500 Subject: [PATCH 107/445] Use all close for comparing expected all_array value --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index f0c6c2be..b7d8eae3 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -361,7 +361,7 @@ def test_all_array_getter(): [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], ] ) - assert np.array_equal(actual_do.all_arrays, expected_all_arrays) + assert np.allclose(actual_do.all_arrays, expected_all_arrays) def test_all_array_setter(): From ba889ece9f9633e9a0d7f9fed108ab9427e5eb84 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 5 Dec 2024 16:33:49 -0500 Subject: [PATCH 108/445] Reset scattering_obj news --- news/scattering_obt.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/news/scattering_obt.rst b/news/scattering_obt.rst index e5177fde..e1f0cf53 100644 --- a/news/scattering_obt.rst +++ b/news/scattering_obt.rst @@ -19,3 +19,5 @@ * **Security:** + +* From d20d2bcd97600831f991fea80d7b3a3bb59926bd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:33:58 +0000 Subject: [PATCH 109/445] [pre-commit.ci] auto fixes from pre-commit hooks --- news/scattering_obt.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/scattering_obt.rst b/news/scattering_obt.rst index e1f0cf53..0090b88e 100644 --- a/news/scattering_obt.rst +++ b/news/scattering_obt.rst @@ -20,4 +20,4 @@ **Security:** -* +* From 76d89755d006ec785872b63baf7790a58155e0f0 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 10:49:33 -0500 Subject: [PATCH 110/445] change value error to warning msg --- src/diffpy/utils/transforms.py | 14 ++++------- tests/test_transforms.py | 44 ++++------------------------------ 2 files changed, 9 insertions(+), 49 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index f13632f8..b4a74bf7 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,9 +16,7 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -invalid_input_emsg = ( - "Input values have resulted in an infinite output. Please ensure there are no zeros in the input." -) +inf_output_msg = "WARNING: The largest output is infinite and cannot be plotted." def _validate_inputs(q, wavelength): @@ -61,7 +59,6 @@ def q_to_tth(q, wavelength): ------- tth : 1D array The array of :math:`2\theta` values in degrees numpy.array([tths]). - This is the correct format for loading into diffpy.utils.DiffractionObject.on_tth. """ _validate_inputs(q, wavelength) q.astype(float) @@ -107,7 +104,6 @@ def tth_to_q(tth, wavelength): q : 1D array The array of :math:`q` values np.array([qs]). The units for the q-values are the inverse of the units of the provided wavelength. - This is the correct format for loading into diffpy.utils.DiffractionObject.on_q. """ tth.astype(float) if np.any(np.deg2rad(tth) > np.pi): @@ -138,8 +134,8 @@ def q_to_d(q): The array of :math:`d` values np.array([ds]). """ if 0 in q: - raise ValueError(invalid_input_emsg) - return 2.0 * np.pi / copy(q)[::-1] + warnings.warn(inf_output_msg) + return 2.0 * np.pi / copy(q) def tth_to_d(ttharray, wavelength): @@ -163,8 +159,8 @@ def d_to_q(d): The units of q must be reciprocal of the units of wavelength. """ if 0 in d: - raise ValueError(invalid_input_emsg) - return 2.0 * np.pi / copy(d)[::-1] + warnings.warn(inf_output_msg) + return 2.0 * np.pi / copy(d) def d_to_tth(darray, wavelength): diff --git a/tests/test_transforms.py b/tests/test_transforms.py index db35d1ef..6c2e4c6e 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -103,8 +103,8 @@ def test_tth_to_q_bad(inputs, expected): ([np.array([])], np.array([])), # UC2: User specified valid q values ( - [np.array([np.pi / 6, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], - np.array([0.4, 0.5, 0.66667, 1, 2, 12]), + [np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0])], + np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), ), ] @@ -115,31 +115,13 @@ def test_q_to_d(inputs, expected): assert np.allclose(actual, expected) -params_q_to_d_bad = [ - # UC1: user specified an invalid q value that results in an infinite d value - ( - [np.array([0, 1, 2, 3, 4])], - [ - ValueError, - "Input values have resulted in an infinite output. Please ensure there are no zeros in the input.", - ], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_q_to_d_bad) -def test_q_to_d_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - q_to_d(inputs[0]) - - params_d_to_q = [ # UC1: User specified empty d values ([np.array([])], np.array([])), # UC2: User specified valid d values ( - [np.array([np.pi / 6, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], - np.array([0.4, 0.5, 0.66667, 1, 2, 12]), + [np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], + np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), ), ] @@ -148,21 +130,3 @@ def test_q_to_d_bad(inputs, expected): def test_d_to_q(inputs, expected): actual = d_to_q(inputs[0]) assert np.allclose(actual, expected) - - -params_d_to_q_bad = [ - # UC1: user specified an invalid d value that results in an infinite q value - ( - [np.array([0, 1, 2, 3, 4])], - [ - ValueError, - "Input values have resulted in an infinite output. Please ensure there are no zeros in the input.", - ], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_d_to_q_bad) -def test_d_to_q_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - d_to_q(inputs[0]) From 1199a4853e06a43e149f1e5a5607ee6d958832bd Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 14:16:47 -0500 Subject: [PATCH 111/445] initial commit --- src/diffpy/utils/transforms.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 71e50599..742de2a3 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,6 +16,7 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) +inf_output_wmsg = "WARNING: The largest output is infinite and cannot be plotted." def _validate_inputs(q, wavelength): @@ -125,8 +126,30 @@ def q_to_d(qarray): return 2.0 * np.pi / copy(qarray) -def tth_to_d(ttharray, wavelength): - qarray = tth_to_q(ttharray, wavelength) +def tth_to_d(tth, wavelength): + r""" + Helper function to convert two-theta to d on independent variable axis. + + Uses the formula .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. + + Parameters + ---------- + tth : 1D array + The array of :math:`2\theta` values np.array([tths]). + The units of tth are expected in degrees. + + wavelength : float + Wavelength of the incoming x-rays/neutrons/electrons + + Returns + ------- + d : 1D array + The array of :math:`d` values np.array([ds]). + """ + tth = np.deg2rad(tth) + if 0 in tth: + warnings.warn(inf_output_wmsg) + qarray = tth_to_q(tth, wavelength) return 2.0 * np.pi / copy(qarray) From ccea574ba34ae69179c38ade63026d22bfc7321a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 14:20:28 -0500 Subject: [PATCH 112/445] better name for the warning msg --- src/diffpy/utils/transforms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index b4a74bf7..bc6416ea 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,7 +16,7 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_msg = "WARNING: The largest output is infinite and cannot be plotted." +inf_output_wmsg = "WARNING: The largest output is infinite and cannot be plotted." def _validate_inputs(q, wavelength): @@ -134,7 +134,7 @@ def q_to_d(q): The array of :math:`d` values np.array([ds]). """ if 0 in q: - warnings.warn(inf_output_msg) + warnings.warn(inf_output_wmsg) return 2.0 * np.pi / copy(q) @@ -159,7 +159,7 @@ def d_to_q(d): The units of q must be reciprocal of the units of wavelength. """ if 0 in d: - warnings.warn(inf_output_msg) + warnings.warn(inf_output_wmsg) return 2.0 * np.pi / copy(d) From 6f29b6ec2fba895a8124443cdff0580d6227add5 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:06:10 -0500 Subject: [PATCH 113/445] initial commit --- src/diffpy/utils/transforms.py | 50 ++++++++++++---- tests/test_transforms.py | 102 ++++++++++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 14 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 742de2a3..d329f607 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -12,8 +12,8 @@ "and number is the wavelength in angstroms." ) invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." -invalid_q_or_wavelength_emsg = ( - "The supplied q-array and wavelength will result in an impossible two-theta. " +invalid_q_or_d_or_wavelength_emsg = ( + "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) inf_output_wmsg = "WARNING: The largest output is infinite and cannot be plotted." @@ -25,7 +25,7 @@ def _validate_inputs(q, wavelength): return np.empty(0) pre_factor = wavelength / (4 * np.pi) if np.any(np.abs(q * pre_factor) > 1.0): - raise ValueError(invalid_q_or_wavelength_emsg) + raise ValueError(invalid_q_or_d_or_wavelength_emsg) def q_to_tth(q, wavelength): @@ -130,7 +130,9 @@ def tth_to_d(tth, wavelength): r""" Helper function to convert two-theta to d on independent variable axis. - Uses the formula .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. + The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. + + Here we convert tth to q first, then to d. Parameters ---------- @@ -146,17 +148,43 @@ def tth_to_d(tth, wavelength): d : 1D array The array of :math:`d` values np.array([ds]). """ - tth = np.deg2rad(tth) - if 0 in tth: + q = tth_to_q(tth, wavelength) + d = copy(tth) + if wavelength is None: + warnings.warn(wavelength_warning_emsg, UserWarning) + for i, _ in enumerate(tth): + d[i] = i + return d + if 0 in q: warnings.warn(inf_output_wmsg) - qarray = tth_to_q(tth, wavelength) - return 2.0 * np.pi / copy(qarray) + return 2.0 * np.pi / copy(q) def d_to_q(darray): return 2.0 * np.pi / copy(darray) -def d_to_tth(darray, wavelength): - qarray = d_to_q(darray) - return q_to_tth(qarray, wavelength) +def d_to_tth(d, wavelength): + r""" + Helper function to convert d to two-theta on independent variable axis. + + The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). + + Here we convert d to q first, then to tth. + + Parameters + ---------- + d : 1D array + The array of :math:`d` values np.array([ds]). + + wavelength : float + Wavelength of the incoming x-rays/neutrons/electrons + + Returns + ------- + tth : 1D array + The array of :math:`2\theta` values np.array([tths]). + The units of tth are expected in degrees. + """ + q = d_to_q(d) + return q_to_tth(q, wavelength) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index e8b15492..7141ba02 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from diffpy.utils.transforms import q_to_tth, tth_to_q +from diffpy.utils.transforms import d_to_tth, q_to_tth, tth_to_d, tth_to_q params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays @@ -31,7 +31,7 @@ def test_q_to_tth(inputs, expected): [4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])], [ ValueError, - "The supplied q-array and wavelength will result in an impossible two-theta. " + "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values.", ], ), @@ -40,7 +40,7 @@ def test_q_to_tth(inputs, expected): [100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], [ ValueError, - "The supplied q-array and wavelength will result in an impossible two-theta. " + "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values.", ], ), @@ -96,3 +96,99 @@ def test_tth_to_q(inputs, expected): def test_tth_to_q_bad(inputs, expected): with pytest.raises(expected[0], match=expected[1]): tth_to_q(inputs[1], inputs[0]) + + +params_tth_to_d = [ + # UC0: User specified empty tth values (without wavelength) + ([None, np.array([])], np.array([])), + # UC1: User specified empty tth values (with wavelength) + ([4 * np.pi, np.array([])], np.array([])), + # UC2: User specified valid tth values between 0-180 degrees (without wavelength) + ( + [None, np.array([0, 30, 60, 90, 120, 180])], + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC3: User specified valid tth values between 0-180 degrees (with wavelength) + ( + [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], + np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_d) +def test_tth_to_d(inputs, expected): + actual = tth_to_d(inputs[1], inputs[0]) + assert np.allclose(actual, expected) + + +params_tth_to_d_bad = [ + # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) + ( + [None, np.array([0, 30, 60, 90, 120, 181])], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), + # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) + ( + [4 * np.pi, np.array([0, 30, 60, 90, 120, 181])], + [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_tth_to_d_bad) +def test_tth_to_d_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + tth_to_d(inputs[1], inputs[0]) + + +params_d_to_tth = [ + # UC1: Empty d values, no wavelength, return empty arrays + ([None, np.empty((0))], np.empty((0))), + # UC2: Empty d values, wavelength specified, return empty arrays + ([4 * np.pi, np.empty((0))], np.empty(0)), + # UC3: User specified valid d values, no wavelength, return empty arrays + ( + [None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC4: User specified valid d values (with wavelength) + ( + [4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi])], + np.array([60.0, 90.0, 120.0]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_d_to_tth) +def test_d_to_tth(inputs, expected): + actual = d_to_tth(inputs[1], inputs[0]) + assert np.allclose(expected, actual) + + +params_d_to_tth_bad = [ + # UC1: user specified invalid d values that result in tth > 180 degrees + ( + [4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])], + [ + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", + ], + ), + # UC2: user specified a wrong wavelength that result in tth > 180 degrees + ( + [100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + [ + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", + ], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_d_to_tth_bad) +def test_d_to_tth_bad(inputs, expected): + with pytest.raises(expected[0], match=expected[1]): + d_to_tth(inputs[1], inputs[0]) From 5c3fa5c1d9680059f27bc42e9c500294461bcabf Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:10:24 -0500 Subject: [PATCH 114/445] edit test cases --- tests/test_transforms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 7141ba02..34652775 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -149,7 +149,7 @@ def test_tth_to_d_bad(inputs, expected): ([4 * np.pi, np.empty((0))], np.empty(0)), # UC3: User specified valid d values, no wavelength, return empty arrays ( - [None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + [None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])], np.array([0, 1, 2, 3, 4, 5]), ), # UC4: User specified valid d values (with wavelength) @@ -169,7 +169,7 @@ def test_d_to_tth(inputs, expected): params_d_to_tth_bad = [ # UC1: user specified invalid d values that result in tth > 180 degrees ( - [4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])], + [4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2])], [ ValueError, "The supplied input array and wavelength will result in an impossible two-theta. " @@ -178,7 +178,7 @@ def test_d_to_tth(inputs, expected): ), # UC2: user specified a wrong wavelength that result in tth > 180 degrees ( - [100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + [100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])], [ ValueError, "The supplied input array and wavelength will result in an impossible two-theta. " From 25db4e7bc16eca717a5904793ad0b0e6991feaf8 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:11:23 -0500 Subject: [PATCH 115/445] add news --- news/d-tth.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/d-tth.rst diff --git a/news/d-tth.rst b/news/d-tth.rst new file mode 100644 index 00000000..2bfc091f --- /dev/null +++ b/news/d-tth.rst @@ -0,0 +1,23 @@ +**Added:** + +* functions to convert between d and tth + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 79399a20d42b92ab40061896670f322250db74c9 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:14:53 -0500 Subject: [PATCH 116/445] add warning message about wavelength in tth_to_q --- src/diffpy/utils/transforms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index bc6416ea..92689284 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -113,6 +113,7 @@ def tth_to_q(tth, wavelength): pre_factor = (4.0 * np.pi) / wavelength q = pre_factor * np.sin(np.deg2rad(tth / 2)) else: # return intensities vs. an x-array that is just the index + warnings.warn(wavelength_warning_emsg, UserWarning) for i, _ in enumerate(q): q[i] = i return q From 664df765d7bea7c5288ee357974177868b803a6b Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:15:26 -0500 Subject: [PATCH 117/445] add news --- news/d-q.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/d-q.rst diff --git a/news/d-q.rst b/news/d-q.rst new file mode 100644 index 00000000..92105f07 --- /dev/null +++ b/news/d-q.rst @@ -0,0 +1,23 @@ +**Added:** + +* functions to convert between d and q + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 2e1853af96d0dd2b13d5bd3e89df12afade2fd0f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:35:33 -0500 Subject: [PATCH 118/445] edit docstring --- src/diffpy/utils/diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index b1ed5a78..d8e86f58 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -377,7 +377,7 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): def on_xtype(self, xtype): f""" - return a 2D np array with x in the first column and y in the second for x of type type + return a list of two 1D np array with x and y data, raise an error if the specified xtype is invalid Parameters ---------- @@ -386,7 +386,7 @@ def on_xtype(self, xtype): Returns ------- - a 2D np array with x and y data + a list of two 1D np array with x and y data """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth() From 3dac06a06cdae0f58d75ad6f936979dd25a82403 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 17:42:48 -0500 Subject: [PATCH 119/445] simplify tests --- news/array_index.rst | 23 +++++++++++++ src/diffpy/utils/diffraction_objects.py | 20 ++--------- tests/test_diffraction_objects.py | 46 +++---------------------- 3 files changed, 30 insertions(+), 59 deletions(-) create mode 100644 news/array_index.rst diff --git a/news/array_index.rst b/news/array_index.rst new file mode 100644 index 00000000..6f687373 --- /dev/null +++ b/news/array_index.rst @@ -0,0 +1,23 @@ +**Added:** + +* function to return the index of the closest value to the specified value in an array. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 3a0f88bc..48fffa7f 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -266,26 +266,10 @@ def get_array_index(self, value, xtype=None): if xtype is None: xtype = self.input_xtype - if self.on_xtype(xtype) is None or len(self.on_xtype(xtype)[0]) == 0: - raise ValueError( - f"The '{xtype}' array is empty. " "Please ensure it is initialized and the correct xtype is used." - ) 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() - nearest_value = np.abs(array[i] - value) - distance = min(np.abs(value - array.min()), np.abs(value - array.max())) - threshold = 0.5 * (array.max() - array.min()) - - if nearest_value != 0 and (array.min() <= value <= array.max() or distance <= threshold): - warnings.warn( - f"WARNING: The value {value} is not an exact match of the '{xtype}' array. " - f"Returning the index of the closest value." - ) - elif distance > threshold: - raise IndexError( - f"The value {value} is too far from any value in the '{xtype}' array. " - f"Please check if you have specified the correct xtype. " - ) return i def _set_xarrays(self, xarray, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 1bf3a459..6fa81d3e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -218,7 +218,7 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte # UC2: target value lies in the array, returns the (first) closest index ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45], [0]), ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25], [0]), - # UC3: target value out of the range but within reasonable distance, returns the closest index + # UC3: target value out of the range, returns the closest index ([4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1], [0]), ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63], [1]), ] @@ -231,46 +231,10 @@ def test_get_array_index(inputs, expected): assert actual == expected[0] -params_index_bad = [ - # UC0: empty array - ( - [2 * np.pi, np.array([]), np.array([]), "tth", "tth", 30], - [ValueError, "The 'tth' array is empty. Please ensure it is initialized and the correct xtype is used."], - ), - # UC1: empty array (because of invalid xtype) - ( - [2 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "invalid", 30], - [ - ValueError, - "The 'invalid' array is empty. Please ensure it is initialized and the correct xtype is used.", - ], - ), - # UC3: value is too far from any element in the array - ( - [2 * np.pi, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "tth", 140], - [ - IndexError, - "The value 140 is too far from any value in the 'tth' array. " - "Please check if you have specified the correct xtype.", - ], - ), - # UC4: value is too far from any element in the array (because of wrong xtype) - ( - [2 * np.pi, np.array([30, 60, 90]), np.array([1, 2, 3]), "tth", "q", 30], - [ - IndexError, - "The value 30 is too far from any value in the 'q' array. " - "Please check if you have specified the correct xtype.", - ], - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_index_bad) -def test_get_array_index_bad(inputs, expected): - test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3]) - with pytest.raises(expected[0], match=re.escape(expected[1])): - test.get_array_index(value=inputs[5], xtype=inputs[4]) +def test_get_array_index_bad(): + test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth") + with pytest.raises(ValueError, match=re.escape("The 'tth' array is empty. Please ensure it is initialized.")): + test.get_array_index(value=30) def test_dump(tmp_path, mocker): From 6295aa5aa83df68357b961d2c70f12f49602d4c8 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 6 Dec 2024 19:49:10 -0500 Subject: [PATCH 120/445] small tweak to infinite d message --- src/diffpy/utils/transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 92689284..a762529a 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,7 +16,7 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_wmsg = "WARNING: The largest output is infinite and cannot be plotted." +inf_output_wmsg = "INFO: The largest d-value in the array is infinite. This is allowed, but it will not be plotted." def _validate_inputs(q, wavelength): @@ -135,7 +135,7 @@ def q_to_d(q): The array of :math:`d` values np.array([ds]). """ if 0 in q: - warnings.warn(inf_output_wmsg) + print(inf_output_wmsg) return 2.0 * np.pi / copy(q) From 39f1c542a854fcf2e462a7190f833efc0ff87d12 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 7 Dec 2024 00:49:19 +0000 Subject: [PATCH 121/445] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/transforms.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index a762529a..0d02fe54 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,7 +16,9 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_wmsg = "INFO: The largest d-value in the array is infinite. This is allowed, but it will not be plotted." +inf_output_wmsg = ( + "INFO: The largest d-value in the array is infinite. This is allowed, but it will not be plotted." +) def _validate_inputs(q, wavelength): From 7eb0c0d237392553b79530de63b778b8bce19b3d Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 6 Dec 2024 20:11:28 -0500 Subject: [PATCH 122/445] remove warning in error msg --- src/diffpy/utils/diffraction_objects.py | 2 +- tests/test_diffraction_objects.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index d8e86f58..703baf03 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -21,7 +21,7 @@ def _xtype_wmsg(xtype): return ( - f"WARNING: I don't know how to handle the xtype, '{xtype}'. Please rerun specifying an " + f"I don't know how to handle the xtype, '{xtype}'. Please rerun specifying an " f"xtype from {*XQUANTITIES, }" ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 95848111..2371838f 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -215,7 +215,7 @@ def test_on_xtype_bad(): with pytest.raises( ValueError, match=re.escape( - f"WARNING: I don't know how to handle the xtype, 'invalid'. Please rerun specifying an " + f"I don't know how to handle the xtype, 'invalid'. Please rerun specifying an " f"xtype from {*XQUANTITIES, }" ), ): From 3588f571b9accc239f8c43e4fec84428d751e8e3 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 16:24:10 -0500 Subject: [PATCH 123/445] Add New release checklist BG standard: python -m build and twine check dist/* --- .github/ISSUE_TEMPLATE/release_checklist.md | 24 +++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index ba1d40ce..11df804d 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -12,29 +12,31 @@ assignees: "" - [ ] All the badges on the README are passing. - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are - missing), tutorials, and other human written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation and on the website (e.g., diffpy.org) are updated. + missing), tutorials, and other human-written text is up-to-date with any changes in the code. +- [ ] Installation instructions in the README, documentation, and the website (e.g., diffpy.org) are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). +- [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: -### PyPI/GitHub full-release preparation checklist: -- [ ] Create a new conda environment and install the rc from pypi (`pip install =??`) -- [ ] License information at Pypi is verified as correct. -- [ ] Docs deployed successfully to `.github.io` -- [ ] Successfully run all tests, tutorial examples or do functional testing +### PyPI/GitHub full-release preparation checklist: -Please let @sbillinge know that all checks are done and package is ready for full release. +- [ ] Create a new conda environment and install the rc from PyPI (`pip install ==??`) +- [ ] License information on PyPI is correct. +- [ ] Docs are deployed successfully to `https://www.diffpy.org/`. +- [ ] Successfully run all tests, tutorial examples or do functional testing. + +Please let @sbillinge know that all checks are done and the package is ready for full release. ### conda-forge release preparation checklist: -- [ ] Ensure that the full release has appeared on Pypi successfully +- [ ] Ensure that the full release has appeared on PyPI successfully. - [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. -- [ ] Close any open issues on the feedstock. Reach out to @bobleesj if you have questions -- [ ] let @sbillinge and @bobleesj when this is ready +- [ ] Close any open issues on the feedstock. Reach out to @bobleesj if you have questions. +- [ ] Tag @sbillinge and @bobleesj for conda-forge release. ### Post-release checklist From 6c5d79b9d2161732cba5262afb70484b07927634 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 16:44:41 -0500 Subject: [PATCH 124/445] Add copy method for instance --- src/diffpy/utils/diffraction_objects.py | 11 +++++++++++ tests/test_diffraction_objects.py | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6ce965d0..888f83c9 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -445,3 +445,14 @@ def dump(self, filepath, xtype=None): f.write(f"{key} = {value}\n") f.write("\n#### start data\n") np.savetxt(f, data_to_save, delimiter=" ") + + def copy(self): + """ + Create a deep copy of the DiffractionObject instance. + + Returns + ------- + DiffractionObject + A new instance of DiffractionObject, which is a deep copy of the current instance. + """ + return deepcopy(self) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aeba1522..b65521cd 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -410,3 +410,15 @@ def test_all_array_setter(): "Please use 'insert_scattering_quantity' to modify `all_arrays`.", ): actual_do.all_arrays = np.empty((4, 4)) + + +def test_copy_object(): + do = DiffractionObject( + name="test", + wavelength=4.0 * np.pi, + xarray=np.array([0.0, 90.0, 180.0]), + yarray=np.array([1.0, 2.0, 3.0]), + xtype="tth", + ) + copy_of_DO = do.copy() + assert do == copy_of_DO From 005a19168e3662a8c0cd4681b20834b72c58710a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 16:44:46 -0500 Subject: [PATCH 125/445] Add news --- news/deepcopy.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/deepcopy.rst diff --git a/news/deepcopy.rst b/news/deepcopy.rst new file mode 100644 index 00000000..578d360c --- /dev/null +++ b/news/deepcopy.rst @@ -0,0 +1,23 @@ +**Added:** + +* copy() method for DiffractionObject instance + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 33ce9b3393e687282bf7e6652d4228e3a880257c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 16:58:45 -0500 Subject: [PATCH 126/445] Add one more test for different memory pointed by copied obj --- tests/test_diffraction_objects.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index b65521cd..dcb99ac6 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -422,3 +422,5 @@ def test_copy_object(): ) copy_of_DO = do.copy() assert do == copy_of_DO + assert id(do) != id(copy_of_DO) + From edfc415d107e468e3f4dcb1c70aff4c1b663595a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 7 Dec 2024 21:58:52 +0000 Subject: [PATCH 127/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index dcb99ac6..81f6ce8c 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -423,4 +423,3 @@ def test_copy_object(): copy_of_DO = do.copy() assert do == copy_of_DO assert id(do) != id(copy_of_DO) - From 876c15ee41c164e91949fbc0f75530cd7bce3d88 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 17:00:38 -0500 Subject: [PATCH 128/445] enhance variable name --- tests/test_diffraction_objects.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index dcb99ac6..dcdd4f59 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -420,7 +420,6 @@ def test_copy_object(): yarray=np.array([1.0, 2.0, 3.0]), xtype="tth", ) - copy_of_DO = do.copy() - assert do == copy_of_DO - assert id(do) != id(copy_of_DO) - + do_copy = do.copy() + assert do == do_copy + assert id(do) != id(do_copy) From a6ddd5b29340a7f72930488246ada44d605fce24 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Sat, 7 Dec 2024 17:56:29 -0500 Subject: [PATCH 129/445] more info on global config for users --- news/config-UX.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/tools.py | 4 ++++ 2 files changed, 27 insertions(+) create mode 100644 news/config-UX.rst diff --git a/news/config-UX.rst b/news/config-UX.rst new file mode 100644 index 00000000..05f66a10 --- /dev/null +++ b/news/config-UX.rst @@ -0,0 +1,23 @@ +**Added:** + +* additional information to users to relieve frustration in finding how to update global config + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 82cca8c9..728d8601 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -83,6 +83,10 @@ def _create_global_config(args): return_bool = False if username is None or email is None else True with open(Path().home() / "diffpyconfig.json", "w") as f: f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) + print( + f"You can manually edit the config file at {Path().home() / 'diffpyconfig.json'} using any text editor.\n" + f"Or you can update the config file by passing new values to get_user_info(), " + f"see examples here: https://www.diffpy.org/diffpy.utils/examples/toolsexample.html") return return_bool From 15d14f520597a195910aec5ea09c6ebd7109348a Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Sat, 7 Dec 2024 17:58:19 -0500 Subject: [PATCH 130/445] pre-commit fix --- src/diffpy/utils/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 728d8601..7e5e858f 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -86,7 +86,8 @@ def _create_global_config(args): print( f"You can manually edit the config file at {Path().home() / 'diffpyconfig.json'} using any text editor.\n" f"Or you can update the config file by passing new values to get_user_info(), " - f"see examples here: https://www.diffpy.org/diffpy.utils/examples/toolsexample.html") + f"see examples here: https://www.diffpy.org/diffpy.utils/examples/toolsexample.html" + ) return return_bool From ee190b9b6c271937f823ed1821a265f6dbcfa4d1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 20:41:19 -0500 Subject: [PATCH 131/445] valid xtype and use getter/setting for xtype --- src/diffpy/utils/diffraction_objects.py | 48 +++++++++++++++++++------ 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6ce965d0..6316f2a2 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -26,6 +26,13 @@ def _xtype_wmsg(xtype): ) +def _setter_wmsg(attribute): + return ( + f"Direct modification of attribute '{attribute}' is not allowed." + f"Please use 'insert_scattering_quantity' to modify '{attribute}'.", + ) + + class DiffractionObject: def __init__( self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype=None @@ -45,6 +52,7 @@ def __init__( xarray = np.empty(0) if yarray is None: yarray = np.empty(0) + self.insert_scattering_quantity(xarray, yarray, xtype) def __eq__(self, other): @@ -186,11 +194,16 @@ def all_arrays(self): return self._all_arrays @all_arrays.setter - def all_arrays(self, value): - raise AttributeError( - "Direct modification of attribute 'all_arrays' is not allowed." - "Please use 'insert_scattering_quantity' to modify `all_arrays`." - ) + def all_arrays(self, _): + raise AttributeError(_setter_wmsg("all_arrays")) + + @property + def xtype(self): + return self._xtype + + @xtype.setter + def xtype(self, _): + raise AttributeError(_setter_wmsg("xtype")) def set_angles_from_list(self, angles_list): self.angles = angles_list @@ -261,7 +274,7 @@ def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): def get_array_index(self, value, xtype=None): """ - returns the index of the closest value in the array associated with the specified xtype + Return the index of the closest value in the array associated with the specified xtype. Parameters ---------- @@ -276,7 +289,7 @@ def get_array_index(self, value, xtype=None): """ if xtype is None: - xtype = self.input_xtype + xtype = self._xtype array = self.on_xtype(xtype)[0] if len(array) == 0: raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.") @@ -335,7 +348,7 @@ def insert_scattering_quantity( """ self._set_xarrays(xarray, xtype) self._all_arrays[:, 0] = yarray - self.input_xtype = xtype + self._xtype = xtype # only update these optional values if non-empty quantities are passed to avoid overwriting # valid data inadvertently if metadata: @@ -347,12 +360,25 @@ def insert_scattering_quantity( if wavelength is not None: self.wavelength = wavelength + # 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 `insert_scattering_quantity` " + "with `xarray` and `yarray` of identical length." + ) + + # 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: + if self._xtype in QQUANTITIES: return self.on_q(), "q" - elif self.input_xtype in ANGLEQUANTITIES: + elif self._xtype in ANGLEQUANTITIES: return self.on_tth(), "tth" - elif self.input_xtype in DQUANTITIES: + elif self._xtype in DQUANTITIES: return self.on_d(), "d" def on_q(self): From c119b1f5fc6702ab95fc4fe4b60706e4fb201ab5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 20:41:26 -0500 Subject: [PATCH 132/445] Add tests for getter and setter --- tests/test_diffraction_objects.py | 34 +++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aeba1522..141a429a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -211,16 +211,15 @@ def test_on_xtype(): assert np.allclose(test.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) -def test_on_xtype_bad(): - test = DiffractionObject() +def test_init_invalid_xtype(): with pytest.raises( ValueError, match=re.escape( - f"I don't know how to handle the xtype, 'invalid'. Please rerun specifying an " + f"I don't know how to handle the xtype, 'invalid_type'. Please rerun specifying an " f"xtype from {*XQUANTITIES, }" ), ): - test.on_xtype("invalid") + DiffractionObject(xtype="invalid_type") params_index = [ @@ -287,7 +286,7 @@ def test_dump(tmp_path, mocker): { "_all_arrays": np.empty(shape=(0, 4)), # instantiate empty "metadata": {}, - "input_xtype": "", + "_xtype": "", "name": "", "scat_quantity": None, "qmin": np.float64(np.inf), @@ -304,7 +303,7 @@ def test_dump(tmp_path, mocker): { "_all_arrays": np.empty(shape=(0, 4)), "metadata": {"thing": "1", "another": "2"}, - "input_xtype": "", + "_xtype": "", "name": "test", "scat_quantity": "x-ray", "qmin": np.float64(np.inf), @@ -332,7 +331,7 @@ def test_dump(tmp_path, mocker): ] ), "metadata": {}, - "input_xtype": "tth", + "_xtype": "tth", "name": "", "scat_quantity": None, "qmin": np.float64(0.0), @@ -361,7 +360,7 @@ def test_dump(tmp_path, mocker): ] ), "metadata": {}, - "input_xtype": "d", + "_xtype": "d", "name": "", "scat_quantity": "x-ray", "qmin": np.float64(0.0), @@ -407,6 +406,23 @@ def test_all_array_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'all_arrays' is not allowed." - "Please use 'insert_scattering_quantity' to modify `all_arrays`.", + "Please use 'insert_scattering_quantity' to modify 'all_arrays'.", ): actual_do.all_arrays = np.empty((4, 4)) + + +def test_xtype_getter(): + do = DiffractionObject(xtype="tth") + assert do.xtype == "tth" + + +def test_xtype_setter(): + do = DiffractionObject(xtype="tth") + + # Attempt to directly modify the property + with pytest.raises( + AttributeError, + match="Direct modification of attribute 'xtype' is not allowed." + "Please use 'insert_scattering_quantity' to modify 'xtype'.", + ): + do.xtype = "q" From 8e993a3db6a9e1555096266ed614ec1fab880598 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 7 Dec 2024 20:41:33 -0500 Subject: [PATCH 133/445] Add news --- news/scattering-obj-valid.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scattering-obj-valid.rst diff --git a/news/scattering-obj-valid.rst b/news/scattering-obj-valid.rst new file mode 100644 index 00000000..adf4dfa9 --- /dev/null +++ b/news/scattering-obj-valid.rst @@ -0,0 +1,23 @@ +**Added:** + +* validate xtype belongs to XQUANTITIES during DiffractionObject init + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 74a740ac8876fbb46c011f3587e30cd2ed111e35 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Sun, 8 Dec 2024 14:25:14 -0500 Subject: [PATCH 134/445] fixed news item --- news/config-UX.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/news/config-UX.rst b/news/config-UX.rst index 05f66a10..af826723 100644 --- a/news/config-UX.rst +++ b/news/config-UX.rst @@ -1,6 +1,6 @@ **Added:** -* additional information to users to relieve frustration in finding how to update global config +* **Changed:** @@ -16,7 +16,7 @@ **Fixed:** -* +* additional information to users to relieve frustration in finding how to update global config **Security:** From d65dce1374f0754d8d19df6d0985cf1814011aea Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 8 Dec 2024 16:03:00 -0500 Subject: [PATCH 135/445] initial commit, tests need discussion --- src/diffpy/utils/diffraction_objects.py | 7 +- tests/test_diffraction_objects.py | 93 +++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6ce965d0..39f6d15c 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -366,7 +366,7 @@ def on_d(self): def scale_to(self, target_diff_object, xtype=None, xvalue=None): f""" - returns a new diffraction object which is the current object but recaled in y to the target + returns a new diffraction object which is the current object but rescaled in y to the target Parameters ---------- @@ -390,14 +390,15 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): data = self.on_xtype(xtype) target = target_diff_object.on_xtype(xtype) + if len(data[0]) == 0 or len(target[0]) == 0 or len(data[0]) != len(target[0]): + raise ValueError("I cannot scale two diffraction objects with empty or different lengths.") if xvalue is None: xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 xindex = (np.abs(data[0] - xvalue)).argmin() ytarget = target[1][xindex] yself = data[1][xindex] - scaled.on_tth[1] = data[1] * ytarget / yself - scaled.on_q[1] = data[1] * ytarget / yself + scaled._all_arrays[:, 0] = data[1] * ytarget / yself return scaled def on_xtype(self, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aeba1522..e386c7cc 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -223,6 +223,99 @@ def test_on_xtype_bad(): test.on_xtype("invalid") +params_scale_to = [ + # UC1: xvalue exact match + ( + [ + np.array([10, 15, 25, 30, 60, 140]), + np.array([10, 20, 25, 30, 60, 100]), + "tth", + 2 * np.pi, + np.array([10, 15, 25, 30, 60, 140]), + np.array([2, 3, 4, 5, 6, 7]), + "tth", + 2 * np.pi, + "tth", + 60, + ], + [np.array([1, 2, 2.5, 3, 6, 10])], + ), + # UC2: xvalue approximate match + ( + [ + np.array([0.11, 0.24, 0.31, 0.4]), + np.array([10, 20, 40, 60]), + "q", + 2 * np.pi, + np.array([0.11, 0.24, 0.31, 0.4]), + np.array([1, 3, 4, 5]), + "q", + 2 * np.pi, + "q", + 0.1, + ], + [np.array([1, 2, 4, 6])], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_scale_to) +def test_scale_to(inputs, expected): + orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + target_diff_object = DiffractionObject( + xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + ) + scaled_diff_object = orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) + # Check the intensity data is same as expected + assert np.allclose(scaled_diff_object.on_xtype(inputs[8])[1], expected[0]) + + +params_scale_to_bad = [ + # UC1: at least one of the y-arrays is empty + ( + [ + np.array([]), + np.array([]), + "tth", + 2 * np.pi, + np.array([11, 14, 16, 20, 25, 30]), + np.array([2, 3, 4, 5, 6, 7]), + "tth", + 2 * np.pi, + "tth", + 60, + ] + ), + # UC2: diffraction objects with different array lengths + ( + [ + np.array([0.11, 0.24, 0.31, 0.4, 0.5]), + np.array([10, 20, 40, 50, 60]), + "q", + 2 * np.pi, + np.array([0.1, 0.15, 0.3, 0.4]), + np.array([1, 3, 4, 5]), + "q", + 2 * np.pi, + "q", + 0.1, + ] + ), +] + + +@pytest.mark.parametrize("inputs", params_scale_to_bad) +def test_scale_to_bad(inputs): + orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + target_diff_object = DiffractionObject( + xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + ) + with pytest.raises( + ValueError, match="I cannot scale two diffraction objects with empty or different lengths." + ): + orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) + + params_index = [ # UC1: exact match ([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]), From 8431fe262ab8fcd1b75cc4d0ac01c829b79b237f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:35:06 +0000 Subject: [PATCH 136/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 666f5dbe..82add998 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -427,6 +427,7 @@ def test_xtype_setter(): ): do.xtype = "q" + def test_copy_object(): do = DiffractionObject( name="test", @@ -438,4 +439,3 @@ def test_copy_object(): do_copy = do.copy() assert do == do_copy assert id(do) != id(do_copy) - From b30cc7e1f6905295a7143d12ad04beea6d070bb1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Dec 2024 11:44:09 -0500 Subject: [PATCH 137/445] maintain input_xtype, while make it non-settable --- src/diffpy/utils/diffraction_objects.py | 14 +++++++------- tests/test_diffraction_objects.py | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 9d4bc323..049bf3ce 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -198,12 +198,12 @@ def all_arrays(self, _): raise AttributeError(_setter_wmsg("all_arrays")) @property - def xtype(self): - return self._xtype + def input_xtype(self): + return self._input_xtype - @xtype.setter - def xtype(self, _): - raise AttributeError(_setter_wmsg("xtype")) + @input_xtype.setter + def input_xtype(self, _): + raise AttributeError(_setter_wmsg("input_xtype")) def set_angles_from_list(self, angles_list): self.angles = angles_list @@ -289,7 +289,7 @@ def get_array_index(self, value, xtype=None): """ if xtype is None: - xtype = self._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.") @@ -348,7 +348,7 @@ def insert_scattering_quantity( """ self._set_xarrays(xarray, xtype) self._all_arrays[:, 0] = yarray - self._xtype = xtype + self._input_xtype = xtype # only update these optional values if non-empty quantities are passed to avoid overwriting # valid data inadvertently if metadata: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 82add998..0384d65d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -286,7 +286,7 @@ def test_dump(tmp_path, mocker): { "_all_arrays": np.empty(shape=(0, 4)), # instantiate empty "metadata": {}, - "_xtype": "", + "_input_xtype": "", "name": "", "scat_quantity": None, "qmin": np.float64(np.inf), @@ -303,7 +303,7 @@ def test_dump(tmp_path, mocker): { "_all_arrays": np.empty(shape=(0, 4)), "metadata": {"thing": "1", "another": "2"}, - "_xtype": "", + "_input_xtype": "", "name": "test", "scat_quantity": "x-ray", "qmin": np.float64(np.inf), @@ -331,7 +331,7 @@ def test_dump(tmp_path, mocker): ] ), "metadata": {}, - "_xtype": "tth", + "_input_xtype": "tth", "name": "", "scat_quantity": None, "qmin": np.float64(0.0), @@ -360,7 +360,7 @@ def test_dump(tmp_path, mocker): ] ), "metadata": {}, - "_xtype": "d", + "_input_xtype": "d", "name": "", "scat_quantity": "x-ray", "qmin": np.float64(0.0), @@ -411,21 +411,21 @@ def test_all_array_setter(): actual_do.all_arrays = np.empty((4, 4)) -def test_xtype_getter(): +def test_input_xtype_getter(): do = DiffractionObject(xtype="tth") - assert do.xtype == "tth" + assert do.input_xtype == "tth" -def test_xtype_setter(): +def test_input_xtype_setter(): do = DiffractionObject(xtype="tth") # Attempt to directly modify the property with pytest.raises( AttributeError, - match="Direct modification of attribute 'xtype' is not allowed." - "Please use 'insert_scattering_quantity' to modify 'xtype'.", + match="Direct modification of attribute 'input_xtype' is not allowed." + "Please use 'insert_scattering_quantity' to modify 'input_xtype'.", ): - do.xtype = "q" + do.input_xtype = "q" def test_copy_object(): From 2a7428cffa122e95188a9b4ec0a95f952ec81f29 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Dec 2024 11:53:29 -0500 Subject: [PATCH 138/445] Use _input_xtype within method --- src/diffpy/utils/diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 049bf3ce..2fedf18d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -374,11 +374,11 @@ def insert_scattering_quantity( raise ValueError(_xtype_wmsg(xtype)) def _get_original_array(self): - if self._xtype in QQUANTITIES: + if self._input_xtype in QQUANTITIES: return self.on_q(), "q" - elif self._xtype in ANGLEQUANTITIES: + elif self._input_xtype in ANGLEQUANTITIES: return self.on_tth(), "tth" - elif self._xtype in DQUANTITIES: + elif self._input_xtype in DQUANTITIES: return self.on_d(), "d" def on_q(self): From c580d82432f7a86b2ae18a093acec57d225daa03 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Dec 2024 11:57:33 -0500 Subject: [PATCH 139/445] Add test for mismatch for y and xarray lenght --- src/diffpy/utils/diffraction_objects.py | 17 +++++++++-------- tests/test_diffraction_objects.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 2fedf18d..3b47dfb2 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -346,6 +346,15 @@ def insert_scattering_quantity( 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 'insert_scattering_quantity' " + "with 'xarray' and 'yarray' of identical length." + ) + self._set_xarrays(xarray, xtype) self._all_arrays[:, 0] = yarray self._input_xtype = xtype @@ -360,14 +369,6 @@ def insert_scattering_quantity( if wavelength is not None: self.wavelength = wavelength - # 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 `insert_scattering_quantity` " - "with `xarray` and `yarray` of identical length." - ) - # Check xtype is valid. An empty string is the default value. if xtype != "": if xtype not in XQUANTITIES: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 0384d65d..88ac38d7 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -411,6 +411,16 @@ def test_all_array_setter(): actual_do.all_arrays = np.empty((4, 4)) +def test_xarray_yarray_length_mismatch(): + with pytest.raises( + ValueError, + match="'xarray' and 'yarray' must have the same length. " + "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "with 'xarray' and 'yarray' of identical length", + ): + DiffractionObject(xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0])) + + def test_input_xtype_getter(): do = DiffractionObject(xtype="tth") assert do.input_xtype == "tth" From 85290a956ad7cc337ab07d18c38dd2e445c0211f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Dec 2024 12:01:24 -0500 Subject: [PATCH 140/445] Remove f in the beginning of docstring --- src/diffpy/utils/diffraction_objects.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 3b47dfb2..5bfb9bd8 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -392,8 +392,8 @@ def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] def scale_to(self, target_diff_object, xtype=None, xvalue=None): - f""" - returns a new diffraction object which is the current object but recaled in y to the target + """ + Return a new diffraction object which is the current object but recaled in y to the target Parameters ---------- @@ -428,8 +428,8 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): return scaled def on_xtype(self, xtype): - f""" - return a list of two 1D np array with x and y data, raise an error if the specified xtype is invalid + """ + Return a list of two 1D np array with x and y data, raise an error if the specified xtype is invalid Parameters ---------- From 9bf532926f6bd1f2d04a1762f581314a76933921 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Dec 2024 12:04:52 -0500 Subject: [PATCH 141/445] Add empty space to _xtype_wmsg --- src/diffpy/utils/diffraction_objects.py | 6 +++--- tests/test_diffraction_objects.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 5bfb9bd8..ab39e53b 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -21,14 +21,14 @@ def _xtype_wmsg(xtype): return ( - f"I don't know how to handle the xtype, '{xtype}'. Please rerun specifying an " - f"xtype from {*XQUANTITIES, }" + f"I don't know how to handle the xtype, '{xtype}'. " + f"Please rerun specifying an xtype from {*XQUANTITIES, }" ) def _setter_wmsg(attribute): return ( - f"Direct modification of attribute '{attribute}' is not allowed." + f"Direct modification of attribute '{attribute}' is not allowed. " f"Please use 'insert_scattering_quantity' to modify '{attribute}'.", ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 88ac38d7..d7b9b32c 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -215,8 +215,8 @@ def test_init_invalid_xtype(): with pytest.raises( ValueError, match=re.escape( - f"I don't know how to handle the xtype, 'invalid_type'. Please rerun specifying an " - f"xtype from {*XQUANTITIES, }" + f"I don't know how to handle the xtype, 'invalid_type'. " + f"Please rerun specifying an xtype from {*XQUANTITIES, }" ), ): DiffractionObject(xtype="invalid_type") @@ -405,7 +405,7 @@ def test_all_array_setter(): # Attempt to directly modify the property with pytest.raises( AttributeError, - match="Direct modification of attribute 'all_arrays' is not allowed." + match="Direct modification of attribute 'all_arrays' is not allowed. " "Please use 'insert_scattering_quantity' to modify 'all_arrays'.", ): actual_do.all_arrays = np.empty((4, 4)) @@ -432,7 +432,7 @@ def test_input_xtype_setter(): # Attempt to directly modify the property with pytest.raises( AttributeError, - match="Direct modification of attribute 'input_xtype' is not allowed." + match="Direct modification of attribute 'input_xtype' is not allowed. " "Please use 'insert_scattering_quantity' to modify 'input_xtype'.", ): do.input_xtype = "q" From 5d07b9a2ba015dd52c8ce96afc4d72606835a373 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 9 Dec 2024 17:11:53 -0500 Subject: [PATCH 142/445] add news and more tests --- news/scaleto.rst | 23 +++++ src/diffpy/utils/diffraction_objects.py | 44 +++++---- tests/test_diffraction_objects.py | 122 +++++++++++++++--------- 3 files changed, 122 insertions(+), 67 deletions(-) create mode 100644 news/scaleto.rst diff --git a/news/scaleto.rst b/news/scaleto.rst new file mode 100644 index 00000000..6f6b0635 --- /dev/null +++ b/news/scaleto.rst @@ -0,0 +1,23 @@ +**Added:** + +* functionality to rescale diffraction objects, placing one on top of another at a specified point + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 39f6d15c..0fd77746 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -364,41 +364,43 @@ def on_tth(self): def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] - def scale_to(self, target_diff_object, xtype=None, xvalue=None): - f""" + def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): + """ returns a new diffraction object which is the current object but rescaled in y to the target + The y-value in the target at the closest specified x-value will be used as the factor to scale to. + The entire array is scaled by this factor so that one object places on top of the other at that point. + If multiple values of `q`, `tth`, or `d` are provided, the priority is `q` > `tth` > `d`. + If none are provided, the midpoint of the current object's `q`-array will be used. + Parameters ---------- target_diff_object: DiffractionObject - the diffraction object you want to scale the current one on to - xtype: string, optional. Default is Q - the xtype, from {XQUANTITIES}, that you will specify a point from to scale to - xvalue: float. Default is the midpoint of the array - the y-value in the target at this x-value will be used as the factor to scale to. - The entire array is scaled be the factor that places on on top of the other at that point. - xvalue does not have to be in the x-array, the point closest to this point will be used for the scaling. + the diffraction object you want to scale the current one onto + + q, tth, d : float, optional, default is the midpoint of the current object's `q`-array + the xvalue (in `q`, `tth`, or `d` space) to align the current and target objects + + offset : float, optional, default is 0 + an offset to add to the scaled y-values Returns ------- the rescaled DiffractionObject as a new object - """ scaled = deepcopy(self) - if xtype is None: - xtype = "q" + xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" + data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) + if len(data[0]) == 0 or len(target[0]) == 0: + raise ValueError("I cannot scale diffraction objects with empty arrays.") - data = self.on_xtype(xtype) - target = target_diff_object.on_xtype(xtype) - if len(data[0]) == 0 or len(target[0]) == 0 or len(data[0]) != len(target[0]): - raise ValueError("I cannot scale two diffraction objects with empty or different lengths.") + xvalue = q if xtype == "q" else tth if xtype == "tth" else d if xvalue is None: - xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 + xvalue = (data[0][0] + data[0][-1]) / 2.0 - xindex = (np.abs(data[0] - xvalue)).argmin() - ytarget = target[1][xindex] - yself = data[1][xindex] - scaled._all_arrays[:, 0] = data[1] * ytarget / yself + x_data, x_target = (np.abs(data[0] - xvalue)).argmin(), (np.abs(target[0] - xvalue)).argmin() + y_data, y_target = data[1][x_data], target[1][x_target] + scaled._all_arrays[:, 0] = data[1] * y_target / y_data + offset return scaled def on_xtype(self, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index e386c7cc..df8f14d3 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -224,96 +224,126 @@ def test_on_xtype_bad(): params_scale_to = [ - # UC1: xvalue exact match + # UC1: same x-array and y-array, check offset ( [ np.array([10, 15, 25, 30, 60, 140]), - np.array([10, 20, 25, 30, 60, 100]), + np.array([2, 3, 4, 5, 6, 7]), "tth", 2 * np.pi, np.array([10, 15, 25, 30, 60, 140]), np.array([2, 3, 4, 5, 6, 7]), "tth", 2 * np.pi, + None, + 60, + None, + 2.1, + ], + ["tth", np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])], + ), + # UC2: same length x-arrays with exact x-value match + ( + [ + np.array([10, 15, 25, 30, 60, 140]), + np.array([10, 20, 25, 30, 60, 100]), + "tth", + 2 * np.pi, + np.array([10, 20, 25, 30, 60, 140]), + np.array([2, 3, 4, 5, 6, 7]), "tth", + 2 * np.pi, + None, 60, + None, + 0, ], - [np.array([1, 2, 2.5, 3, 6, 10])], + ["tth", np.array([1, 2, 2.5, 3, 6, 10])], ), - # UC2: xvalue approximate match + # UC3: same length x-arrays with approximate x-value match ( [ - np.array([0.11, 0.24, 0.31, 0.4]), + np.array([0.12, 0.24, 0.31, 0.4]), np.array([10, 20, 40, 60]), "q", 2 * np.pi, - np.array([0.11, 0.24, 0.31, 0.4]), + np.array([0.14, 0.24, 0.31, 0.4]), np.array([1, 3, 4, 5]), "q", 2 * np.pi, - "q", 0.1, + None, + None, + 0, ], - [np.array([1, 2, 4, 6])], + ["q", np.array([1, 2, 4, 6])], ), -] - - -@pytest.mark.parametrize("inputs, expected", params_scale_to) -def test_scale_to(inputs, expected): - orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) - target_diff_object = DiffractionObject( - xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] - ) - scaled_diff_object = orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) - # Check the intensity data is same as expected - assert np.allclose(scaled_diff_object.on_xtype(inputs[8])[1], expected[0]) - - -params_scale_to_bad = [ - # UC1: at least one of the y-arrays is empty + # UC4: different x-array lengths with approximate x-value match ( [ - np.array([]), - np.array([]), + np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + np.array([10, 20, 30, 40, 50, 60, 100]), "tth", 2 * np.pi, - np.array([11, 14, 16, 20, 25, 30]), - np.array([2, 3, 4, 5, 6, 7]), + np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), "tth", 2 * np.pi, - "tth", + None, 60, - ] + None, + 0, + ], + # scaling factor is calculated at index = 5 for self and index = 6 for target + ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], ), - # UC2: diffraction objects with different array lengths + # UC5: user specified multiple x-values, prioritize q > tth > d ( [ - np.array([0.11, 0.24, 0.31, 0.4, 0.5]), - np.array([10, 20, 40, 50, 60]), - "q", + np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + np.array([10, 20, 30, 40, 50, 60, 100]), + "tth", 2 * np.pi, - np.array([0.1, 0.15, 0.3, 0.4]), - np.array([1, 3, 4, 5]), - "q", + np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "tth", 2 * np.pi, - "q", - 0.1, - ] + None, + 60, + 10, + 0, + ], + ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], ), ] -@pytest.mark.parametrize("inputs", params_scale_to_bad) -def test_scale_to_bad(inputs): +@pytest.mark.parametrize("inputs, expected", params_scale_to) +def test_scale_to(inputs, expected): orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) target_diff_object = DiffractionObject( xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] ) - with pytest.raises( - ValueError, match="I cannot scale two diffraction objects with empty or different lengths." - ): - orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) + scaled_diff_object = orig_diff_object.scale_to( + target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11] + ) + # Check the intensity data is same as expected + assert np.allclose(scaled_diff_object.on_xtype(expected[0])[1], expected[1]) + + +def test_scale_to_bad(): + # UC1: at least one of the y-arrays is empty + orig_diff_object = DiffractionObject( + xarray=np.array([]), yarray=np.array([]), xtype="tth", wavelength=2 * np.pi + ) + target_diff_object = DiffractionObject( + xarray=np.array([11, 14, 16, 20, 25, 30]), + yarray=np.array([2, 3, 4, 5, 6, 7]), + xtype="tth", + wavelength=2 * np.pi, + ) + with pytest.raises(ValueError, match="I cannot scale diffraction objects with empty arrays."): + orig_diff_object.scale_to(target_diff_object, tth=20) params_index = [ From 3c5a426929fc8eb935375befa4841badfbd0202a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 10 Dec 2024 11:40:27 -0500 Subject: [PATCH 143/445] remove error msg, change copy method --- src/diffpy/utils/diffraction_objects.py | 4 +--- tests/test_diffraction_objects.py | 15 --------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 1e7ee05d..e1b45cd1 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -388,11 +388,9 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): ------- the rescaled DiffractionObject as a new object """ - scaled = deepcopy(self) + scaled = self.copy() xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) - if len(data[0]) == 0 or len(target[0]) == 0: - raise ValueError("I cannot scale diffraction objects with empty arrays.") xvalue = q if xtype == "q" else tth if xtype == "tth" else d if xvalue is None: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index e066c025..2903bb49 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -331,21 +331,6 @@ def test_scale_to(inputs, expected): assert np.allclose(scaled_diff_object.on_xtype(expected[0])[1], expected[1]) -def test_scale_to_bad(): - # UC1: at least one of the y-arrays is empty - orig_diff_object = DiffractionObject( - xarray=np.array([]), yarray=np.array([]), xtype="tth", wavelength=2 * np.pi - ) - target_diff_object = DiffractionObject( - xarray=np.array([11, 14, 16, 20, 25, 30]), - yarray=np.array([2, 3, 4, 5, 6, 7]), - xtype="tth", - wavelength=2 * np.pi, - ) - with pytest.raises(ValueError, match="I cannot scale diffraction objects with empty arrays."): - orig_diff_object.scale_to(target_diff_object, tth=20) - - params_index = [ # UC1: exact match ([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]), From 73b2f46df52bf51a26dbbde4719605cfddc62acc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 11 Dec 2024 21:56:38 -0500 Subject: [PATCH 144/445] Remove unused test util func, fix test for comparing two objects --- tests/test_diffraction_objects.py | 48 ++----------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index dcdd4f59..a8678fd8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -8,43 +8,6 @@ from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject - -def compare_dicts(dict1, dict2): - assert dict1.keys() == dict2.keys(), "Keys mismatch" - for key in dict1: - val1, val2 = dict1[key], dict2[key] - if isinstance(val1, np.ndarray) and isinstance(val2, np.ndarray): - assert np.allclose(val1, val2), f"Arrays for key '{key}' differ" - elif isinstance(val1, np.float64) and isinstance(val2, np.float64): - assert np.isclose(val1, val2), f"Float64 values for key '{key}' differ" - else: - assert val1 == val2, f"Values for key '{key}' differ: {val1} != {val2}" - - -def dicts_equal(dict1, dict2): - equal = True - print("") - print(dict1) - print(dict2) - if not dict1.keys() == dict2.keys(): - equal = False - for key in dict1: - val1, val2 = dict1[key], dict2[key] - if isinstance(val1, np.ndarray) and isinstance(val2, np.ndarray): - if not np.allclose(val1, val2): - equal = False - elif isinstance(val1, list) and isinstance(val2, list): - if not val1.all() == val2.all(): - equal = False - elif isinstance(val1, np.float64) and isinstance(val2, np.float64): - if not np.isclose(val1, val2): - equal = False - else: - if not val1 == val2: - equal = False - return equal - - params = [ ( # Default {}, @@ -193,14 +156,9 @@ def dicts_equal(dict1, dict2): @pytest.mark.parametrize("inputs1, inputs2, expected", params) def test_diffraction_objects_equality(inputs1, inputs2, expected): - diffraction_object1 = DiffractionObject(**inputs1) - diffraction_object2 = DiffractionObject(**inputs2) - # diffraction_object1_attributes = [key for key in diffraction_object1.__dict__ if not key.startswith("_")] - # for i, attribute in enumerate(diffraction_object1_attributes): - # setattr(diffraction_object1, attribute, inputs1[i]) - # setattr(diffraction_object2, attribute, inputs2[i]) - print(dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__), expected) - assert dicts_equal(diffraction_object1.__dict__, diffraction_object2.__dict__) == expected + do_1 = DiffractionObject(**inputs1) + do_2 = DiffractionObject(**inputs2) + assert (do_1 == do_2) == expected def test_on_xtype(): From 919c60bab6c0c2d7b77503db6edbebd423230fe5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 12:48:11 -0500 Subject: [PATCH 145/445] ename to in init --- news/mv-input-scattering-quan.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/diffraction_objects.py | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 news/mv-input-scattering-quan.rst diff --git a/news/mv-input-scattering-quan.rst b/news/mv-input-scattering-quan.rst new file mode 100644 index 00000000..25e8aac9 --- /dev/null +++ b/news/mv-input-scattering-quan.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* Rename `input_scattering_quantity` to `input_data` in `DiffractionObject` init + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ab39e53b..fe853d02 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -53,7 +53,7 @@ def __init__( if yarray is None: yarray = np.empty(0) - self.insert_scattering_quantity(xarray, yarray, xtype) + self.input_data(xarray, yarray, xtype) def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -317,7 +317,7 @@ def _set_xarrays(self, xarray, xtype): self.dmin = np.nanmin(self._all_arrays[:, 3], initial=np.inf) self.dmax = np.nanmax(self._all_arrays[:, 3], initial=0.0) - def insert_scattering_quantity( + def input_data( self, xarray, yarray, From f311988add3866b2e18c2a2185fde6d2a31f7811 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 12:56:26 -0500 Subject: [PATCH 146/445] Fix insert_scattering_quantity to input data in all other files globally --- src/diffpy/utils/diffraction_objects.py | 4 ++-- tests/test_diffraction_objects.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index fe853d02..59aac37b 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -29,7 +29,7 @@ def _xtype_wmsg(xtype): def _setter_wmsg(attribute): return ( f"Direct modification of attribute '{attribute}' is not allowed. " - f"Please use 'insert_scattering_quantity' to modify '{attribute}'.", + f"Please use 'input_data' to modify '{attribute}'.", ) @@ -351,7 +351,7 @@ def input_data( if len(xarray) != len(yarray): raise ValueError( "'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index f93190ae..328bb512 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -364,7 +364,7 @@ def test_all_array_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'all_arrays' is not allowed. " - "Please use 'insert_scattering_quantity' to modify 'all_arrays'.", + "Please use 'input_data' to modify 'all_arrays'.", ): actual_do.all_arrays = np.empty((4, 4)) @@ -373,7 +373,7 @@ def test_xarray_yarray_length_mismatch(): with pytest.raises( ValueError, match="'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length", ): DiffractionObject(xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0])) @@ -391,7 +391,7 @@ def test_input_xtype_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'input_xtype' is not allowed. " - "Please use 'insert_scattering_quantity' to modify 'input_xtype'.", + "Please use 'input_data' to modify 'input_xtype'.", ): do.input_xtype = "q" From b97db395aefd2df297e20f60f5816f2264e93bc4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 13:28:57 -0500 Subject: [PATCH 147/445] Add uuid property to DiffractionObject --- src/diffpy/utils/diffraction_objects.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ab39e53b..abfd44b0 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -1,4 +1,5 @@ import datetime +import uuid import warnings from copy import deepcopy @@ -29,7 +30,7 @@ def _xtype_wmsg(xtype): def _setter_wmsg(attribute): return ( f"Direct modification of attribute '{attribute}' is not allowed. " - f"Please use 'insert_scattering_quantity' to modify '{attribute}'.", + f"Please use 'input_data' to modify '{attribute}'.", ) @@ -53,7 +54,8 @@ def __init__( if yarray is None: yarray = np.empty(0) - self.insert_scattering_quantity(xarray, yarray, xtype) + self._id = uuid.uuid4() + self.input_data(xarray, yarray, xtype) def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -205,6 +207,14 @@ def input_xtype(self): def input_xtype(self, _): raise AttributeError(_setter_wmsg("input_xtype")) + @property + def id(self): + return self._id + + @id.setter + def id(self, _): + raise AttributeError(_setter_wmsg("id")) + def set_angles_from_list(self, angles_list): self.angles = angles_list self.n_steps = len(angles_list) - 1.0 @@ -317,7 +327,7 @@ def _set_xarrays(self, xarray, xtype): self.dmin = np.nanmin(self._all_arrays[:, 3], initial=np.inf) self.dmax = np.nanmax(self._all_arrays[:, 3], initial=0.0) - def insert_scattering_quantity( + def input_data( self, xarray, yarray, @@ -351,7 +361,7 @@ def insert_scattering_quantity( if len(xarray) != len(yarray): raise ValueError( "'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length." ) From 5d87cdcaef215d783cbc05042f6c717e134f17c1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 13:29:12 -0500 Subject: [PATCH 148/445] Modify tests functions to test gettable id property --- tests/test_diffraction_objects.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index f93190ae..ef758b9d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -1,5 +1,7 @@ import re +import uuid from pathlib import Path +from uuid import UUID import numpy as np import pytest @@ -335,8 +337,8 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize("inputs, expected", tc_params) def test_constructor(inputs, expected): - actual_do = DiffractionObject(**inputs) - diff = DeepDiff(actual_do.__dict__, expected, ignore_order=True, significant_digits=13) + actual = DiffractionObject(**inputs).__dict__ + diff = DeepDiff(actual, expected, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") assert diff == {} @@ -364,16 +366,33 @@ def test_all_array_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'all_arrays' is not allowed. " - "Please use 'insert_scattering_quantity' to modify 'all_arrays'.", + "Please use 'input_data' to modify 'all_arrays'.", ): actual_do.all_arrays = np.empty((4, 4)) +def test_id_getter(): + do = DiffractionObject() + assert hasattr(do, "id") + assert isinstance(do.id, UUID) + assert len(str(do.id)) == 36 + + +def test_id_setter(): + do = DiffractionObject() + # Attempt to directly modify the property + with pytest.raises( + AttributeError, + match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", + ): + do.id = uuid.uuid4() + + def test_xarray_yarray_length_mismatch(): with pytest.raises( ValueError, match="'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " + "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length", ): DiffractionObject(xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0])) @@ -391,7 +410,7 @@ def test_input_xtype_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'input_xtype' is not allowed. " - "Please use 'insert_scattering_quantity' to modify 'input_xtype'.", + "Please use 'input_data' to modify 'input_xtype'.", ): do.input_xtype = "q" From cd535869827ddc77441597ad3fbc91b854e572b0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 13:29:17 -0500 Subject: [PATCH 149/445] Add news --- news/uuid.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/uuid.rst diff --git a/news/uuid.rst b/news/uuid.rst new file mode 100644 index 00000000..474793f4 --- /dev/null +++ b/news/uuid.rst @@ -0,0 +1,23 @@ +**Added:** + +* Gettable `id` property to `DiffractionObject` + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 6169620dce28ab3f599b16c371ff29fe26a4133e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 13:31:36 -0500 Subject: [PATCH 150/445] Use insert_scattering_quantity for now before PR merged --- src/diffpy/utils/diffraction_objects.py | 8 ++++---- tests/test_diffraction_objects.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index abfd44b0..0ec305bc 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -30,7 +30,7 @@ def _xtype_wmsg(xtype): def _setter_wmsg(attribute): return ( f"Direct modification of attribute '{attribute}' is not allowed. " - f"Please use 'input_data' to modify '{attribute}'.", + f"Please use 'insert_scattering_quantity' to modify '{attribute}'.", ) @@ -55,7 +55,7 @@ def __init__( yarray = np.empty(0) self._id = uuid.uuid4() - self.input_data(xarray, yarray, xtype) + self.insert_scattering_quantity(xarray, yarray, xtype) def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -327,7 +327,7 @@ def _set_xarrays(self, xarray, xtype): 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( + def insert_scattering_quantity( self, xarray, yarray, @@ -361,7 +361,7 @@ def input_data( 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' " + "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " "with 'xarray' and 'yarray' of identical length." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ef758b9d..b54d7a02 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -366,7 +366,7 @@ def test_all_array_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'all_arrays' is not allowed. " - "Please use 'input_data' to modify 'all_arrays'.", + "Please use 'insert_scattering_quantity' to modify 'all_arrays'.", ): actual_do.all_arrays = np.empty((4, 4)) @@ -383,7 +383,7 @@ def test_id_setter(): # Attempt to directly modify the property with pytest.raises( AttributeError, - match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", + match="Direct modification of attribute 'id' is not allowed. Please use 'insert_scattering_quantity' to modify 'id'.", ): do.id = uuid.uuid4() @@ -392,7 +392,7 @@ def test_xarray_yarray_length_mismatch(): with pytest.raises( ValueError, match="'xarray' and 'yarray' must have the same length. " - "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " + "Please re-initialize 'DiffractionObject' or re-run the method 'insert_scattering_quantity' " "with 'xarray' and 'yarray' of identical length", ): DiffractionObject(xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0])) @@ -410,7 +410,7 @@ def test_input_xtype_setter(): with pytest.raises( AttributeError, match="Direct modification of attribute 'input_xtype' is not allowed. " - "Please use 'input_data' to modify 'input_xtype'.", + "Please use 'insert_scattering_quantity' to modify 'input_xtype'.", ): do.input_xtype = "q" From 0dd95e898b3b7b964117ac1c6d900287216eefe7 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 14:35:34 -0500 Subject: [PATCH 151/445] Use input_data across all tests file instead of input_scattering_data --- tests/test_diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index c01c203b..3a7ae366 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -378,12 +378,12 @@ def test_id_getter(): assert len(str(do.id)) == 36 -def test_id_setter(): +def test_id_setter_error(): do = DiffractionObject() # Attempt to directly modify the property with pytest.raises( AttributeError, - match="Direct modification of attribute 'id' is not allowed. Please use 'insert_scattering_quantity' to modify 'id'.", + match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", ): do.id = uuid.uuid4() @@ -403,7 +403,7 @@ def test_input_xtype_getter(): assert do.input_xtype == "tth" -def test_input_xtype_setter(): +def test_input_xtype_setter_error(): do = DiffractionObject(xtype="tth") # Attempt to directly modify the property From 79161f177ec2463be321fd6a47fe28ca63f27328 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 12 Dec 2024 15:04:49 -0500 Subject: [PATCH 152/445] remove expected parameters in bad tests --- tests/test_diffraction_objects.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 68dd0c01..043da5c0 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -290,27 +290,24 @@ def test_scale_to(inputs, expected): params_scale_to_bad = [ ( - [ - np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - np.array([10, 20, 30, 40, 50, 60, 100]), - "tth", - 2 * np.pi, - np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "tth", - 2 * np.pi, - None, - 60, - 10, - 0, - ], - ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], + np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + np.array([10, 20, 30, 40, 50, 60, 100]), + "tth", + 2 * np.pi, + np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "tth", + 2 * np.pi, + None, + 60, + 10, + 0, ), ] -@pytest.mark.parametrize("inputs, expected", params_scale_to_bad) -def test_scale_to_bad(inputs, expected): +@pytest.mark.parametrize("inputs", params_scale_to_bad) +def test_scale_to_bad(inputs): orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) target_diff_object = DiffractionObject( xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] From 1b7aa14903814ba6df2c235f240c4724966db308 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 12 Dec 2024 15:07:40 -0500 Subject: [PATCH 153/445] edit docstring --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 936bb25c..a8f0bbaa 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -397,7 +397,7 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. - If multiple values of `q`, `tth`, or `d` are provided, the priority is `q` > `tth` > `d`. + If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. If none are provided, the midpoint of the current object's `q`-array will be used. Parameters From 4bd441fa39fc8fee9090097858ea729321de812a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 17:13:49 -0500 Subject: [PATCH 154/445] Add mocker for testing id property --- tests/test_diffraction_objects.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 3a7ae366..5b366846 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -378,12 +378,18 @@ def test_id_getter(): assert len(str(do.id)) == 36 +def test_id_getter_with_mock(mocker): + mocker.patch.object(DiffractionObject, "id", new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87")) + do = DiffractionObject() + assert do.id == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") + + def test_id_setter_error(): do = DiffractionObject() - # Attempt to directly modify the property + with pytest.raises( AttributeError, - match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", + match="Direct modification of attribute 'id' is not allowed. " "Please use 'input_data' to modify 'id'.", ): do.id = uuid.uuid4() From 3a52b2f82893a307a32dbc28638eb43fbf6b35a7 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 17:24:39 -0500 Subject: [PATCH 155/445] Fix spacing in error msg --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 5b366846..50080550 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -389,7 +389,7 @@ def test_id_setter_error(): with pytest.raises( AttributeError, - match="Direct modification of attribute 'id' is not allowed. " "Please use 'input_data' to modify 'id'.", + match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", ): do.id = uuid.uuid4() From 6e51eb79e66a78c7dbafe4797174ffe19ad7be75 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 17:30:57 -0500 Subject: [PATCH 156/445] Re-run CI for codecov fail From 53585aeb13a12be4a4b8f3319626d8ed691d36be Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 18:22:37 -0500 Subject: [PATCH 157/445] Add doc build yml file --- .github/workflows/publish-docs-on-release.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/publish-docs-on-release.yml diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml new file mode 100644 index 00000000..b09c688a --- /dev/null +++ b/.github/workflows/publish-docs-on-release.yml @@ -0,0 +1,16 @@ +name: Deploy Documentation on Release + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + docs: + permissions: + contents: write + uses: Billingegroup/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 + with: + project: diffpy.utils + c_extension: false + headless: false From 3ec44dcdd5e2b6d6258fe4eb420875f9c5965930 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 23:26:11 -0500 Subject: [PATCH 158/445] Rename Diffraction_objects to DiffractionObject in README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 48967571..520d4029 100644 --- a/README.rst +++ b/README.rst @@ -43,7 +43,7 @@ General purpose shared utilities for the diffpy libraries. The diffpy.utils package provides functions for extracting array data from variously formatted text files, an interpolation function based on the Whittaker-Shannon formula that can be used to resample a PDF or other profile -function over a new grid, `Diffraction_objects` for conveniently manipulating +function over a new grid, `DiffractionObject` for conveniently manipulating diffraction data, and some wx GUI utilities used by the PDFgui program. From 4fa614ff905a9f805fd812764ad3b502dbaebe6e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 23:27:25 -0500 Subject: [PATCH 159/445] Import numpy as np, remove end of line, improve docstring --- src/diffpy/utils/__init__.py | 2 - src/diffpy/utils/parsers/__init__.py | 2 - src/diffpy/utils/parsers/loaddata.py | 3 -- src/diffpy/utils/resampler.py | 61 +++++++++++++--------------- src/diffpy/utils/version.py | 2 - src/diffpy/utils/wx/__init__.py | 2 - src/diffpy/utils/wx/gridutils.py | 3 -- tests/test_resample.py | 26 ++++++++---- 8 files changed, 45 insertions(+), 56 deletions(-) diff --git a/src/diffpy/utils/__init__.py b/src/diffpy/utils/__init__.py index 41a4fa54..c9909a8a 100644 --- a/src/diffpy/utils/__init__.py +++ b/src/diffpy/utils/__init__.py @@ -20,5 +20,3 @@ # silence the pyflakes syntax checker assert __version__ or True - -# End of file diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index cd95dd63..bab9943c 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -15,5 +15,3 @@ """Various utilities related to data parsing and manipulation. """ - -# End of file diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 870e6016..39c4b163 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -344,6 +344,3 @@ def isfloat(s): except ValueError: pass return False - - -# End of file diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index e88c4f0b..3959afdf 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -15,52 +15,50 @@ """Various utilities related to data parsing and manipulation.""" -import numpy +import numpy as np -# NOTE - this should be faster than resample below and conforms more closely to -# numpy.interp. I'm keeping resample for legacy reasons. def wsinterp(x, xp, fp, left=None, right=None): """One-dimensional Whittaker-Shannon interpolation. - This uses the Whittaker-Shannon interpolation formula to interpolate the value of fp (array), - which is defined over xp (array), at x (array or float). + Reconstruct a continuous signal from discrete data points by utilizing sinc functions + as interpolation kernels. This function interpolates the values of fp (array), + which are defined over xp (array), at new points x (array or float). Parameters ---------- x: ndarray - Desired range for interpolation. + The x values at which interpolation is computed. xp: ndarray - Defined range for fp. + The array of known x values. fp: ndarray - Function to be interpolated. + The array of y values associated xp. left: float If given, set fp for x < xp[0] to left. Otherwise, if left is None (default) or not given, set fp for x < xp[0] to fp evaluated at xp[-1]. right: float If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for - x > xp[-1] to fp evaluated at xp[-1]. + x > xp[-1] to fp evaluated at xp[-1] Returns ------- - float: - If input x is a scalar (not an array), return the interpolated value at x. - ndarray: - If input x is an array, return the interpolated array with dimensions of x. + ndarray or float + Interpolated values at points x. Returns a single float if x is a scalar, + otherwise returns a numpy.ndarray. """ - scalar = numpy.isscalar(x) + scalar = np.isscalar(x) if scalar: - x = numpy.array(x) + x = np.array(x) x.resize(1) # shape = (nxp, nx), nxp copies of x data span axis 1 - u = numpy.resize(x, (len(xp), len(x))) + u = np.resize(x, (len(xp), len(x))) # Must take transpose of u for proper broadcasting with xp. # shape = (nx, nxp), v(xp) data spans axis 1 v = (xp - u.T) / (xp[1] - xp[0]) # shape = (nx, nxp), m(v) data spans axis 1 - m = fp * numpy.sinc(v) + m = fp * np.sinc(v) # Sum over m(v) (axis 1) - fp_at_x = numpy.sum(m, axis=1) + fp_at_x = np.sum(m, axis=1) # Enforce left and right if left is None: @@ -100,36 +98,33 @@ def resample(r, s, dr): dr0 = r[1] - r[0] # Constant timestep if dr0 < dr: - rnew = numpy.arange(r[0], r[-1] + 0.5 * dr, dr) - snew = numpy.interp(rnew, r, s) + rnew = np.arange(r[0], r[-1] + 0.5 * dr, dr) + snew = np.interp(rnew, r, s) return rnew, snew elif dr0 > dr: # Tried to pad the end of s to dampen, but nothing works. # m = (s[-1] - s[-2]) / dr0 # b = (s[-2] * r[-1] - s[-1] * r[-2]) / dr0 - # rpad = r[-1] + numpy.arange(1, len(s))*dr0 + # rpad = r[-1] + np.arange(1, len(s))*dr0 # spad = rpad * m + b - # spad = numpy.concatenate([s,spad]) - # rnew = numpy.arange(0, rpad[-1], dr) - # snew = numpy.zeros_like(rnew) + # spad = np.concatenate([s,spad]) + # rnew = np.arange(0, rpad[-1], dr) + # snew = np.zeros_like(rnew) # Accommodate for the fact that r[0] might not be 0 # u = (rnew-r[0]) / dr0 # for n in range(len(spad)): - # snew += spad[n] * numpy.sinc(u - n) + # snew += spad[n] * np.sinc(u - n) - # sel = numpy.logical_and(rnew >= r[0], rnew <= r[-1]) + # sel = np.logical_and(rnew >= r[0], rnew <= r[-1]) - rnew = numpy.arange(0, r[-1], dr) - snew = numpy.zeros_like(rnew) + rnew = np.arange(0, r[-1], dr) + snew = np.zeros_like(rnew) u = (rnew - r[0]) / dr0 for n in range(len(s)): - snew += s[n] * numpy.sinc(u - n) - sel = numpy.logical_and(rnew >= r[0], rnew <= r[-1]) + snew += s[n] * np.sinc(u - n) + sel = np.logical_and(rnew >= r[0], rnew <= r[-1]) return rnew[sel], snew[sel] # If we got here, then no resampling is required return r.copy(), s.copy() - - -# End of file diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index e42fed78..0bc397e4 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -22,5 +22,3 @@ from importlib.metadata import version __version__ = version("diffpy.utils") - -# End of file diff --git a/src/diffpy/utils/wx/__init__.py b/src/diffpy/utils/wx/__init__.py index c2d0617b..3f7417ef 100644 --- a/src/diffpy/utils/wx/__init__.py +++ b/src/diffpy/utils/wx/__init__.py @@ -15,5 +15,3 @@ """Utilities related wx Python GUIs. """ - -# End of file diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index 1a25bfb3..c459ac30 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -163,6 +163,3 @@ def _indicesToBlocks(indices): i0 = i rv = [tuple(ij) for ij in rngs] return rv - - -# End of file diff --git a/tests/test_resample.py b/tests/test_resample.py index deddcca2..137f5dd9 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -1,3 +1,5 @@ +import random + import numpy as np import pytest @@ -5,22 +7,28 @@ def test_wsinterp(): - import random - - # Check known points are unchanged by interpolation # FIXME: if another SW interp function exists, run comparisons for interpolated points + # Sampling rate ssr = 44100**-1 # Standard sampling rate for human-hearable frequencies - t = ssr + + # Creating a symmetric set of sample points around zero. n = 5 - xp = np.array([i * t for i in range(-n, n + 1, 1)]) - x = np.array([i * t for i in range(-n - 1, n + 2, 1)]) + xp = np.array([i * ssr for i in range(-n, n + 1, 1)]) + x = np.array([i * ssr for i in range(-n - 1, n + 2, 1)]) + assert len(xp) == 11 and len(x) == 13 - # Interpolate a few random datasets + # Generating fp values across 10 trial runs trials = 10 - for trial in range(trials): - fp = np.array([random.random() * ssr for i in range(-n, n + 1, 1)]) + + for _ in range(trials): + # Create random function values (fp) at the points defined in xp above + fp = np.array([random.random() * ssr for _ in range(-n, n + 1, 1)]) + + # Interpolate the values at new x points fp_at_x = wsinterp(x, xp, fp) + + # Check that the known points are unchanged by interpolation assert np.allclose(fp_at_x[1:-1], fp) for i in range(len(x)): assert fp_at_x[i] == pytest.approx(wsinterp(x[i], xp, fp)) From f737a13fb038d1fc76aef3d064fac5029718718e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 12 Dec 2024 23:29:27 -0500 Subject: [PATCH 160/445] Add a period to docstring --- src/diffpy/utils/resampler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 3959afdf..7245b57e 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -38,7 +38,7 @@ def wsinterp(x, xp, fp, left=None, right=None): set fp for x < xp[0] to fp evaluated at xp[-1]. right: float If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for - x > xp[-1] to fp evaluated at xp[-1] + x > xp[-1] to fp evaluated at xp[-1]. Returns ------- From 83a0c30e23c549d3443d7de22eb0095a759346d6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 10:23:48 -0500 Subject: [PATCH 161/445] Add citation to wsint, remove empty line, add 'the' in docstrings --- src/diffpy/utils/resampler.py | 5 +++-- tests/test_resample.py | 8 +------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 7245b57e..3c761d1b 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -24,6 +24,7 @@ def wsinterp(x, xp, fp, left=None, right=None): Reconstruct a continuous signal from discrete data points by utilizing sinc functions as interpolation kernels. This function interpolates the values of fp (array), which are defined over xp (array), at new points x (array or float). + The implementation is based on E. T. Whittaker's 1915 paper (https://doi.org/10.1017/S0370164600017806). Parameters ---------- @@ -32,7 +33,7 @@ def wsinterp(x, xp, fp, left=None, right=None): xp: ndarray The array of known x values. fp: ndarray - The array of y values associated xp. + The array of y values associated with xp. left: float If given, set fp for x < xp[0] to left. Otherwise, if left is None (default) or not given, set fp for x < xp[0] to fp evaluated at xp[-1]. @@ -43,7 +44,7 @@ def wsinterp(x, xp, fp, left=None, right=None): Returns ------- ndarray or float - Interpolated values at points x. Returns a single float if x is a scalar, + The interpolated values at points x. Returns a single float if x is a scalar, otherwise returns a numpy.ndarray. """ scalar = np.isscalar(x) diff --git a/tests/test_resample.py b/tests/test_resample.py index 137f5dd9..c4e25ac3 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -18,21 +18,15 @@ def test_wsinterp(): x = np.array([i * ssr for i in range(-n - 1, n + 2, 1)]) assert len(xp) == 11 and len(x) == 13 - # Generating fp values across 10 trial runs + # Generating a new set of fp values across 10 trial runs trials = 10 for _ in range(trials): # Create random function values (fp) at the points defined in xp above fp = np.array([random.random() * ssr for _ in range(-n, n + 1, 1)]) - - # Interpolate the values at new x points fp_at_x = wsinterp(x, xp, fp) # Check that the known points are unchanged by interpolation assert np.allclose(fp_at_x[1:-1], fp) for i in range(len(x)): assert fp_at_x[i] == pytest.approx(wsinterp(x[i], xp, fp)) - - -if __name__ == "__main__": - test_wsinterp() From a2f244dc90d30cfb28efe3a3493a990fbca93194 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 10:25:04 -0500 Subject: [PATCH 162/445] Reword from generating to generate in comment --- src/diffpy/utils/resampler.py | 3 ++- tests/test_resample.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 3c761d1b..5f6062d2 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -24,7 +24,8 @@ def wsinterp(x, xp, fp, left=None, right=None): Reconstruct a continuous signal from discrete data points by utilizing sinc functions as interpolation kernels. This function interpolates the values of fp (array), which are defined over xp (array), at new points x (array or float). - The implementation is based on E. T. Whittaker's 1915 paper (https://doi.org/10.1017/S0370164600017806). + The implementation is based on E. T. Whittaker's 1915 paper + (https://doi.org/10.1017/S0370164600017806). Parameters ---------- diff --git a/tests/test_resample.py b/tests/test_resample.py index c4e25ac3..784932bb 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -18,7 +18,7 @@ def test_wsinterp(): x = np.array([i * ssr for i in range(-n - 1, n + 2, 1)]) assert len(xp) == 11 and len(x) == 13 - # Generating a new set of fp values across 10 trial runs + # Generate a new set of fp values across 10 trial runs trials = 10 for _ in range(trials): From 91594ed5273f877fd0ae8a7f01a819c5ad7f2a6c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 10:27:56 -0500 Subject: [PATCH 163/445] Rerun CI From 781e3581cfa9a55d90cd2d6722b1dd5a265c3513 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 12:22:45 -0500 Subject: [PATCH 164/445] Capture no wavelenght warning for test_q_to_tth --- tests/test_transforms.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index f1c940b7..5f86294b 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,24 +5,30 @@ params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays - ([None, np.empty((0))], np.empty((0))), - # UC2: Empty q values, wavelength specified, return empty arrays - ([4 * np.pi, np.empty((0))], np.empty(0)), + (None, np.empty((0)), np.empty((0))), + # # UC2: Empty q values, wavelength specified, return empty arrays + (4 * np.pi, np.empty((0)), np.empty(0)), # UC3: User specified valid q values, no wavelength, return empty arrays ( - [None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], + None, + np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), # UC4: User specified valid q values (with wavelength) # expected tth values are 2*arcsin(q) in degrees - ([4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0])], np.array([0, 90.0, 180.0])), + (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ] +@pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) +def test_q_to_tth(wavelength, q, expected_tth): + + if wavelength is None: + with pytest.warns(UserWarning, match="INFO: no wavelength has been specified"): + actual_tth = q_to_tth(q, wavelength) + else: + actual_tth = q_to_tth(q, wavelength) -@pytest.mark.parametrize("inputs, expected", params_q_to_tth) -def test_q_to_tth(inputs, expected): - actual = q_to_tth(inputs[1], inputs[0]) - assert np.allclose(expected, actual) + assert np.allclose(expected_tth, actual_tth) params_q_to_tth_bad = [ From 7ac21a37708566ae668555cecc31e1da8e1af31d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:26:22 +0000 Subject: [PATCH 165/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 5f86294b..43391e3b 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -19,10 +19,11 @@ (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ] + @pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) def test_q_to_tth(wavelength, q, expected_tth): - - if wavelength is None: + + if wavelength is None: with pytest.warns(UserWarning, match="INFO: no wavelength has been specified"): actual_tth = q_to_tth(q, wavelength) else: From 792ce171b1bce508df171f1966cef3b728211714 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 12:26:35 -0500 Subject: [PATCH 166/445] Apply precommit --- tests/test_transforms.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 5f86294b..43391e3b 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -19,10 +19,11 @@ (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ] + @pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) def test_q_to_tth(wavelength, q, expected_tth): - - if wavelength is None: + + if wavelength is None: with pytest.warns(UserWarning, match="INFO: no wavelength has been specified"): actual_tth = q_to_tth(q, wavelength) else: From c22a95bc9a03231fb812c29bbf2ffded06d17355 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 13 Dec 2024 12:26:50 -0500 Subject: [PATCH 167/445] remove default value in the function --- src/diffpy/utils/diffraction_objects.py | 19 +++++++------ tests/test_diffraction_objects.py | 36 ++++++++++++------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a8f0bbaa..0813ef9b 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -397,15 +397,14 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. - If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. - If none are provided, the midpoint of the current object's `q`-array will be used. + If multiple values of `q`, `tth`, or `d` are provided, or none are provided, an error will be raised. Parameters ---------- target_diff_object: DiffractionObject the diffraction object you want to scale the current one onto - q, tth, d : float, optional, default is the midpoint of the current object's `q`-array + q, tth, d : float, optional, must specify exactly one of them the xvalue (in `q`, `tth`, or `d` space) to align the current and target objects offset : float, optional, default is 0 @@ -417,19 +416,19 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): """ scaled = self.copy() count = sum([q is not None, tth is not None, d is not None]) - if count > 1: - raise ValueError("You can only specify one of 'q', 'tth', or 'd'. Please rerun specifying only one.") + if count != 1: + raise ValueError( + "You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." + ) xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) xvalue = q if xtype == "q" else tth if xtype == "tth" else d - if xvalue is None: - xvalue = (data[0][0] + data[0][-1]) / 2.0 - x_data, x_target = (np.abs(data[0] - xvalue)).argmin(), (np.abs(target[0] - xvalue)).argmin() - y_data, y_target = data[1][x_data], target[1][x_target] - scaled._all_arrays[:, 0] = data[1] * y_target / y_data + offset + xindex_data = (np.abs(data[0] - xvalue)).argmin() + xindex_target = (np.abs(target[0] - xvalue)).argmin() + scaled._all_arrays[:, 0] = data[1] * target[1][xindex_target] / data[1][xindex_data] + offset return scaled def on_xtype(self, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 043da5c0..bd8c6c6e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -254,24 +254,6 @@ def test_init_invalid_xtype(): # scaling factor is calculated at index = 5 for self and index = 6 for target ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], ), - # UC5: user did not specify anything, use the midpoint of the current object's q-array - ( - [ - np.array([0.1, 0.2, 0.3]), - np.array([1, 2, 3]), - "q", - 2 * np.pi, - np.array([0.05, 0.1, 0.2, 0.3]), - np.array([5, 10, 20, 30]), - "q", - 2 * np.pi, - None, - None, - None, - 0, - ], - ["q", np.array([10, 20, 30])], - ), ] @@ -289,6 +271,22 @@ def test_scale_to(inputs, expected): params_scale_to_bad = [ + # UC1: user did not specify anything + ( + np.array([0.1, 0.2, 0.3]), + np.array([1, 2, 3]), + "q", + 2 * np.pi, + np.array([0.05, 0.1, 0.2, 0.3]), + np.array([5, 10, 20, 30]), + "q", + 2 * np.pi, + None, + None, + None, + 0, + ), + # UC2: user specified more than one of q, tth, and d ( np.array([10, 25, 30.1, 40.2, 61, 120, 140]), np.array([10, 20, 30, 40, 50, 60, 100]), @@ -313,7 +311,7 @@ def test_scale_to_bad(inputs): xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] ) with pytest.raises( - ValueError, match="You can only specify one of 'q', 'tth', or 'd'. Please rerun specifying only one." + ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ): orig_diff_object.scale_to(target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11]) From 8c891db7d11d94fe79852cca7d40742299ee64e5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 23:29:37 -0500 Subject: [PATCH 168/445] use variables within parametrize for test_diffraction_objects --- tests/test_diffraction_objects.py | 42 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 50080550..5a5d9805 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -164,11 +164,11 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_on_xtype(): - test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") - assert np.allclose(test.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])]) - assert np.allclose(test.on_xtype("2theta"), [np.array([30, 60]), np.array([1, 2])]) - assert np.allclose(test.on_xtype("q"), [np.array([0.51764, 1]), np.array([1, 2])]) - assert np.allclose(test.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) + do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") + assert np.allclose(do.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])]) + assert np.allclose(do.on_xtype("2theta"), [np.array([30, 60]), np.array([1, 2])]) + assert np.allclose(do.on_xtype("q"), [np.array([0.51764, 1]), np.array([1, 2])]) + assert np.allclose(do.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) def test_init_invalid_xtype(): @@ -184,34 +184,34 @@ def test_init_invalid_xtype(): params_index = [ # UC1: exact match - ([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]), + (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), # UC2: target value lies in the array, returns the (first) closest index - ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45], [0]), - ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25], [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), # UC3: target value out of the range, returns the closest index - ([4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1], [0]), - ([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63], [1]), + (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), ] -@pytest.mark.parametrize("inputs, expected", params_index) -def test_get_array_index(inputs, expected): - test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3]) - actual = test.get_array_index(value=inputs[5], xtype=inputs[4]) - assert actual == expected[0] +@pytest.mark.parametrize("wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", params_index) +def test_get_array_index(wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index): + do = DiffractionObject(wavelength=wavelength, xarray=xarray, yarray=yarray, xtype=xtype_1) + actual_index = do.get_array_index(value=value, xtype=xtype_2) + assert actual_index == expected_index def test_get_array_index_bad(): - test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth") + do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth") with pytest.raises(ValueError, match=re.escape("The 'tth' array is empty. Please ensure it is initialized.")): - test.get_array_index(value=30) + do.get_array_index(value=30) def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - test = DiffractionObject( + do = DiffractionObject( wavelength=1.54, name="test", scat_quantity="x-ray", @@ -222,7 +222,7 @@ def test_dump(tmp_path, mocker): ) mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): - test.dump(file, "q") + do.dump(file, "q") with open(file, "r") as f: actual = f.read() expected = ( @@ -360,7 +360,7 @@ def test_all_array_getter(): def test_all_array_setter(): - actual_do = DiffractionObject() + do = DiffractionObject() # Attempt to directly modify the property with pytest.raises( @@ -368,7 +368,7 @@ def test_all_array_setter(): match="Direct modification of attribute 'all_arrays' is not allowed. " "Please use 'input_data' to modify 'all_arrays'.", ): - actual_do.all_arrays = np.empty((4, 4)) + do.all_arrays = np.empty((4, 4)) def test_id_getter(): From cce5716329249677e3c93234d5017f8b744a09a4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 23:29:57 -0500 Subject: [PATCH 169/445] Remove INFO and improve how to set wavelenght warning msg --- src/diffpy/utils/transforms.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index c150b2a4..77df975e 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -4,12 +4,10 @@ import numpy as np wavelength_warning_emsg = ( - "INFO: no wavelength has been specified. You can continue " - "to use the DiffractionObject but some of its powerful features " - "will not be available. To specify a wavelength, set " - "diffraction_object.wavelength = [number], " - "where diffraction_object is the variable name of you Diffraction Object, " - "and number is the wavelength in angstroms." + "No wavelength has been specified. You can continue to use the DiffractionObject, but " + "some of its powerful features will not be available. " + "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " + "you may set do.wavelength = 1.54 with the unit in angstroms." ) invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." invalid_q_or_d_or_wavelength_emsg = ( From 6bb822f574b7189687eb46d4a3a1ce0c62a87281 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 23:30:30 -0500 Subject: [PATCH 170/445] Use variable names within parametrize for transforms --- tests/test_transforms.py | 61 +++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 43391e3b..453844c2 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -1,3 +1,5 @@ +import re + import numpy as np import pytest @@ -6,7 +8,7 @@ params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0))), - # # UC2: Empty q values, wavelength specified, return empty arrays + # UC2: Empty q values, wavelength specified, return empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), # UC3: User specified valid q values, no wavelength, return empty arrays ( @@ -23,8 +25,15 @@ @pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) def test_q_to_tth(wavelength, q, expected_tth): + wavelength_warning_emsg = ( + "No wavelength has been specified. You can continue to use the DiffractionObject, but " + "some of its powerful features will not be available. " + "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " + "you may set do.wavelength = 1.54 with the unit in angstroms." + ) if wavelength is None: - with pytest.warns(UserWarning, match="INFO: no wavelength has been specified"): + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_emsg)): + actual_tth = q_to_tth(q, wavelength) actual_tth = q_to_tth(q, wavelength) else: actual_tth = q_to_tth(q, wavelength) @@ -35,54 +44,54 @@ def test_q_to_tth(wavelength, q, expected_tth): params_q_to_tth_bad = [ # UC1: user specified invalid q values that result in tth > 180 degrees ( - [4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])], - [ - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], + 4 * np.pi, + np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", ), # UC2: user specified a wrong wavelength that result in tth > 180 degrees ( - [100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])], - [ - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], + 100, + np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", ), ] -@pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) -def test_q_to_tth_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - q_to_tth(inputs[1], inputs[0]) +@pytest.mark.parametrize("q, wavelength, expected_error_type, expected_error_msg", params_q_to_tth_bad) +def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): + with pytest.raises(expected_error_type, match=expected_error_msg): + q_to_tth(wavelength, q) params_tth_to_q = [ # UC0: User specified empty tth values (without wavelength) - ([None, np.array([])], np.array([])), + (None, np.array([]), np.array([])), # UC1: User specified empty tth values (with wavelength) - ([4 * np.pi, np.array([])], np.array([])), + (4 * np.pi, np.array([]), np.array([])), # UC2: User specified valid tth values between 0-180 degrees (without wavelength) ( - [None, np.array([0, 30, 60, 90, 120, 180])], + None, + np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), # UC3: User specified valid tth values between 0-180 degrees (with wavelength) # expected q values are sin15, sin30, sin45, sin60, sin90 ( - [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], + 4 * np.pi, + np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), ), ] -@pytest.mark.parametrize("inputs, expected", params_tth_to_q) -def test_tth_to_q(inputs, expected): - actual = tth_to_q(inputs[1], inputs[0]) - assert np.allclose(actual, expected) +@pytest.mark.parametrize("wavelength, tth, expected_q", params_tth_to_q) +def test_tth_to_q(wavelength, tth, expected_q): + actual_q = tth_to_q(tth, wavelength) + assert np.allclose(actual_q, expected_q) params_tth_to_q_bad = [ From d176b6b34b0a504beb6aa73390ad1db89c43b05f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 13 Dec 2024 23:35:29 -0500 Subject: [PATCH 171/445] Remove another duplicate line for actual_tth = q_to_tth(q, wavelength) --- tests/test_transforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 453844c2..19096784 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -34,7 +34,6 @@ def test_q_to_tth(wavelength, q, expected_tth): if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_emsg)): actual_tth = q_to_tth(q, wavelength) - actual_tth = q_to_tth(q, wavelength) else: actual_tth = q_to_tth(q, wavelength) From 92a2c4c50442f91f0950ecf74f5ec3d7a88c1826 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:09:35 -0500 Subject: [PATCH 172/445] Add do_minimal setup to conftest --- tests/conftest.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 09de40f6..894d6b31 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,11 @@ import json from pathlib import Path +import numpy as np import pytest +from diffpy.utils.diffraction_objects import DiffractionObject + @pytest.fixture def user_filesystem(tmp_path): @@ -28,3 +31,9 @@ def _load(filename): return base_path / filename return _load + + +@pytest.fixture +def do_minimal(): + # Create an instance of DiffractionObject with minimal setup + return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="tth", wavelength=1.54) From 56541e5b6ed7e48774cf739947ccab063ca20f4e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:10:35 -0500 Subject: [PATCH 173/445] Refactor and remove input_data func --- src/diffpy/utils/diffraction_objects.py | 100 ++++++------------------ 1 file changed, 26 insertions(+), 74 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index abfd44b0..23afb790 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -36,26 +36,36 @@ def _setter_wmsg(attribute): class DiffractionObject: def __init__( - self, name=None, wavelength=None, scat_quantity=None, metadata=None, xarray=None, yarray=None, xtype=None + self, + xarray, + yarray, + xtype, + wavelength, + 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 = "" + # Check xtype is valid. An empty string is the default value. + 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 - if xarray is None: - xarray = np.empty(0) - if yarray is None: - yarray = np.empty(0) - + self._input_xtype = xtype self._id = uuid.uuid4() - self.input_data(xarray, yarray, xtype) + self._set_xarrays(xarray, xtype) + self._all_arrays[:, 0] = yarray def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -298,8 +308,7 @@ 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.") @@ -327,63 +336,6 @@ def _set_xarrays(self, xarray, xtype): 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" From 1144d1f9c989d16ce1f2cd1ef4e7ff6642461d44 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:11:00 -0500 Subject: [PATCH 174/445] Re-organize test with required parameters of xarray, yarray, wavelength, and xtype --- tests/test_diffraction_objects.py | 116 +++++++++++------------------- 1 file changed, 40 insertions(+), 76 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 50080550..089dfc2a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -11,11 +11,6 @@ from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject params = [ - ( # Default - {}, - {}, - True, - ), ( # Compare same attributes { "name": "same", @@ -41,8 +36,8 @@ { "name": "something", "scat_quantity": "", - "wavelength": None, - "xtype": "", + "wavelength": 0.71, + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -50,8 +45,8 @@ { "name": "something else", "scat_quantity": "", - "wavelength": None, - "xtype": "", + "wavelength": 0.71, + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -62,15 +57,15 @@ { "scat_quantity": "", "wavelength": 0.71, - "xtype": "", + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, }, { "scat_quantity": "", - "wavelength": None, - "xtype": "", + "wavelength": 0.42, + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -81,7 +76,7 @@ { "scat_quantity": "", "wavelength": 0.71, - "xtype": "", + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -89,7 +84,7 @@ { "scat_quantity": "", "wavelength": 0.711, - "xtype": "", + "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -99,16 +94,16 @@ ( # Different scat_quantity { "scat_quantity": "x-ray", - "wavelength": None, - "xtype": "", + "xtype": "tth", + "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, }, { "scat_quantity": "neutron", - "wavelength": None, - "xtype": "", + "xtype": "tth", + "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -118,16 +113,16 @@ ( # Different on_q { "scat_quantity": "", - "wavelength": None, "xtype": "q", + "wavelength": 0.71, "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), "metadata": {}, }, { "scat_quantity": "", - "wavelength": None, "xtype": "q", + "wavelength": 0.71, "xarray": np.array([3.0, 4.0]), "yarray": np.array([100.0, 200.0]), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -137,16 +132,16 @@ ( # Different metadata { "scat_quantity": "", - "wavelength": None, - "xtype": "", + "xtype": "q", + "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 0, "thing2": "thing2"}, }, { "scat_quantity": "", - "wavelength": None, - "xtype": "", + "xtype": "q", + "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -171,7 +166,7 @@ def test_on_xtype(): assert np.allclose(test.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) -def test_init_invalid_xtype(): +def test_init_invalid_xtype(do_minimal): with pytest.raises( ValueError, match=re.escape( @@ -179,7 +174,7 @@ def test_init_invalid_xtype(): f"Please rerun specifying an xtype from {*XQUANTITIES, }" ), ): - DiffractionObject(xtype="invalid_type") + return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="invalid_type", wavelength=1.54) params_index = [ @@ -241,40 +236,6 @@ def test_dump(tmp_path, mocker): tc_params = [ - ( - {}, - { - "_all_arrays": np.empty(shape=(0, 4)), # instantiate empty - "metadata": {}, - "_input_xtype": "", - "name": "", - "scat_quantity": None, - "qmin": np.float64(np.inf), - "qmax": np.float64(0.0), - "tthmin": np.float64(np.inf), - "tthmax": np.float64(0.0), - "dmin": np.float64(np.inf), - "dmax": np.float64(0.0), - "wavelength": None, - }, - ), - ( # instantiate just non-array attributes - {"name": "test", "scat_quantity": "x-ray", "metadata": {"thing": "1", "another": "2"}}, - { - "_all_arrays": np.empty(shape=(0, 4)), - "metadata": {"thing": "1", "another": "2"}, - "_input_xtype": "", - "name": "test", - "scat_quantity": "x-ray", - "qmin": np.float64(np.inf), - "qmax": np.float64(0.0), - "tthmin": np.float64(np.inf), - "tthmax": np.float64(0.0), - "dmin": np.float64(np.inf), - "dmax": np.float64(0.0), - "wavelength": None, - }, - ), ( # instantiate just array attributes { "xarray": np.array([0.0, 90.0, 180.0]), @@ -293,7 +254,7 @@ def test_dump(tmp_path, mocker): "metadata": {}, "_input_xtype": "tth", "name": "", - "scat_quantity": None, + "scat_quantity": "", "qmin": np.float64(0.0), "qmax": np.float64(1.0), "tthmin": np.float64(0.0), @@ -339,6 +300,9 @@ def test_dump(tmp_path, mocker): def test_constructor(inputs, expected): actual = DiffractionObject(**inputs).__dict__ diff = DeepDiff(actual, expected, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") + print("Print diff") + print(diff) + # {'dictionary_item_added': ["root['name']", "root['scat_quantity']"]} assert diff == {} @@ -359,8 +323,8 @@ def test_all_array_getter(): assert np.allclose(actual_do.all_arrays, expected_all_arrays) -def test_all_array_setter(): - actual_do = DiffractionObject() +def test_all_array_setter(do_minimal): + actual_do = do_minimal # Attempt to directly modify the property with pytest.raises( @@ -371,21 +335,21 @@ def test_all_array_setter(): actual_do.all_arrays = np.empty((4, 4)) -def test_id_getter(): - do = DiffractionObject() +def test_id_getter(do_minimal): + do = do_minimal assert hasattr(do, "id") assert isinstance(do.id, UUID) assert len(str(do.id)) == 36 -def test_id_getter_with_mock(mocker): +def test_id_getter_with_mock(mocker, do_minimal): mocker.patch.object(DiffractionObject, "id", new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87")) - do = DiffractionObject() + do = do_minimal assert do.id == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") -def test_id_setter_error(): - do = DiffractionObject() +def test_id_setter_error(do_minimal): + do = do_minimal with pytest.raises( AttributeError, @@ -401,18 +365,18 @@ def test_xarray_yarray_length_mismatch(): "Please re-initialize 'DiffractionObject' or re-run the method 'input_data' " "with 'xarray' and 'yarray' of identical length", ): - DiffractionObject(xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0])) + DiffractionObject( + xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0]), xtype="tth", wavelength=1.54 + ) -def test_input_xtype_getter(): - do = DiffractionObject(xtype="tth") +def test_input_xtype_getter(do_minimal): + do = do_minimal assert do.input_xtype == "tth" -def test_input_xtype_setter_error(): - do = DiffractionObject(xtype="tth") - - # Attempt to directly modify the property +def test_input_xtype_setter_error(do_minimal): + do = do_minimal with pytest.raises( AttributeError, match="Direct modification of attribute 'input_xtype' is not allowed. " From e7b4dc2b53f0007d7f36715ba2d57fcedf37254b Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:15:09 -0500 Subject: [PATCH 175/445] Add input public data back to init func --- src/diffpy/utils/diffraction_objects.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 23afb790..2eddc8a6 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -45,6 +45,12 @@ def __init__( name="", metadata={}, ): + + self._id = uuid.uuid4() + self.input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) + + def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): + # Check xtype is valid. An empty string is the default value. if xtype not in XQUANTITIES: raise ValueError(_xtype_wmsg(xtype)) @@ -63,7 +69,7 @@ def __init__( self.name = name self._input_xtype = xtype - self._id = uuid.uuid4() + self._set_xarrays(xarray, xtype) self._all_arrays[:, 0] = yarray From 6dc2738613ff034efe26ad74cdb651a0ea5e0527 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:16:37 -0500 Subject: [PATCH 176/445] Remove print diff function in pytest for debugging --- tests/test_diffraction_objects.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 089dfc2a..9118fad8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -300,9 +300,6 @@ def test_dump(tmp_path, mocker): def test_constructor(inputs, expected): actual = DiffractionObject(**inputs).__dict__ diff = DeepDiff(actual, expected, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") - print("Print diff") - print(diff) - # {'dictionary_item_added': ["root['name']", "root['scat_quantity']"]} assert diff == {} @@ -348,6 +345,18 @@ def test_id_getter_with_mock(mocker, do_minimal): assert do.id == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") + def input_data( + self, + xarray, + yarray, + xtype, + metadata={}, + scat_quantity=None, + name=None, + wavelength=None, + ): + + def test_id_setter_error(do_minimal): do = do_minimal From 50b7d0d9c867d060d6be6243920653f12fa46a70 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:30:32 -0500 Subject: [PATCH 177/445] Update input_data docstrings for clarity --- src/diffpy/utils/diffraction_objects.py | 28 ++++++++++++++++++++++++- tests/test_diffraction_objects.py | 12 ----------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 2eddc8a6..d49be5d2 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -49,7 +49,33 @@ def __init__( self._id = uuid.uuid4() self.input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) - def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): + def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity=None, name="", metadata={}): + """ + Insert a new scattering quantity into the scattering object. + + Parameters + ---------- + xarray : array-like + The independent variable array (e.g., q, tth, or d). + 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}, + such as "q", "tth", or "d". + wavelength : float + The wavelength of the incoming beam, specified in angstroms (Å). + scat_quantity : str, optional + The type of scattering experiment (e.g., "x-ray", "neutron"). Default is "". + 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 {}. + + Returns + ------- + None + This method updates the object in place and does not return a value. + """ # Check xtype is valid. An empty string is the default value. if xtype not in XQUANTITIES: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9118fad8..d21591cf 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -345,18 +345,6 @@ def test_id_getter_with_mock(mocker, do_minimal): assert do.id == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") - def input_data( - self, - xarray, - yarray, - xtype, - metadata={}, - scat_quantity=None, - name=None, - wavelength=None, - ): - - def test_id_setter_error(do_minimal): do = do_minimal From 291cc89852387e32f326559557657893cabfa62e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:31:27 -0500 Subject: [PATCH 178/445] Add quotation marks around q, tth, d in docstrings --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index d49be5d2..c3c1d3ee 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -56,7 +56,7 @@ def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity=None, name Parameters ---------- xarray : array-like - The independent variable array (e.g., q, tth, or d). + The independent variable array (e.g., "q", "tth", or "d"). yarray : array-like The dependent variable array corresponding to intensity values xtype : str From b8a9adc52d69d197966014aabed7b4817f99c145 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:32:10 -0500 Subject: [PATCH 179/445] Use empty string for default scattering quantity --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index c3c1d3ee..7e704df1 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -49,7 +49,7 @@ def __init__( self._id = uuid.uuid4() self.input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) - def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity=None, name="", metadata={}): + def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity="", name="", metadata={}): """ Insert a new scattering quantity into the scattering object. From e3c52089a5a31f450eb819b4550894c191f4c586 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 01:35:05 -0500 Subject: [PATCH 180/445] Final cleanup on input_data docstrings --- src/diffpy/utils/diffraction_objects.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 7e704df1..fc86984e 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -58,14 +58,14 @@ def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity="", name=" xarray : array-like The independent variable array (e.g., "q", "tth", or "d"). yarray : array-like - The dependent variable array corresponding to intensity values + The dependent variable array corresponding to intensity values. xtype : str The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}, such as "q", "tth", or "d". wavelength : float The wavelength of the incoming beam, specified in angstroms (Å). scat_quantity : str, optional - The type of scattering experiment (e.g., "x-ray", "neutron"). Default is "". + 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 @@ -95,7 +95,6 @@ def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity="", name=" self.name = name self._input_xtype = xtype - self._set_xarrays(xarray, xtype) self._all_arrays[:, 0] = yarray From 69aa7c7665b1171e64b20c2e1c6d77ebb743fdd6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 11:25:03 -0500 Subject: [PATCH 181/445] Improve wavelength warning msg --- src/diffpy/utils/transforms.py | 2 +- tests/test_transforms.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 77df975e..fa3e8747 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -7,7 +7,7 @@ "No wavelength has been specified. You can continue to use the DiffractionObject, but " "some of its powerful features will not be available. " "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " - "you may set do.wavelength = 1.54 with the unit in angstroms." + "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." ) invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." invalid_q_or_d_or_wavelength_emsg = ( diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 19096784..909026e6 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -29,7 +29,7 @@ def test_q_to_tth(wavelength, q, expected_tth): "No wavelength has been specified. You can continue to use the DiffractionObject, but " "some of its powerful features will not be available. " "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " - "you may set do.wavelength = 1.54 with the unit in angstroms." + "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." ) if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_emsg)): From 53413fb9038e00e5dae3b206a0a5742ec8dbd0a4 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 14 Dec 2024 11:51:20 -0500 Subject: [PATCH 182/445] make tests more readable --- src/diffpy/utils/diffraction_objects.py | 8 +- tests/test_diffraction_objects.py | 202 +++++++++++++----------- 2 files changed, 114 insertions(+), 96 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 0813ef9b..7c8e3aee 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -405,7 +405,8 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): the diffraction object you want to scale the current one onto q, tth, d : float, optional, must specify exactly one of them - the xvalue (in `q`, `tth`, or `d` space) to align the current and target objects + The value of the x-array where you want the curves to line up vertically. + Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. offset : float, optional, default is 0 an offset to add to the scaled y-values @@ -421,8 +422,9 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): "You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ) - xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" - data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) + xtype = "q" if q is not None else "tth" if tth is not None else "d" + data = self.on_xtype(xtype) + target = target_diff_object.on_xtype(xtype) xvalue = q if xtype == "q" else tth if xtype == "tth" else d diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index bd8c6c6e..8a2ed670 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -183,137 +183,153 @@ def test_init_invalid_xtype(): params_scale_to = [ # UC1: same x-array and y-array, check offset ( - [ - np.array([10, 15, 25, 30, 60, 140]), - np.array([2, 3, 4, 5, 6, 7]), - "tth", - 2 * np.pi, - np.array([10, 15, 25, 30, 60, 140]), - np.array([2, 3, 4, 5, 6, 7]), - "tth", - 2 * np.pi, - None, - 60, - None, - 2.1, - ], - ["tth", np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])], + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([10, 15, 25, 30, 60, 140]), + "target_yarray": np.array([2, 3, 4, 5, 6, 7]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": None, + "offset": 2.1, + }, + {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), # UC2: same length x-arrays with exact x-value match ( - [ - np.array([10, 15, 25, 30, 60, 140]), - np.array([10, 20, 25, 30, 60, 100]), - "tth", - 2 * np.pi, - np.array([10, 20, 25, 30, 60, 140]), - np.array([2, 3, 4, 5, 6, 7]), - "tth", - 2 * np.pi, - None, - 60, - None, - 0, - ], - ["tth", np.array([1, 2, 2.5, 3, 6, 10])], + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([10, 20, 25, 30, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([10, 20, 25, 30, 60, 140]), + "target_yarray": np.array([2, 3, 4, 5, 6, 7]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": None, + "offset": 0, + }, + {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), # UC3: same length x-arrays with approximate x-value match ( - [ - np.array([0.12, 0.24, 0.31, 0.4]), - np.array([10, 20, 40, 60]), - "q", - 2 * np.pi, - np.array([0.14, 0.24, 0.31, 0.4]), - np.array([1, 3, 4, 5]), - "q", - 2 * np.pi, - 0.1, - None, - None, - 0, - ], - ["q", np.array([1, 2, 4, 6])], + { + "xarray": np.array([0.12, 0.24, 0.31, 0.4]), + "yarray": np.array([10, 20, 40, 60]), + "xtype": "q", + "wavelength": 2 * np.pi, + "target_xarray": np.array([0.14, 0.24, 0.31, 0.4]), + "target_yarray": np.array([1, 3, 4, 5]), + "target_xtype": "q", + "target_wavelength": 2 * np.pi, + "q": 0.1, + "tth": None, + "d": None, + "offset": 0, + }, + {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), # UC4: different x-array lengths with approximate x-value match ( - [ - np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - np.array([10, 20, 30, 40, 50, 60, 100]), - "tth", - 2 * np.pi, - np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "tth", - 2 * np.pi, - None, - 60, - None, - 0, - ], - # scaling factor is calculated at index = 5 for self and index = 6 for target - ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": None, + "offset": 0, + }, + # scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) + {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), ] @pytest.mark.parametrize("inputs, expected", params_scale_to) def test_scale_to(inputs, expected): - orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + orig_diff_object = DiffractionObject( + xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] + ) target_diff_object = DiffractionObject( - xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + xarray=inputs["target_xarray"], + yarray=inputs["target_yarray"], + xtype=inputs["target_xtype"], + wavelength=inputs["target_wavelength"], ) scaled_diff_object = orig_diff_object.scale_to( - target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11] + target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] ) - # Check the intensity data is same as expected - assert np.allclose(scaled_diff_object.on_xtype(expected[0])[1], expected[1]) + # Check the intensity data is the same as expected + assert np.allclose(scaled_diff_object.on_xtype(expected["xtype"])[1], expected["yarray"]) params_scale_to_bad = [ # UC1: user did not specify anything ( - np.array([0.1, 0.2, 0.3]), - np.array([1, 2, 3]), - "q", - 2 * np.pi, - np.array([0.05, 0.1, 0.2, 0.3]), - np.array([5, 10, 20, 30]), - "q", - 2 * np.pi, - None, - None, - None, - 0, + { + "xarray": np.array([0.1, 0.2, 0.3]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + "wavelength": 2 * np.pi, + "target_xarray": np.array([0.05, 0.1, 0.2, 0.3]), + "target_yarray": np.array([5, 10, 20, 30]), + "target_xtype": "q", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": None, + "d": None, + "offset": 0, + } ), # UC2: user specified more than one of q, tth, and d ( - np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - np.array([10, 20, 30, 40, 50, 60, 100]), - "tth", - 2 * np.pi, - np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "tth", - 2 * np.pi, - None, - 60, - 10, - 0, + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": 10, + "offset": 0, + } ), ] @pytest.mark.parametrize("inputs", params_scale_to_bad) def test_scale_to_bad(inputs): - orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + orig_diff_object = DiffractionObject( + xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] + ) target_diff_object = DiffractionObject( - xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + xarray=inputs["target_xarray"], + yarray=inputs["target_yarray"], + xtype=inputs["target_xtype"], + wavelength=inputs["target_wavelength"], ) with pytest.raises( ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ): - orig_diff_object.scale_to(target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11]) + orig_diff_object.scale_to( + target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] + ) params_index = [ From de6f12d8fe5f53ea1bded2da97d58bfc1ebb99b8 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:41:39 -0500 Subject: [PATCH 183/445] make wavelength optional, test invalid init args --- src/diffpy/utils/diffraction_objects.py | 87 +++++++++++++++---------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 8765a258..ed42ba83 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -35,49 +35,64 @@ 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 valuester + >>> metadata = { + ... "package_info": {"version": "3.6.0"} + ... } + >>> do = DiffractionObject( + ... xarray=x, + ... yarray=y, + ... xtype="q", + ... wavelength=1.54, + ... scat_quantity="x-ray", + ... metadata=metadata + ... ) + >>> print(do.metadata) + """ + def __init__( self, xarray, yarray, xtype, - wavelength, + wavelength=None, scat_quantity="", name="", metadata={}, ): self._id = uuid.uuid4() - self.input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) - - def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity="", name="", metadata={}): - """ - Insert a new scattering quantity into the scattering object. - - Parameters - ---------- - xarray : array-like - The independent variable array (e.g., "q", "tth", or "d"). - 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}, - such as "q", "tth", or "d". - wavelength : float - The wavelength of the incoming beam, specified in angstroms (Å). - 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 {}. - - Returns - ------- - None - This method updates the object in place and does not return a value. - """ + self._input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) - # Check xtype is valid. An empty string is the default value. + def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): if xtype not in XQUANTITIES: raise ValueError(_xtype_wmsg(xtype)) @@ -93,10 +108,9 @@ def input_data(self, xarray, yarray, xtype, wavelength, scat_quantity="", name=" self.wavelength = wavelength self.metadata = metadata self.name = name - self._input_xtype = xtype - self._set_xarrays(xarray, xtype) - self._all_arrays[:, 0] = yarray + self._set_arrays(xarray, xtype, yarray) + self._set_min_max_xarray() def __eq__(self, other): if not isinstance(other, DiffractionObject): @@ -346,8 +360,9 @@ def get_array_index(self, value, xtype=None): i = (np.abs(array - value)).argmin() return i - def _set_xarrays(self, xarray, xtype): + def _set_arrays(self, xarray, xtype, yarray): 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) @@ -360,6 +375,8 @@ 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) From 7ccebe86ea05baf1db7d24416809f2992f1fb8f0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:41:59 -0500 Subject: [PATCH 184/445] Add wavelength user behavior back --- tests/test_diffraction_objects.py | 47 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 7f18f29e..7ef5f945 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -15,7 +15,6 @@ { "name": "same", "scat_quantity": "x-ray", - "wavelength": 0.71, "xtype": "q", "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), @@ -24,7 +23,6 @@ { "name": "same", "scat_quantity": "x-ray", - "wavelength": 0.71, "xtype": "q", "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), @@ -35,8 +33,6 @@ ( # Different names { "name": "something", - "scat_quantity": "", - "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), @@ -44,8 +40,6 @@ }, { "name": "something else", - "scat_quantity": "", - "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), @@ -55,7 +49,6 @@ ), ( # Different wavelengths { - "scat_quantity": "", "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), @@ -63,7 +56,6 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, { - "scat_quantity": "", "wavelength": 0.42, "xtype": "tth", "xarray": np.empty(0), @@ -74,7 +66,6 @@ ), ( # Different wavelengths { - "scat_quantity": "", "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), @@ -82,7 +73,6 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, { - "scat_quantity": "", "wavelength": 0.711, "xtype": "tth", "xarray": np.empty(0), @@ -112,15 +102,12 @@ ), ( # Different on_q { - "scat_quantity": "", "xtype": "q", "wavelength": 0.71, "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), - "metadata": {}, }, { - "scat_quantity": "", "xtype": "q", "wavelength": 0.71, "xarray": np.array([3.0, 4.0]), @@ -131,7 +118,6 @@ ), ( # Different metadata { - "scat_quantity": "", "xtype": "q", "wavelength": 0.71, "xarray": np.empty(0), @@ -139,7 +125,6 @@ "metadata": {"thing1": 0, "thing2": "thing2"}, }, { - "scat_quantity": "", "xtype": "q", "wavelength": 0.71, "xarray": np.empty(0), @@ -166,7 +151,7 @@ def test_on_xtype(): assert np.allclose(do.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) -def test_init_invalid_xtype(do_minimal): +def test_init_invalid_xtype(): with pytest.raises( ValueError, match=re.escape( @@ -387,7 +372,8 @@ def test_dump(tmp_path, mocker): assert actual == expected -tc_params = [ + +@pytest.mark.parametrize("init_args, expected_do_dict", [ ( # instantiate just array attributes { "xarray": np.array([0.0, 90.0, 180.0]), @@ -445,14 +431,27 @@ def test_dump(tmp_path, mocker): "wavelength": 4.0 * np.pi, }, ), -] +]) +def test_init_valid(init_args, expected_do_dict): + actual_do_dict = DiffractionObject(**init_args).__dict__ + diff = DeepDiff(actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") + assert diff == {} -@pytest.mark.parametrize("inputs, expected", tc_params) -def test_constructor(inputs, expected): - actual = DiffractionObject(**inputs).__dict__ - diff = DeepDiff(actual, expected, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") - assert diff == {} + +@pytest.mark.parametrize("init_args, error_message", [ + ( # UC1: no arguments provided + {}, + "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ), + ( # UC2: only xarray and yarray provided + {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, + "missing 1 required positional argument: 'xtype'", + ), +]) +def test_init_invalid_args(init_args, error_message): + with pytest.raises(TypeError, match=error_message): + DiffractionObject(**init_args) def test_all_array_getter(): @@ -473,7 +472,7 @@ def test_all_array_getter(): def test_all_array_setter(do_minimal): - actual_do = do_minimal + do = do_minimal # Attempt to directly modify the property with pytest.raises( AttributeError, From 2752578001a76e8458e02a966e7f2adfb7ce3099 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 14 Dec 2024 18:42:08 +0000 Subject: [PATCH 185/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 150 ++++++++++++++++-------------- 1 file changed, 78 insertions(+), 72 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 7ef5f945..9fef29fa 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -372,83 +372,89 @@ def test_dump(tmp_path, mocker): assert actual == expected - -@pytest.mark.parametrize("init_args, expected_do_dict", [ - ( # instantiate just array attributes - { - "xarray": np.array([0.0, 90.0, 180.0]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "tth", - "wavelength": 4.0 * np.pi, - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "tth", - "name": "", - "scat_quantity": "", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), - ( # instantiate just array attributes - { - "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "d", - "wavelength": 4.0 * np.pi, - "scat_quantity": "x-ray", - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "d", - "name": "", - "scat_quantity": "x-ray", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), -]) +@pytest.mark.parametrize( + "init_args, expected_do_dict", + [ + ( # instantiate just array attributes + { + "xarray": np.array([0.0, 90.0, 180.0]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "tth", + "wavelength": 4.0 * np.pi, + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "tth", + "name": "", + "scat_quantity": "", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ( # instantiate just array attributes + { + "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "d", + "wavelength": 4.0 * np.pi, + "scat_quantity": "x-ray", + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "d", + "name": "", + "scat_quantity": "x-ray", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ], +) def test_init_valid(init_args, expected_do_dict): actual_do_dict = DiffractionObject(**init_args).__dict__ - diff = DeepDiff(actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']") + diff = DeepDiff( + actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']" + ) assert diff == {} - -@pytest.mark.parametrize("init_args, error_message", [ - ( # UC1: no arguments provided - {}, - "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", - ), - ( # UC2: only xarray and yarray provided - {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, - "missing 1 required positional argument: 'xtype'", - ), -]) +@pytest.mark.parametrize( + "init_args, error_message", + [ + ( # UC1: no arguments provided + {}, + "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ), + ( # UC2: only xarray and yarray provided + {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, + "missing 1 required positional argument: 'xtype'", + ), + ], +) def test_init_invalid_args(init_args, error_message): with pytest.raises(TypeError, match=error_message): DiffractionObject(**init_args) From d1f767b98c7c80a5688bc2bb0077c4bf440bb673 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:43:39 -0500 Subject: [PATCH 186/445] Restore wavelenght none in one of the test cases --- tests/test_diffraction_objects.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9fef29fa..9b6769b7 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -103,13 +103,11 @@ ( # Different on_q { "xtype": "q", - "wavelength": 0.71, "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), }, { "xtype": "q", - "wavelength": 0.71, "xarray": np.array([3.0, 4.0]), "yarray": np.array([100.0, 200.0]), "metadata": {"thing1": 1, "thing2": "thing2"}, From afe814b82d09e6bceeba64b3716aa10a2bae4ad8 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:46:16 -0500 Subject: [PATCH 187/445] Fix typo in docstrings --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ed42ba83..6aa54682 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -63,7 +63,7 @@ class DiffractionObject: >>> 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 valuester + >>> y = np.array([10, 20, 40, 60]) # intensity values >>> metadata = { ... "package_info": {"version": "3.6.0"} ... } From 46c7be89e9cb53433bbf4b428dd16817a5be3f5a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:56:07 -0500 Subject: [PATCH 188/445] Use refined param names for pytest parametrize --- tests/test_diffraction_objects.py | 154 ++++++++++++++++-------------- 1 file changed, 80 insertions(+), 74 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9b6769b7..ecd5db16 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -370,67 +370,70 @@ def test_dump(tmp_path, mocker): assert actual == expected +test_init_valid_params = [ + ( # instantiate just array attributes + { + "xarray": np.array([0.0, 90.0, 180.0]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "tth", + "wavelength": 4.0 * np.pi, + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "tth", + "name": "", + "scat_quantity": "", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ( # instantiate just array attributes + { + "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "d", + "wavelength": 4.0 * np.pi, + "scat_quantity": "x-ray", + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "d", + "name": "", + "scat_quantity": "x-ray", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), +] + + @pytest.mark.parametrize( "init_args, expected_do_dict", - [ - ( # instantiate just array attributes - { - "xarray": np.array([0.0, 90.0, 180.0]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "tth", - "wavelength": 4.0 * np.pi, - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "tth", - "name": "", - "scat_quantity": "", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), - ( # instantiate just array attributes - { - "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "d", - "wavelength": 4.0 * np.pi, - "scat_quantity": "x-ray", - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "d", - "name": "", - "scat_quantity": "x-ray", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), - ], + test_init_valid_params, ) def test_init_valid(init_args, expected_do_dict): actual_do_dict = DiffractionObject(**init_args).__dict__ @@ -440,21 +443,24 @@ def test_init_valid(init_args, expected_do_dict): assert diff == {} -@pytest.mark.parametrize( - "init_args, error_message", - [ - ( # UC1: no arguments provided - {}, - "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", - ), - ( # UC2: only xarray and yarray provided - {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, - "missing 1 required positional argument: 'xtype'", - ), - ], -) -def test_init_invalid_args(init_args, error_message): - with pytest.raises(TypeError, match=error_message): +test_init_invalid_params = [ + ( # UC1: no arguments provided + {}, + "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ), + ( # UC2: only xarray and yarray provided + {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, + "missing 1 required positional argument: 'xtype'", + ), +] + + +@pytest.mark.parametrize("init_args, expected_error_msg", test_init_invalid_params) +def test_init_invalid_args( + init_args, + expected_error_msg, +): + with pytest.raises(TypeError, match=expected_error_msg): DiffractionObject(**init_args) From d24aacdb5ffea1778f4602f71fdd585f70babfac Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 13:58:25 -0500 Subject: [PATCH 189/445] Remove hard coded wavelength in test func --- tests/test_diffraction_objects.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ecd5db16..992be091 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -85,7 +85,6 @@ { "scat_quantity": "x-ray", "xtype": "tth", - "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -93,7 +92,6 @@ { "scat_quantity": "neutron", "xtype": "tth", - "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, @@ -117,14 +115,12 @@ ( # Different metadata { "xtype": "q", - "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 0, "thing2": "thing2"}, }, { "xtype": "q", - "wavelength": 0.71, "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, From e285214524534ee10c4335c9277edec5e45e9b8a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 14:08:03 -0500 Subject: [PATCH 190/445] Add news for requires 3 input parameters of , , --- news/no-empty-object.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/no-empty-object.rst diff --git a/news/no-empty-object.rst b/news/no-empty-object.rst new file mode 100644 index 00000000..02126c98 --- /dev/null +++ b/news/no-empty-object.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* `DiffractionObject` requires 3 input parameters of `xarrays`, `yarrays`, `xtype` + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 1ee215716b2a30b5230519a266f4eb3d719faf1d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 14:22:35 -0500 Subject: [PATCH 191/445] Refactor test_on_xtype using a minimal do defined in conftest --- tests/conftest.py | 8 ++++++++ tests/test_diffraction_objects.py | 20 ++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 09de40f6..c0767b49 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,11 @@ import json from pathlib import Path +import numpy as np import pytest +from diffpy.utils.diffraction_objects import DiffractionObject + @pytest.fixture def user_filesystem(tmp_path): @@ -28,3 +31,8 @@ def _load(filename): return base_path / filename return _load + + +@pytest.fixture +def do_minimal_tth(): + return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 65519eac..74e0e3cd 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -163,12 +163,20 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert (do_1 == do_2) == expected -def test_on_xtype(): - do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") - assert np.allclose(do.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])]) - assert np.allclose(do.on_xtype("2theta"), [np.array([30, 60]), np.array([1, 2])]) - assert np.allclose(do.on_xtype("q"), [np.array([0.51764, 1]), np.array([1, 2])]) - assert np.allclose(do.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])]) +@pytest.mark.parametrize( + "input_xtype, expected_xarray", + [ + ("tth", np.array([30, 60])), + ("2theta", np.array([30, 60])), + ("q", np.array([0.51764, 1])), + ("d", np.array([12.13818, 6.28319])), + ], +) +def test_on_xtype(input_xtype, expected_xarray, do_minimal_tth): + do = do_minimal_tth + result = do.on_xtype(input_xtype) + assert np.allclose(result[0], expected_xarray) + assert np.allclose(result[1], np.array([1, 2])) def test_init_invalid_xtype(): From 8053d315c22c9454c2071e35c1b17b9a24d8b57c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 14:30:11 -0500 Subject: [PATCH 192/445] improve test variable name --- tests/test_diffraction_objects.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 74e0e3cd..4d210e3e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -164,7 +164,7 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): @pytest.mark.parametrize( - "input_xtype, expected_xarray", + "xtype, expected_xarray", [ ("tth", np.array([30, 60])), ("2theta", np.array([30, 60])), @@ -172,11 +172,11 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): ("d", np.array([12.13818, 6.28319])), ], ) -def test_on_xtype(input_xtype, expected_xarray, do_minimal_tth): +def test_on_xtype(xtype, expected_xarray, do_minimal_tth): do = do_minimal_tth - result = do.on_xtype(input_xtype) - assert np.allclose(result[0], expected_xarray) - assert np.allclose(result[1], np.array([1, 2])) + actual_xrray, actual_yarray = do.on_xtype(xtype) + assert np.allclose(actual_xrray, expected_xarray) + assert np.allclose(actual_yarray, np.array([1, 2])) def test_init_invalid_xtype(): From 3a3fcecd2ca0fffaf4862e42ab73f238be9f17dd Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 15:04:27 -0500 Subject: [PATCH 193/445] Remove set_angles_from_list, set_angles_from, set_qs_from_range and add news --- news/rm-range-methods.rst | 23 +++++++++ src/diffpy/utils/diffraction_objects.py | 67 ------------------------- 2 files changed, 23 insertions(+), 67 deletions(-) create mode 100644 news/rm-range-methods.rst diff --git a/news/rm-range-methods.rst b/news/rm-range-methods.rst new file mode 100644 index 00000000..8150da16 --- /dev/null +++ b/news/rm-range-methods.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* `set_angles_from_list`, `set_angles_from`, `set_qs_from_range` methods in `DiffractionObject` + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index b5179874..a242c895 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -215,73 +215,6 @@ def id(self): def id(self, _): raise AttributeError(_setter_wmsg("id")) - def set_angles_from_list(self, angles_list): - self.angles = angles_list - self.n_steps = len(angles_list) - 1.0 - self.begin_angle = self.angles[0] - self.end_angle = self.angles[-1] - - def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None): - """ - create an array of linear spaced Q-values - - Parameters - ---------- - begin_q float - the beginning angle - end_q float - the ending angle - step_size float - the size of the step between points. Only specify step_size or n_steps, not both - n_steps integer - the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both - - Returns - ------- - Sets self.qs - self.qs array of floats - the q values in the independent array - - """ - self.qs = self._set_array_from_range(begin_q, end_q, step_size=step_size, n_steps=n_steps) - - def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps=None): - """ - create an array of linear spaced angle-values - - Parameters - ---------- - begin_angle float - the beginning angle - end_angle float - the ending angle - step_size float - the size of the step between points. Only specify step_size or n_steps, not both - n_steps integer - the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both - - Returns - ------- - Sets self.angles - self.angles array of floats - the q values in the independent array - - """ - self.angles = self._set_array_from_range(begin_angle, end_angle, step_size=step_size, n_steps=n_steps) - - def _set_array_from_range(self, begin, end, step_size=None, n_steps=None): - if step_size is not None and n_steps is not None: - print( - "WARNING: both step_size and n_steps have been given. n_steps will be used and step_size will be " - "reset." - ) - array = np.linspace(begin, end, n_steps) - elif step_size is not None: - array = np.arange(begin, end, step_size) - elif n_steps is not None: - array = np.linspace(begin, end, n_steps) - return array - def get_array_index(self, value, xtype=None): """ Return the index of the closest value in the array associated with the specified xtype. From 784bc65f9182c5d035c7ac6fe7f75002f23f2843 Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Sat, 14 Dec 2024 16:53:43 -0500 Subject: [PATCH 194/445] add deprecation warning to resample function --- news/resample-dep.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/resampler.py | 9 +++++++++ 2 files changed, 32 insertions(+) create mode 100644 news/resample-dep.rst diff --git a/news/resample-dep.rst b/news/resample-dep.rst new file mode 100644 index 00000000..91bb3e3e --- /dev/null +++ b/news/resample-dep.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* `resample` function in resampler. Replaced with `wsinterp` with better functionality. + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 5f6062d2..07f915f9 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -15,6 +15,8 @@ """Various utilities related to data parsing and manipulation.""" +import warnings + import numpy as np @@ -97,6 +99,13 @@ def resample(r, s, dr): Returns resampled (r, s). """ + warnings.warn( + "The 'resample' function is deprecated and will be removed in a future release (3.8.0). \n" + "'resample' has been renamed 'wsinterp' to better reflect functionality. Please use 'wsinterp' instead.", + DeprecationWarning, + stacklevel=2, + ) + dr0 = r[1] - r[0] # Constant timestep if dr0 < dr: From ebfd3edb2d710a8a13b8c5ae53e49361f6b09b5f Mon Sep 17 00:00:00 2001 From: Sparks29032 Date: Sat, 14 Dec 2024 20:02:10 -0500 Subject: [PATCH 195/445] nsinterp --- doc/source/examples/resampleexample.rst | 10 ++++++- news/nsinterp.rst | 23 ++++++++++++++ src/diffpy/utils/resampler.py | 40 +++++++++++++++++++++++++ tests/test_resample.py | 22 +++++++++++++- 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 news/nsinterp.rst diff --git a/doc/source/examples/resampleexample.rst b/doc/source/examples/resampleexample.rst index f7ba5e18..2757b791 100644 --- a/doc/source/examples/resampleexample.rst +++ b/doc/source/examples/resampleexample.rst @@ -58,7 +58,7 @@ given enough datapoints. nickel_resample = wsinterp(grid, nickel_grid, nickel_func) target_resample = wsinterp(grid, target_grid, target_func) - We can now plot the difference to see that these two functions are quite similar.: + We can now plot the difference to see that these two functions are quite similar.:: plt.plot(grid, target_resample) plt.plot(grid, nickel_resample) @@ -78,3 +78,11 @@ given enough datapoints. In the case of our dataset, our band-limit is ``qmax=25.0`` and our function spans :math:`r \in (0.0, 60.0)`. Thus, our original grid requires :math:`25.0 * 60.0 / \pi < 478`. Since our grid has :math:`601` datapoints, our reconstruction was perfect as shown from the comparison between ``Nickel.gr`` and ``NiTarget.gr``. + + This computation is implemented in the function ``nsinterp``.:: + + from diffpy.utils.resampler import nsinterp + qmin = 0 + qmax = 25 + nickel_resample = (nickel_grid, nickel_func, qmin, qmax) + diff --git a/news/nsinterp.rst b/news/nsinterp.rst new file mode 100644 index 00000000..9b716b87 --- /dev/null +++ b/news/nsinterp.rst @@ -0,0 +1,23 @@ +**Added:** + +* Function nsinterp for automatic interpolation onto the Nyquist-Shannon grid. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 5f6062d2..76108404 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -77,6 +77,46 @@ def wsinterp(x, xp, fp, left=None, right=None): return fp_at_x +def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): + """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon grid. + + Takes a band-limited function fp and original grid xp and resamples fp on the NS grid. + Uses the minimum number of points N required by the Nyquist sampling theorem. + N = (qmax-qmin)(rmax-rmin)/pi, where rmin and rmax are the ends of the real-space ranges. + fp must be finite, and the user inputs qmin and qmax of the frequency-domain. + + Parameters + ---------- + xp: ndarray + The array of known x values. + fp: ndarray + The array of y values associated with xp. + qmin: float + The lower band limit in the frequency domain. + qmax: float + The upper band limit in the frequency domain. + + Returns + ------- + x: ndarray + The Nyquist-Shannon grid computed for the given qmin and qmax. + fp_at_x: ndarray + The interpolated values at points x. Returns a single float if x is a scalar, + otherwise returns a numpy.ndarray. + """ + # Ensure numpy array + xp = np.array(xp) + rmin = np.min(xp) + rmax = np.max(xp) + + nspoints = int(np.round((qmax-qmin)*(rmax-rmin)/np.pi)) + + x = np.linspace(rmin, rmax, nspoints) + fp_at_x = wsinterp(x, xp, fp) + + return x, fp_at_x + + def resample(r, s, dr): """Resample a PDF on a new grid. diff --git a/tests/test_resample.py b/tests/test_resample.py index 784932bb..e43af070 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from diffpy.utils.resampler import wsinterp +from diffpy.utils.resampler import wsinterp, nsinterp def test_wsinterp(): @@ -30,3 +30,23 @@ def test_wsinterp(): assert np.allclose(fp_at_x[1:-1], fp) for i in range(len(x)): assert fp_at_x[i] == pytest.approx(wsinterp(x[i], xp, fp)) + + +def test_nsinterp(): + # Create a cosine function cos(2x) for x \in [0, 3pi] + xp = np.linspace(0, 3*np.pi, 100) + fp = np.cos(2 * xp) + + # Want to resample onto the grid {0, pi, 2pi, 3pi} + x = np.array([0, np.pi, 2*np.pi, 3*np.pi]) + + # Get wsinterp result + ws_f = wsinterp(x, xp, fp) + + # Use nsinterp with qmin-qmax=4/3 + qmin = np.random.uniform() + qmax = qmin + 4/3 + ns_x, ns_f = nsinterp(xp, fp, qmin, qmax) + + assert np.allclose(x, ns_x) + assert np.allclose(ws_f, ns_f) From b1213b1f557de11f9aa20b6ad20974fbe50fb598 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 01:07:07 +0000 Subject: [PATCH 196/445] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/examples/resampleexample.rst | 1 - src/diffpy/utils/resampler.py | 2 +- tests/test_resample.py | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/source/examples/resampleexample.rst b/doc/source/examples/resampleexample.rst index 2757b791..3166e42b 100644 --- a/doc/source/examples/resampleexample.rst +++ b/doc/source/examples/resampleexample.rst @@ -85,4 +85,3 @@ given enough datapoints. qmin = 0 qmax = 25 nickel_resample = (nickel_grid, nickel_func, qmin, qmax) - diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 76108404..890f29fb 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -109,7 +109,7 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): rmin = np.min(xp) rmax = np.max(xp) - nspoints = int(np.round((qmax-qmin)*(rmax-rmin)/np.pi)) + nspoints = int(np.round((qmax - qmin) * (rmax - rmin) / np.pi)) x = np.linspace(rmin, rmax, nspoints) fp_at_x = wsinterp(x, xp, fp) diff --git a/tests/test_resample.py b/tests/test_resample.py index e43af070..6e6294ed 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from diffpy.utils.resampler import wsinterp, nsinterp +from diffpy.utils.resampler import nsinterp, wsinterp def test_wsinterp(): @@ -34,18 +34,18 @@ def test_wsinterp(): def test_nsinterp(): # Create a cosine function cos(2x) for x \in [0, 3pi] - xp = np.linspace(0, 3*np.pi, 100) + xp = np.linspace(0, 3 * np.pi, 100) fp = np.cos(2 * xp) # Want to resample onto the grid {0, pi, 2pi, 3pi} - x = np.array([0, np.pi, 2*np.pi, 3*np.pi]) + x = np.array([0, np.pi, 2 * np.pi, 3 * np.pi]) # Get wsinterp result ws_f = wsinterp(x, xp, fp) # Use nsinterp with qmin-qmax=4/3 qmin = np.random.uniform() - qmax = qmin + 4/3 + qmax = qmin + 4 / 3 ns_x, ns_f = nsinterp(xp, fp, qmin, qmax) assert np.allclose(x, ns_x) From 5d88cf6595e67cb923d007397e4c907c8b85bd76 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 14 Dec 2024 23:45:12 -0500 Subject: [PATCH 197/445] Remove 2 wavelength warnings with test_tth_to_q func --- tests/conftest.py | 9 +++++++ tests/test_transforms.py | 51 +++++++++++++++++++++------------------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c0767b49..44f66178 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,3 +36,12 @@ def _load(filename): @pytest.fixture def do_minimal_tth(): return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") + +@pytest.fixture +def wavelength_warning_msg(): + return ( + "No wavelength has been specified. You can continue to use the DiffractionObject, but " + "some of its powerful features will not be available. " + "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " + "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." +) \ No newline at end of file diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 909026e6..e9bb54bf 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,6 +5,7 @@ from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q + params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0))), @@ -23,16 +24,10 @@ @pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) -def test_q_to_tth(wavelength, q, expected_tth): - - wavelength_warning_emsg = ( - "No wavelength has been specified. You can continue to use the DiffractionObject, but " - "some of its powerful features will not be available. " - "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " - "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." - ) +def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): + if wavelength is None: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_emsg)): + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): actual_tth = q_to_tth(q, wavelength) else: actual_tth = q_to_tth(q, wavelength) @@ -66,7 +61,7 @@ def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): q_to_tth(wavelength, q) -params_tth_to_q = [ +test_tth_t_q_params = [ # UC0: User specified empty tth values (without wavelength) (None, np.array([]), np.array([])), # UC1: User specified empty tth values (with wavelength) @@ -86,31 +81,38 @@ def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): ), ] - -@pytest.mark.parametrize("wavelength, tth, expected_q", params_tth_to_q) -def test_tth_to_q(wavelength, tth, expected_q): - actual_q = tth_to_q(tth, wavelength) +@pytest.mark.parametrize("wavelength, tth, expected_q", test_tth_t_q_params) +def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): + if wavelength is None: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + actual_q = tth_to_q(tth, wavelength) + else: + actual_q = tth_to_q(tth, wavelength) + assert np.allclose(actual_q, expected_q) - -params_tth_to_q_bad = [ +tth_to_q_bad_params = [ # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) ( - [None, np.array([0, 30, 60, 90, 120, 181])], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + None, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", ), # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) ( - [4 * np.pi, np.array([0, 30, 60, 90, 120, 181])], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + 4 * np.pi, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", ), ] -@pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) -def test_tth_to_q_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - tth_to_q(inputs[1], inputs[0]) +@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", tth_to_q_bad_params) +def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): + with pytest.raises(expected_error_type, match=expected_error_msg): + tth_to_q(tth, wavelength) params_q_to_d = [ @@ -212,6 +214,7 @@ def test_tth_to_d_bad(inputs, expected): @pytest.mark.parametrize("inputs, expected", params_d_to_tth) def test_d_to_tth(inputs, expected): actual = d_to_tth(inputs[1], inputs[0]) + assert np.allclose(expected, actual) From 73d8ada4e489a7f84c5bb6f8f0ecc5ecaf695d23 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 00:00:43 -0500 Subject: [PATCH 198/445] Refactor transform tests, pass variables to parameters --- tests/conftest.py | 11 ++-- tests/test_transforms.py | 136 ++++++++++++++++++++------------------- 2 files changed, 77 insertions(+), 70 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 44f66178..866dce39 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,11 +37,12 @@ def _load(filename): def do_minimal_tth(): return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") + @pytest.fixture def wavelength_warning_msg(): return ( - "No wavelength has been specified. You can continue to use the DiffractionObject, but " - "some of its powerful features will not be available. " - "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " - "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." -) \ No newline at end of file + "No wavelength has been specified. You can continue to use the DiffractionObject, but " + "some of its powerful features will not be available. " + "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " + "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." + ) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index e9bb54bf..177745f1 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,7 +5,6 @@ from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q - params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0))), @@ -35,7 +34,7 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): assert np.allclose(expected_tth, actual_tth) -params_q_to_tth_bad = [ +test_q_to_tth_bad_params = [ # UC1: user specified invalid q values that result in tth > 180 degrees ( 4 * np.pi, @@ -55,7 +54,7 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): ] -@pytest.mark.parametrize("q, wavelength, expected_error_type, expected_error_msg", params_q_to_tth_bad) +@pytest.mark.parametrize("q, wavelength, expected_error_type, expected_error_msg", test_q_to_tth_bad_params) def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): with pytest.raises(expected_error_type, match=expected_error_msg): q_to_tth(wavelength, q) @@ -81,6 +80,7 @@ def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): ), ] + @pytest.mark.parametrize("wavelength, tth, expected_q", test_tth_t_q_params) def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): if wavelength is None: @@ -88,10 +88,11 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): actual_q = tth_to_q(tth, wavelength) else: actual_q = tth_to_q(tth, wavelength) - + assert np.allclose(actual_q, expected_q) -tth_to_q_bad_params = [ + +test_tth_to_q_bad_params = [ # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) ( None, @@ -109,138 +110,143 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): ] -@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", tth_to_q_bad_params) +@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", test_tth_to_q_bad_params) def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): with pytest.raises(expected_error_type, match=expected_error_msg): tth_to_q(tth, wavelength) -params_q_to_d = [ +test_q_to_d_params = [ # UC1: User specified empty q values - ([np.array([])], np.array([])), + (np.array([]), np.array([])), # UC2: User specified valid q values ( - [np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], + np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), ), ] -@pytest.mark.parametrize("inputs, expected", params_q_to_d) -def test_q_to_d(inputs, expected): - actual = q_to_d(inputs[0]) - assert np.allclose(actual, expected) +@pytest.mark.parametrize("q, expected_d", test_q_to_d_params) +def test_q_to_d(q, expected_d): + actual_d = q_to_d(q) + assert np.allclose(actual_d, expected_d) -params_d_to_q = [ +test_d_to_q_params = [ # UC1: User specified empty d values - ([np.array([])], np.array([])), + (np.array([]), np.array([])), # UC2: User specified valid d values ( - [np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0])], + np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), ), ] -@pytest.mark.parametrize("inputs, expected", params_d_to_q) -def test_d_to_q(inputs, expected): - actual = d_to_q(inputs[0]) - assert np.allclose(actual, expected) +@pytest.mark.parametrize("d, expected_q", test_d_to_q_params) +def test_d_to_q(d, expected_q): + actual_q = d_to_q(d) + assert np.allclose(actual_q, expected_q) -params_tth_to_d = [ +test_tth_to_d_params = [ # UC0: User specified empty tth values (without wavelength) - ([None, np.array([])], np.array([])), + (None, np.array([]), np.array([])), # UC1: User specified empty tth values (with wavelength) - ([4 * np.pi, np.array([])], np.array([])), + (4 * np.pi, np.array([]), np.array([])), # UC2: User specified valid tth values between 0-180 degrees (without wavelength) ( - [None, np.array([0, 30, 60, 90, 120, 180])], + None, + np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), # UC3: User specified valid tth values between 0-180 degrees (with wavelength) ( - [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], + 4 * np.pi, + np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), ), ] -@pytest.mark.parametrize("inputs, expected", params_tth_to_d) -def test_tth_to_d(inputs, expected): - actual = tth_to_d(inputs[1], inputs[0]) - assert np.allclose(actual, expected) +@pytest.mark.parametrize("wavelength, tth, expected_d", test_tth_to_d_params) +def test_tth_to_d(wavelength, tth, expected_d): + actual_d = tth_to_d(tth, wavelength) + assert np.allclose(actual_d, expected_d) -params_tth_to_d_bad = [ +test_tth_to_d_invalid_params = [ # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) ( - [None, np.array([0, 30, 60, 90, 120, 181])], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + None, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", ), # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) ( - [4 * np.pi, np.array([0, 30, 60, 90, 120, 181])], - [ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."], + 4 * np.pi, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", ), ] -@pytest.mark.parametrize("inputs, expected", params_tth_to_d_bad) -def test_tth_to_d_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - tth_to_d(inputs[1], inputs[0]) +@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", test_tth_to_d_invalid_params) +def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_msg): + with pytest.raises(expected_error_type, match=expected_error_msg): + tth_to_d(tth, wavelength) -params_d_to_tth = [ +test_d_to_tth_params = [ # UC1: Empty d values, no wavelength, return empty arrays - ([None, np.empty((0))], np.empty((0))), + (None, np.empty((0)), np.empty((0))), # UC2: Empty d values, wavelength specified, return empty arrays - ([4 * np.pi, np.empty((0))], np.empty(0)), + (4 * np.pi, np.empty((0)), np.empty(0)), # UC3: User specified valid d values, no wavelength, return empty arrays ( - [None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])], + None, + np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), ), # UC4: User specified valid d values (with wavelength) ( - [4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi])], + 4 * np.pi, + np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), ), ] -@pytest.mark.parametrize("inputs, expected", params_d_to_tth) -def test_d_to_tth(inputs, expected): - actual = d_to_tth(inputs[1], inputs[0]) +@pytest.mark.parametrize("wavelength, d, expected_tth", test_d_to_tth_params) +def test_d_to_tth(wavelength, d, expected_tth): + actual_tth = d_to_tth(d, wavelength) + assert np.allclose(actual_tth, expected_tth) - assert np.allclose(expected, actual) - -params_d_to_tth_bad = [ +test_d_to_tth_bad_params = [ # UC1: user specified invalid d values that result in tth > 180 degrees ( - [4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2])], - [ - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], + 4 * np.pi, + np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", ), # UC2: user specified a wrong wavelength that result in tth > 180 degrees ( - [100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])], - [ - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ], + 100, + np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), + ValueError, + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values.", ), ] -@pytest.mark.parametrize("inputs, expected", params_d_to_tth_bad) -def test_d_to_tth_bad(inputs, expected): - with pytest.raises(expected[0], match=expected[1]): - d_to_tth(inputs[1], inputs[0]) +@pytest.mark.parametrize("wavelength, d, expected_error_type, expected_error_msg", test_d_to_tth_bad_params) +def test_d_to_tth_bad(wavelength, d, expected_error_type, expected_error_msg): + with pytest.raises(expected_error_type, match=expected_error_msg): + d_to_tth(d, wavelength) From 5e395388634b268b0d9f81b8d3dc7e4d2a7cd43f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 00:16:16 -0500 Subject: [PATCH 199/445] Remove the use of extra variable for params to prevent global scope in the file --- tests/conftest.py | 8 + tests/test_transforms.py | 327 +++++++++++++++++++-------------------- 2 files changed, 164 insertions(+), 171 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 866dce39..6d23ba55 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,3 +46,11 @@ def wavelength_warning_msg(): "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." ) + + +@pytest.fixture +def invalid_q_or_d_or_wavelength_error_msg(): + return ( + "The supplied input array and wavelength will result in an impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject with correct values." + ) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 177745f1..4ebb8a73 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -5,24 +5,25 @@ from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q -params_q_to_tth = [ - # UC1: Empty q values, no wavelength, return empty arrays - (None, np.empty((0)), np.empty((0))), - # UC2: Empty q values, wavelength specified, return empty arrays - (4 * np.pi, np.empty((0)), np.empty(0)), - # UC3: User specified valid q values, no wavelength, return empty arrays - ( - None, - np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), - np.array([0, 1, 2, 3, 4, 5]), - ), - # UC4: User specified valid q values (with wavelength) - # expected tth values are 2*arcsin(q) in degrees - (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), -] - -@pytest.mark.parametrize("wavelength, q, expected_tth", params_q_to_tth) +@pytest.mark.parametrize( + "wavelength, q, expected_tth", + [ + # UC1: Empty q values, no wavelength, return empty arrays + (None, np.empty((0)), np.empty((0))), + # UC2: Empty q values, wavelength specified, return empty arrays + (4 * np.pi, np.empty((0)), np.empty(0)), + # UC3: user specified valid q values, no wavelength, return empty arrays + ( + None, + np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC4: user specified valid q values (with wavelength) + # expected tth values are 2*arcsin(q) in degrees + (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), + ], +) def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): if wavelength is None: @@ -34,54 +35,51 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): assert np.allclose(expected_tth, actual_tth) -test_q_to_tth_bad_params = [ - # UC1: user specified invalid q values that result in tth > 180 degrees - ( - 4 * np.pi, - np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees - ( - 100, - np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ), -] - - -@pytest.mark.parametrize("q, wavelength, expected_error_type, expected_error_msg", test_q_to_tth_bad_params) -def test_q_to_tth_bad(q, wavelength, expected_error_type, expected_error_msg): +@pytest.mark.parametrize( + "wavelength, q, expected_error_type", + [ + # UC1: user specified invalid q values that result in tth > 180 degrees + ( + 4 * np.pi, + np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), + ValueError, + ), + # UC2: user specified a wrong wavelength that result in tth > 180 degrees + ( + 100, + np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), + ValueError, + ), + ], +) +def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wavelength_error_msg): + expected_error_msg = invalid_q_or_d_or_wavelength_error_msg with pytest.raises(expected_error_type, match=expected_error_msg): q_to_tth(wavelength, q) -test_tth_t_q_params = [ - # UC0: User specified empty tth values (without wavelength) - (None, np.array([]), np.array([])), - # UC1: User specified empty tth values (with wavelength) - (4 * np.pi, np.array([]), np.array([])), - # UC2: User specified valid tth values between 0-180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 180]), - np.array([0, 1, 2, 3, 4, 5]), - ), - # UC3: User specified valid tth values between 0-180 degrees (with wavelength) - # expected q values are sin15, sin30, sin45, sin60, sin90 - ( - 4 * np.pi, - np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), - np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), - ), -] - - -@pytest.mark.parametrize("wavelength, tth, expected_q", test_tth_t_q_params) +@pytest.mark.parametrize( + "wavelength, tth, expected_q", + [ + # UC0: user specified empty tth values (without wavelength) + (None, np.array([]), np.array([])), + # UC1: user specified empty tth values (with wavelength) + (4 * np.pi, np.array([]), np.array([])), + # UC2: user specified valid tth values between 0-180 degrees (without wavelength) + ( + None, + np.array([0, 30, 60, 90, 120, 180]), + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC3: user specified valid tth values between 0-180 degrees (with wavelength) + # expected q values are sin15, sin30, sin45, sin60, sin90 + ( + 4 * np.pi, + np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), + np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), + ), + ], +) def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): @@ -92,115 +90,116 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): assert np.allclose(actual_q, expected_q) -test_tth_to_q_bad_params = [ - # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 181]), - ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", - ), - # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) - ( - 4 * np.pi, - np.array([0, 30, 60, 90, 120, 181]), - ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", - ), -] - - -@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", test_tth_to_q_bad_params) +@pytest.mark.parametrize( + "wavelength, tth, expected_error_type, expected_error_msg", + [ + # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) + ( + None, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", + ), + # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) + ( + 4 * np.pi, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", + ), + ], +) def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): with pytest.raises(expected_error_type, match=expected_error_msg): tth_to_q(tth, wavelength) -test_q_to_d_params = [ - # UC1: User specified empty q values - (np.array([]), np.array([])), - # UC2: User specified valid q values - ( - np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), - np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), - ), -] - - -@pytest.mark.parametrize("q, expected_d", test_q_to_d_params) +@pytest.mark.parametrize( + "q, expected_d", + [ + # UC1: User specified empty q values + (np.array([]), np.array([])), + # UC2: User specified valid q values + ( + np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), + np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), + ), + ], +) def test_q_to_d(q, expected_d): actual_d = q_to_d(q) assert np.allclose(actual_d, expected_d) -test_d_to_q_params = [ - # UC1: User specified empty d values - (np.array([]), np.array([])), - # UC2: User specified valid d values - ( - np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), - np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), - ), -] - - -@pytest.mark.parametrize("d, expected_q", test_d_to_q_params) +@pytest.mark.parametrize( + "d, expected_q", + [ + # UC1: User specified empty d values + (np.array([]), np.array([])), + # UC2: User specified valid d values + ( + np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), + np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), + ), + ], +) def test_d_to_q(d, expected_q): actual_q = d_to_q(d) assert np.allclose(actual_q, expected_q) -test_tth_to_d_params = [ - # UC0: User specified empty tth values (without wavelength) - (None, np.array([]), np.array([])), - # UC1: User specified empty tth values (with wavelength) - (4 * np.pi, np.array([]), np.array([])), - # UC2: User specified valid tth values between 0-180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 180]), - np.array([0, 1, 2, 3, 4, 5]), - ), - # UC3: User specified valid tth values between 0-180 degrees (with wavelength) - ( - 4 * np.pi, - np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), - np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), - ), -] - - -@pytest.mark.parametrize("wavelength, tth, expected_d", test_tth_to_d_params) +@pytest.mark.parametrize( + "wavelength, tth, expected_d", + [ + # UC0: User specified empty tth values (without wavelength) + (None, np.array([]), np.array([])), + # UC1: User specified empty tth values (with wavelength) + (4 * np.pi, np.array([]), np.array([])), + # UC2: User specified valid tth values between 0-180 degrees (without wavelength) + ( + None, + np.array([0, 30, 60, 90, 120, 180]), + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC3: User specified valid tth values between 0-180 degrees (with wavelength) + ( + 4 * np.pi, + np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), + np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), + ), + ], +) def test_tth_to_d(wavelength, tth, expected_d): actual_d = tth_to_d(tth, wavelength) assert np.allclose(actual_d, expected_d) -test_tth_to_d_invalid_params = [ - # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 181]), - ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", - ), - # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) - ( - 4 * np.pi, - np.array([0, 30, 60, 90, 120, 181]), - ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", - ), -] - - -@pytest.mark.parametrize("wavelength, tth, expected_error_type, expected_error_msg", test_tth_to_d_invalid_params) +@pytest.mark.parametrize( + "wavelength, tth, expected_error_type, expected_error_msg", + [ + # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) + ( + None, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", + ), + # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) + ( + 4 * np.pi, + np.array([0, 30, 60, 90, 120, 181]), + ValueError, + "Two theta exceeds 180 degrees. Please check the input values for errors.", + ), + ], +) def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_msg): with pytest.raises(expected_error_type, match=expected_error_msg): tth_to_d(tth, wavelength) -test_d_to_tth_params = [ + +@pytest.mark.parametrize("wavelength, d, expected_tth", [ # UC1: Empty d values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0))), # UC2: Empty d values, wavelength specified, return empty arrays @@ -217,36 +216,22 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), ), -] - - -@pytest.mark.parametrize("wavelength, d, expected_tth", test_d_to_tth_params) +]) def test_d_to_tth(wavelength, d, expected_tth): actual_tth = d_to_tth(d, wavelength) assert np.allclose(actual_tth, expected_tth) -test_d_to_tth_bad_params = [ - # UC1: user specified invalid d values that result in tth > 180 degrees - ( - 4 * np.pi, - np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees - ( - 100, - np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), - ValueError, - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values.", - ), -] - - -@pytest.mark.parametrize("wavelength, d, expected_error_type, expected_error_msg", test_d_to_tth_bad_params) -def test_d_to_tth_bad(wavelength, d, expected_error_type, expected_error_msg): +@pytest.mark.parametrize( + "wavelength, d, expected_error_type", + [ + # UC1: user specified invalid d values that result in tth > 180 degrees + (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), + # UC2: user specified a wrong wavelength that result in tth > 180 degrees + (100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), ValueError), + ], +) +def test_d_to_tth_bad(wavelength, d, expected_error_type, invalid_q_or_d_or_wavelength_error_msg): + expected_error_msg = invalid_q_or_d_or_wavelength_error_msg with pytest.raises(expected_error_type, match=expected_error_msg): d_to_tth(d, wavelength) From cdf96dffe5c1745af5ddb952592f310428d23fa5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 00:17:38 -0500 Subject: [PATCH 200/445] Apply pre-commit --- tests/test_transforms.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 4ebb8a73..487cf6ca 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -198,25 +198,27 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m tth_to_d(tth, wavelength) - -@pytest.mark.parametrize("wavelength, d, expected_tth", [ - # UC1: Empty d values, no wavelength, return empty arrays - (None, np.empty((0)), np.empty((0))), - # UC2: Empty d values, wavelength specified, return empty arrays - (4 * np.pi, np.empty((0)), np.empty(0)), - # UC3: User specified valid d values, no wavelength, return empty arrays - ( - None, - np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), - np.array([0, 1, 2, 3, 4, 5]), - ), - # UC4: User specified valid d values (with wavelength) - ( - 4 * np.pi, - np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), - np.array([60.0, 90.0, 120.0]), - ), -]) +@pytest.mark.parametrize( + "wavelength, d, expected_tth", + [ + # UC1: Empty d values, no wavelength, return empty arrays + (None, np.empty((0)), np.empty((0))), + # UC2: Empty d values, wavelength specified, return empty arrays + (4 * np.pi, np.empty((0)), np.empty(0)), + # UC3: User specified valid d values, no wavelength, return empty arrays + ( + None, + np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), + np.array([0, 1, 2, 3, 4, 5]), + ), + # UC4: User specified valid d values (with wavelength) + ( + 4 * np.pi, + np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), + np.array([60.0, 90.0, 120.0]), + ), + ], +) def test_d_to_tth(wavelength, d, expected_tth): actual_tth = d_to_tth(d, wavelength) assert np.allclose(actual_tth, expected_tth) From 01e71dc9c61124c903c2cead537a40d0f814612f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 15 Dec 2024 16:25:44 -0500 Subject: [PATCH 201/445] initial commit --- .../examples/diffractionobjectsexample.rst | 34 ------------ doc/source/examples/transformsexample.rst | 53 +++++++++++++++++++ .../utilities/diffractionobjectsutility.rst | 13 ----- doc/source/utilities/transformsutility.rst | 14 +++++ 4 files changed, 67 insertions(+), 47 deletions(-) delete mode 100644 doc/source/examples/diffractionobjectsexample.rst create mode 100644 doc/source/examples/transformsexample.rst delete mode 100644 doc/source/utilities/diffractionobjectsutility.rst create mode 100644 doc/source/utilities/transformsutility.rst diff --git a/doc/source/examples/diffractionobjectsexample.rst b/doc/source/examples/diffractionobjectsexample.rst deleted file mode 100644 index 48409432..00000000 --- a/doc/source/examples/diffractionobjectsexample.rst +++ /dev/null @@ -1,34 +0,0 @@ -.. _Diffraction Objects Example: - -:tocdepth: -1 - -Diffraction Objects Example -########################### - -This example will demonstrate how to use the ``DiffractionObject`` class in the -``diffpy.utils.diffraction_objects`` module to process and analyze diffraction data. - -1) Assuming we have created a ``DiffractionObject`` called my_diffraction_pattern from a measured diffraction pattern, - and we have specified the wavelength (see Section ??, to be added), - we can use the ``q_to_tth`` and ``tth_to_q`` functions to convert between q and two-theta. :: - - # Example: convert q to tth - my_diffraction_pattern.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] - my_diffraction_pattern.q_to_tth() - - This function will convert your provided q array and return a two theta array in degrees. - To load the converted array, you can either call ``test.q_to_tth()`` or ``test.on_q[0]``. :: - - # Example: convert tth to q - from diffpy.utils.diffraction_objects import DiffractionObject - my_diffraction_pattern.on_tth = [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]] - my_diffraction_pattern.tth_to_q() - - Similarly, to load the converted array, you can either call ``test.tth_to_q()`` or ``test.on_tth[0]``. - -2) Both functions require a wavelength to perform conversions. Without a wavelength, they will return empty arrays. - Therefore, we strongly encourage you to specify a wavelength when using these functions. :: - - # Example: without wavelength specified - my_diffraction_pattern.on_q = [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]] - my_diffraction_pattern.q_to_tth() # returns an empty array diff --git a/doc/source/examples/transformsexample.rst b/doc/source/examples/transformsexample.rst new file mode 100644 index 00000000..7877f866 --- /dev/null +++ b/doc/source/examples/transformsexample.rst @@ -0,0 +1,53 @@ +.. _Transforms Example: + +:tocdepth: -1 + +Transforms Example +################## + +This example will demonstrate how to use the functions in the +``diffpy.utils.transforms`` module to process and analyze diffraction data. + +1) Converting from ``q`` to ``2theta`` or ``d``: + If you have a 1D ``q``-array, you can use the ``q_to_tth`` and ``q_to_d`` functions + to convert it to ``2theta`` or ``d``. :: + + # Example: convert q to 2theta + from diffpy.utils.transformers import q_to_tth + wavelength = 0.71 + q = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) + tth = q_to_tth(q, wavelength) + + # Example: convert q to d + from diffpy.utils.transformers import q_to_d + q = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) + d = q_to_d(q) + +(2) Converting from ``2theta`` to ``q`` or ``d``: + For a 1D ``2theta`` array, you can convert it to ``q`` or ``d`` in a similar way. :: + + # Example: convert 2theta to q + from diffpy.utils.transformers import tth_to_q + wavelength = 0.71 + tth = np.array([0, 30, 60, 90, 120, 180]) + q = tth_to_q(tth, wavelength) + + # Example: convert 2theta to d + from diffpy.utils.transformers import tth_to_d + wavelength = 0.71 + tth = np.array([0, 30, 60, 90, 120, 180]) + d = tth_to_d(tth, wavelength) + +(3) Converting from ``d`` to ``q`` or ``2theta``: + For a 1D ``d`` array, you can convert it to ``q`` or ``2theta``. :: + + # Example: convert d to q + from diffpy.utils.transformers import tth_to_q + d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) + q = d_to_q(d) + + # Example: convert d to 2theta + from diffpy.utils.transformers import d_to_tth + wavelength = 0.71 + d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) + tth = d_to_tth(d, wavelength) diff --git a/doc/source/utilities/diffractionobjectsutility.rst b/doc/source/utilities/diffractionobjectsutility.rst deleted file mode 100644 index 94cb1308..00000000 --- a/doc/source/utilities/diffractionobjectsutility.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. _Diffraction Objects Utility: - -Diffraction Objects Utility -=========================== - -The ``diffpy.utils.diffraction_objects`` module provides functions -for managing and analyzing diffraction data, including angle-space conversions -and interactions between diffraction data. - -These functions help developers standardize diffraction data and update the arrays -in the associated ``DiffractionObject``, enabling easier analysis and further processing. - -For a more in-depth tutorial for how to use these functions, click :ref:`here `. diff --git a/doc/source/utilities/transformsutility.rst b/doc/source/utilities/transformsutility.rst new file mode 100644 index 00000000..5e2bac2a --- /dev/null +++ b/doc/source/utilities/transformsutility.rst @@ -0,0 +1,14 @@ +.. _Transforms Utility: + +Transforms Utility +================== + +The ``diffpy.utils.transforms`` module provides a set of functions for managing and analyzing diffraction data, +including angle-space transformations between ``q``, ``2theta``, and ``d``-spacing. + +These functions allow developers to standardize diffraction data and convert it between different spacings, +simplifying analysis, visualization, and processing. +They are also internally used by the ``DiffractionObject`` class for efficient data manipulation. +For more information about this, click :ref:`here `. + +For a more in-depth tutorial for how to use these functions, click :ref:`here `. From ef751973519f983055f28c273092b017afecf9e2 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 16:45:56 -0500 Subject: [PATCH 202/445] improve UC comment for test_q_to_tth func --- tests/test_transforms.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 487cf6ca..70660033 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -9,18 +9,23 @@ @pytest.mark.parametrize( "wavelength, q, expected_tth", [ - # UC1: Empty q values, no wavelength, return empty arrays + # UC1.1: User specified empty 'q' and no 'wavelength'. + # Expect empty 'tth' array and UserWarning about missing wavelength. (None, np.empty((0)), np.empty((0))), - # UC2: Empty q values, wavelength specified, return empty arrays + + # UC1.2: User specified empty 'q' and 'wavelength'. Expect empty 'tth' array. (4 * np.pi, np.empty((0)), np.empty(0)), - # UC3: user specified valid q values, no wavelength, return empty arrays + + # UC2.1: User specified non-empty 'q' values and no 'wavelength'. + # Expect non-empty 'tth' array and UserWarning about missing wavelength. ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # UC4: user specified valid q values (with wavelength) - # expected tth values are 2*arcsin(q) in degrees + + # UC2.2: User specified non-empty 'q' values and 'wavelength'. + # Expect tth values are 2*arcsin(q) in degrees. (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) From 8ebaede0549d4988162fe828be92ebff39f9971d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:46:06 +0000 Subject: [PATCH 203/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 70660033..d3e51e35 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -12,10 +12,8 @@ # UC1.1: User specified empty 'q' and no 'wavelength'. # Expect empty 'tth' array and UserWarning about missing wavelength. (None, np.empty((0)), np.empty((0))), - # UC1.2: User specified empty 'q' and 'wavelength'. Expect empty 'tth' array. (4 * np.pi, np.empty((0)), np.empty(0)), - # UC2.1: User specified non-empty 'q' values and no 'wavelength'. # Expect non-empty 'tth' array and UserWarning about missing wavelength. ( @@ -23,7 +21,6 @@ np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # UC2.2: User specified non-empty 'q' values and 'wavelength'. # Expect tth values are 2*arcsin(q) in degrees. (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), From 7a2603e45d93ebf369535757ad0cc4eb0222328c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 22:05:29 +0000 Subject: [PATCH 204/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index cb33f760..0dd5024d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,8 +38,8 @@ 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") - From 8693153a019b43b9f55b5e1f5c41e1e63c326d50 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:12:41 -0500 Subject: [PATCH 205/445] Add metadata data by materials scientist --- src/diffpy/utils/diffraction_objects.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 3d1653be..4caadd6a 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -65,7 +65,10 @@ class 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"} + ... "sample": "rock salt from the beach", + ... "composition": "NaCl", + ... "temperature": "300 K,", + ... "experimenters": "Phill, Sally" ... } >>> do = DiffractionObject( ... xarray=x, @@ -95,7 +98,6 @@ def __init__( 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( @@ -103,7 +105,6 @@ def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, me "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 From e68b59e25ff66f3619b9fa14fdbbeec6e88ff928 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:15:55 -0500 Subject: [PATCH 206/445] Add name example to diffraction object example --- src/diffpy/utils/diffraction_objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 4caadd6a..d28ef630 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -76,6 +76,7 @@ class DiffractionObject: ... xtype="q", ... wavelength=1.54, ... scat_quantity="x-ray", + ... name="Rock Salt X-ray Diffraction", ... metadata=metadata ... ) >>> print(do.metadata) From 235f624d0c24452cdd5ce5f3ba8915592504a52a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:17:09 -0500 Subject: [PATCH 207/445] Order x, y, xtype for _set_arrays --- src/diffpy/utils/diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index d28ef630..2ecc073f 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -111,7 +111,7 @@ def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, me self.metadata = metadata self.name = name self._input_xtype = xtype - self._set_arrays(xarray, xtype, yarray) + self._set_arrays(xarray, yarray, xtype) self._set_min_max_xarray() def __eq__(self, other): @@ -295,7 +295,7 @@ def get_array_index(self, value, xtype=None): i = (np.abs(array - value)).argmin() return i - def _set_arrays(self, xarray, xtype, yarray): + def _set_arrays(self, xarray, yarray, xtype): self._all_arrays = np.empty(shape=(len(xarray), 4)) self._all_arrays[:, 0] = yarray if xtype.lower() in QQUANTITIES: From 88ea61b1550453cc0244f08a723f9fcd13ce41c8 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:20:38 -0500 Subject: [PATCH 208/445] Remove 0.71 harded coded wavelength --- tests/test_diffraction_objects.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 94345451..cfc86c63 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -49,7 +49,6 @@ ), ( # Different wavelengths { - "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), @@ -66,7 +65,6 @@ ), ( # Different wavelengths { - "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), From 6b20c1a2913d3f8b063838a74f78a4e498ffd240 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:24:10 -0500 Subject: [PATCH 209/445] Add wavelength of 0.711 and 0.71 back --- tests/test_diffraction_objects.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index cfc86c63..787d7be4 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -15,6 +15,7 @@ { "name": "same", "scat_quantity": "x-ray", + "wavelength": 0.71, "xtype": "q", "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), @@ -23,6 +24,7 @@ { "name": "same", "scat_quantity": "x-ray", + "wavelength": 0.71, "xtype": "q", "xarray": np.array([1.0, 2.0]), "yarray": np.array([100.0, 200.0]), @@ -64,7 +66,8 @@ False, ), ( # Different wavelengths - { + { + "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), From 9f0f11ac3a8e5713aa71984bcaf0091999422119 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 02:25:12 +0000 Subject: [PATCH 210/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 787d7be4..0dcc2163 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -66,7 +66,7 @@ False, ), ( # Different wavelengths - { + { "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), From aa1af39567c0c24caa8195694a3c6f62a86bf2b9 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:28:00 -0500 Subject: [PATCH 211/445] Final setup of wavelenth value comparison --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 0dcc2163..cb4e08ae 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -51,13 +51,13 @@ ), ( # Different wavelengths { + "wavelength": 0.71, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), "metadata": {"thing1": 1, "thing2": "thing2"}, }, { - "wavelength": 0.42, "xtype": "tth", "xarray": np.empty(0), "yarray": np.empty(0), From 9343e86fccb8080e65818dc4456bde3325fae348 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 15 Dec 2024 21:38:10 -0500 Subject: [PATCH 212/445] Do not call UCs for test function --- tests/test_transforms.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index d3e51e35..5fb2326f 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -9,20 +9,22 @@ @pytest.mark.parametrize( "wavelength, q, expected_tth", [ - # UC1.1: User specified empty 'q' and no 'wavelength'. - # Expect empty 'tth' array and UserWarning about missing wavelength. + # Case 1: Allow empty arrays for q + # 1. Empty q values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0))), - # UC1.2: User specified empty 'q' and 'wavelength'. Expect empty 'tth' array. + # 2. Empty q values, wavelength specified, return empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # UC2.1: User specified non-empty 'q' values and no 'wavelength'. - # Expect non-empty 'tth' array and UserWarning about missing wavelength. + + # Case 2: Allow wavelength to be missing. + # Valid q values, no wavelength, return index array ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # UC2.2: User specified non-empty 'q' values and 'wavelength'. - # Expect tth values are 2*arcsin(q) in degrees. + + # Case 3: Correctly specified q and wavelength + # Expected tth values are 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) From d962c1b37abcc3a873db22271627ca184f9fa9f1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 02:38:44 +0000 Subject: [PATCH 213/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 5fb2326f..cf6a6ebb 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -14,7 +14,6 @@ (None, np.empty((0)), np.empty((0))), # 2. Empty q values, wavelength specified, return empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # Case 2: Allow wavelength to be missing. # Valid q values, no wavelength, return index array ( @@ -22,7 +21,6 @@ np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # Case 3: Correctly specified q and wavelength # Expected tth values are 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), From d09d7fb38cd79666a5426a9dc934dce3a9955868 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 05:36:28 -0500 Subject: [PATCH 214/445] news tweak --- news/no-empty-object.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/no-empty-object.rst b/news/no-empty-object.rst index 02126c98..7e4ec7a4 100644 --- a/news/no-empty-object.rst +++ b/news/no-empty-object.rst @@ -4,7 +4,7 @@ **Changed:** -* `DiffractionObject` requires 3 input parameters of `xarrays`, `yarrays`, `xtype` +* `DiffractionObject` requires 3 input parameters of `xarray`, `yarray`, `xtype`, to be instantiated. It can be instantiated with empty arrays. **Deprecated:** From 0e362c155b39eefbd7988f4f8bfec6696f97cd02 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 05:41:39 -0500 Subject: [PATCH 215/445] tweak name example --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 2ecc073f..a388f1b2 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -76,7 +76,7 @@ class DiffractionObject: ... xtype="q", ... wavelength=1.54, ... scat_quantity="x-ray", - ... name="Rock Salt X-ray Diffraction", + ... name="beach_rock_salt_1", ... metadata=metadata ... ) >>> print(do.metadata) From c1422402eb3695fd479fdd0cf5912d24ef1f913e Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 05:45:56 -0500 Subject: [PATCH 216/445] tweak array length error message --- src/diffpy/utils/diffraction_objects.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a388f1b2..ae61c094 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -99,12 +99,12 @@ def __init__( 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." + "'xarray' and 'yarray' are different lengths. They must " + "correspond to each other and have the same length. " + "Please re-initialize 'DiffractionObject' + "with valid 'xarray' and 'yarray's" ) self.scat_quantity = scat_quantity self.wavelength = wavelength From 24b1bbcd832249ee23124f51aeb2f3c913181bd6 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 05:53:08 -0500 Subject: [PATCH 217/445] fix missing quote --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ae61c094..d72a2889 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -103,7 +103,7 @@ def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, me raise ValueError( "'xarray' and 'yarray' are different lengths. They must " "correspond to each other and have the same length. " - "Please re-initialize 'DiffractionObject' + "Please re-initialize 'DiffractionObject'" "with valid 'xarray' and 'yarray's" ) self.scat_quantity = scat_quantity From 2c51af145f5d427594ebc51df4984e047b510b47 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 06:01:29 -0500 Subject: [PATCH 218/445] fix error message check --- tests/test_diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index cb4e08ae..0aa807e8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -523,9 +523,9 @@ def test_id_setter_error(do_minimal): def test_xarray_yarray_length_mismatch(): with pytest.raises( ValueError, - match="'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", + match="'xarray' and 'yarray' are different lengths. " + "They must correspond to each other and have the same length. Please " + "re-initialize 'DiffractionObject'with valid 'xarray' and 'yarray's" ): DiffractionObject( xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0]), xtype="tth", wavelength=1.54 From ba4b985df971440325442a50ac6de63eaad05fa5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:03:49 +0000 Subject: [PATCH 219/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 0aa807e8..dfb67a3d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -525,7 +525,7 @@ def test_xarray_yarray_length_mismatch(): ValueError, match="'xarray' and 'yarray' are different lengths. " "They must correspond to each other and have the same length. Please " - "re-initialize 'DiffractionObject'with valid 'xarray' and 'yarray's" + "re-initialize 'DiffractionObject'with valid 'xarray' and 'yarray's", ): DiffractionObject( xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0]), xtype="tth", wavelength=1.54 From fffe01afa1c6f38a6fe88dd1a2a0a58b4095cced Mon Sep 17 00:00:00 2001 From: Alison Wu Date: Mon, 16 Dec 2024 13:40:21 -0500 Subject: [PATCH 220/445] updated tools example page with better get_user_info example --- doc/source/examples/toolsexample.rst | 38 +++++++++++++++++++--------- news/tools-doc-update.rst | 23 +++++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 news/tools-doc-update.rst diff --git a/doc/source/examples/toolsexample.rst b/doc/source/examples/toolsexample.rst index b3f615fd..05bac195 100644 --- a/doc/source/examples/toolsexample.rst +++ b/doc/source/examples/toolsexample.rst @@ -24,30 +24,44 @@ Using the tools module, we can efficiently get them in terms of a dictionary. The function returns a dictionary containing the username and email information. -2) You can also override existing values by passing a dictionary to the function with the keys ``"username"`` and ``"email"`` :: +2) There are a few ways you could use to override the user information in the configuration files + * You can override existing values by passing a dictionary to the function with the keys ``"username"`` and ``"email"`` :: - new_args = {"username": "new_username", "email": "new@example.com"} - new_user_info = get_user_info(new_args) + new_args = {"username": "new_username", "email": "new@example.com"} + new_user_info = get_user_info(new_args) -3) You can update only the username or email individually, for example :: + This returns "new_username" and "new@example.com" instead of the + * You can update only the username or email individually, for example :: - new_username = {"username": new_username} - new_user_info = get_user_info(new_username) + new_username = {"username": new_username} + new_user_info = get_user_info(new_username) - This updates username to "new_username" while fetching the email from inputs or the configuration files. - Similarly, you can update only the email. :: + This updates username to "new_username" while fetching the email from inputs or the configuration files. + Similarly, you can update only the email. :: - new_email = {"email": new@email.com} - new_user_info = get_user_info(new_email) + new_email = {"email": new@email.com} + new_user_info = get_user_info(new_email) - This updates the email to "new@email.com" while fetching the username from inputs or the configuration files. + This updates the email to "new@email.com" while fetching the username from inputs or the configuration files. + +3) You can also permanently update your default configuration file manually. + Locate the file ``diffpyconfig.json``, which is usually in the user's home directory (``~/.diffpy/``) or ``~/.config/diffpy/``. + Open the file using a text editor such as nano, vim, or a graphical editor like VS Code. + Look for entries like these :: + + { + "username": "John Doe", + "email": "john.doe@example.com" + } + + Then you can update the username and email as needed, make sure to save your edits. 4) We also have the function ``get_package_info``, which inserts or updates package names and versions in the given metadata dictionary under the key "package_info". It stores the package information as {"package_info": {"package_name": "version_number"}}. This function can be used as follows. :: - from diffpy.utils.tools import get_user_info + from diffpy.utils.tools import get_package_info package_metadata = get_package_info("my_package") You can also specify an existing dictionary to be updated with the information. :: diff --git a/news/tools-doc-update.rst b/news/tools-doc-update.rst new file mode 100644 index 00000000..87f2876e --- /dev/null +++ b/news/tools-doc-update.rst @@ -0,0 +1,23 @@ +**Added:** + +* Information on how to update the default user information + +**Changed:** + +* Enumerated list for the different ways to set user information + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Typo for get_package_info example + +**Security:** + +* From 9f48f34e05b710d1ebac69ac20baddba3347eadb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:41:26 +0000 Subject: [PATCH 221/445] [pre-commit.ci] auto fixes from pre-commit hooks --- news/tools-doc-update.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/tools-doc-update.rst b/news/tools-doc-update.rst index 87f2876e..e04b7b17 100644 --- a/news/tools-doc-update.rst +++ b/news/tools-doc-update.rst @@ -16,7 +16,7 @@ **Fixed:** -* Typo for get_package_info example +* Typo for get_package_info example **Security:** From b5ab9fec08fbd942d2d91ce7d114e31d4bbc4f91 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 16 Dec 2024 20:09:35 -0500 Subject: [PATCH 222/445] simon tweaks to tools example --- doc/source/examples/toolsexample.rst | 174 +++++++++++++++++++++------ src/diffpy/utils/tools.py | 4 +- 2 files changed, 140 insertions(+), 38 deletions(-) diff --git a/doc/source/examples/toolsexample.rst b/doc/source/examples/toolsexample.rst index 05bac195..a56f76d3 100644 --- a/doc/source/examples/toolsexample.rst +++ b/doc/source/examples/toolsexample.rst @@ -5,69 +5,171 @@ Tools Example ############# -This example will demonstrate how diffpy.utils allows us to conveniently load and manage user and package information. +The tools module contains various tools that may be useful when you manipulate and analyze diffraction data. + +Automatically Capture User Info +=============================== + +One task we would like to do is to capture and propagate useful metadata that describes the diffraction data. +Some is essential such as wavelength and radiation type. Other metadata is useful such as information about the +sample, co-workers and so on. However, one of the most important bits of information is the name of the data owner. +For example, in ``DiffractionObjects`` this is stored in the ``metadata`` dictionary as ``username``, ``user_email``, +and ``user_orcid``. + +To reduce experimenter overhead when collecting this information, we have developed an infrastructure that helps +to capture this information automatically when you are using `DiffractionObjects` and other diffpy tools. +You may also reuse this infrastructure for your own projects using tools in this tutorial. + +This example will demonstrate how ``diffpy.utils`` allows us to conveniently load and manage user and package information. Using the tools module, we can efficiently get them in terms of a dictionary. -1) We have the function ``get_user_info`` that neatly returns a dictionary containing the username and email. - You can use this function without arguments. :: +Load user info into your program +-------------------------------- + +To use this functionality in your own code make use of the ``get_user_info`` function in +``diffpy.utils.tools`` which will search for information about the user, parse it, and return +it in a dictionary object e.g. if the user is "Jane Doe" with email "janedoe@gmail.com" and the +function can find the information, if you type this + +.. code-block:: python from diffpy.utils.tools import get_user_info user_info = get_user_info() - This function will first attempt to load the information from configuration files looking first in - the current working directory and then in the user's home directory. - If no configuration files exist, it prompts for user input and creates a configuration file in the home directory - so that the next time the program is run it will no longer have to prompt the user. - It can be passed user information which overrides looking in config files, and so could be passed - information that is entered through a gui or command line interface to override default information at runtime. - It prioritizes prompted user inputs, then current working directory config file, and finally home directory config file. +The function will return + +.. code-block:: python + + {"email": "janedoe@email.com", "username": "Jane Doe"} + + +Where does ``get_user_info()`` get the user information from? +------------------------------------------------------------- + +The function will first attempt to load the information from configuration files with the name ``diffpyconfig.json`` +on your hard-drive. +It looks first for the file in the current working directory. If it cannot find it there it will look +user's home, i.e., login, directory. To find this directory, open a terminal and a unix or mac system type :: + + cd ~ + pwd + +Or type ``Echo $HOME``. On a Windows computer :: + + echo %USERPROFILE%" + +What if no config files exist yet? +----------------------------------- + +If no configuration files can be found, the function attempts to create one in the user's home +directory. The function will pause execution and ask for a user-response to enter the information. +It will then write the config file in the user's home directory. + +In this way, the next, and subsequent times the program is run, it will no longer have to prompt the user +as it will successfully find the new config file. + +Getting user data with no config files and with no interruption of execution +---------------------------------------------------------------------------- + +If you would like get run ``get_user_data()`` but without execution interruption even if it cannot find +an input file, type + +.. code-block:: python + + user_data = get_user_data(skip_config_creation=True) + +Passing user information directly to ``get_user_data()`` +-------------------------------------------------------- - The function returns a dictionary containing the username and email information. +It can be passed user information which fully or partially overrides looking in config files +For example, in this way it would be possible to pass in information +that is entered through a gui or command line interface. E.g., -2) There are a few ways you could use to override the user information in the configuration files - * You can override existing values by passing a dictionary to the function with the keys ``"username"`` and ``"email"`` :: + .. code-block:: python - new_args = {"username": "new_username", "email": "new@example.com"} - new_user_info = get_user_info(new_args) + new_user_info = get_user_info({"username": "new_username", "email": "new@example.com"}) - This returns "new_username" and "new@example.com" instead of the - * You can update only the username or email individually, for example :: +This returns ``{"username": "new_username", "email": "new@example.com"}`` (and so, effectively, does nothing) +However, You can update only the username or email individually, for example - new_username = {"username": new_username} - new_user_info = get_user_info(new_username) +.. code-block:: python - This updates username to "new_username" while fetching the email from inputs or the configuration files. - Similarly, you can update only the email. :: + new_user_info = get_user_info({"username": new_username}) - new_email = {"email": new@email.com} - new_user_info = get_user_info(new_email) +will return ``{"username": "new_username", "email": "janedoe@gmail.com"}`` +if it found ``janedoe@gmail.com`` as the email in the config file. +Similarly, you can update only the email in the returned dictionary, - This updates the email to "new@email.com" while fetching the username from inputs or the configuration files. +.. code-block:: python -3) You can also permanently update your default configuration file manually. - Locate the file ``diffpyconfig.json``, which is usually in the user's home directory (``~/.diffpy/``) or ``~/.config/diffpy/``. - Open the file using a text editor such as nano, vim, or a graphical editor like VS Code. - Look for entries like these :: + new_user_info = get_user_info({"email": new@email.com}) + +which will return ``{"username": "Jane Doe", "email": "new@email.com"}`` +if it found ``Jane Doe`` as the user in the config file. + +I entered the wrong information in my config file so it always loads incorrect information +------------------------------------------------------------------------------------------ + +You can use of the above methods to temporarily override the incorrect information in your +global config file. However, it is easy to fix this simply by editing that file using a text +editor. + +Locate the file ``diffpyconfig.json``, in your home directory and open it in an editor :: { "username": "John Doe", "email": "john.doe@example.com" } - Then you can update the username and email as needed, make sure to save your edits. + Then you can edit the username and email as needed, make sure to save your edits. + +Automatically Capture Info about a Software Package Being Used +============================================================== + +We also have a handy tool for capturing information about a python package that is being used +to save in the metadata. To use this functionality, use he function ``get_package_info``, which +inserts or updates software package names and versions in a given metadata dictionary under +the key "package_info", e.g., -4) We also have the function ``get_package_info``, which inserts or updates package names and versions - in the given metadata dictionary under the key "package_info". - It stores the package information as {"package_info": {"package_name": "version_number"}}. - This function can be used as follows. :: +.. code-block:: python + + {"package_info": {"diffpy.utils": "0.3.0", "my_package": "0.3.1"}} + +If the installed version of the package "my_package" is 0.3.1. + +This function can be used in your code as follows + +.. code-block:: python from diffpy.utils.tools import get_package_info package_metadata = get_package_info("my_package") - You can also specify an existing dictionary to be updated with the information. :: +or + +.. code-block:: python + + package_metadata = get_package_info(["first_package", "second_package"]) + +which returns (for example) + +.. code-block:: python + + {"package_info": {"diffpy.utils": "0.3.0", "first_package": "1.0.1", "second_package": "0.0.7"}} + + +You can also specify an existing dictionary to be updated with the information. + +.. code-block:: python existing_dict = {"key": "value"} updated_dict = get_package_info("my_package", metadata=existing_dict)) - note that `"diffpy.utils"` is automatically included in the package info since the `get_user_info` function is - part of diffpy.utils. +Which returns + +.. code-block:: python + + {"key": "value", "package_info": {"diffpy.utils": "0.3.0", "my_package": "0.3.1"}} + + +Note that ``"diffpy.utils"`` is automatically included in the package info since the ``get_user_info`` function is +part of ``diffpy.utils``. diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 7e5e858f..3fc10031 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -118,8 +118,8 @@ def get_user_info(args=None): print( "No global configuration file was found containing " "information about the user to associate with the data.\n" - "By following the prompts below you can add your name and email to this file on the current" - " computer and your name will be automatically associated with subsequent diffpy data by default.\n" + "By following the prompts below you can add your name and email to this file on the current " + "computer and your name will be automatically associated with subsequent diffpy data by default.\n" "This is not recommended on a shared or public computer. " "You will only have to do that once.\n" "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" From 14b729919cce6185a97327fe0fa69a9b26d8b44d Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 17 Dec 2024 17:51:47 -0500 Subject: [PATCH 223/445] initial commit --- .../parserdata.zip => example_data/parser_data.zip} | Bin doc/source/examples/examples.rst | 7 ++++--- .../{parsersexample.rst => parsers_example.rst} | 2 +- .../{resampleexample.rst => resample_example.rst} | 2 +- .../{toolsexample.rst => tools_example.rst} | 0 ...transformsexample.rst => transforms_example.rst} | 12 ++++++------ .../{parsersutility.rst => parsers_utility.rst} | 0 .../{resampleutility.rst => resample_utility.rst} | 2 +- .../{toolsutility.rst => tools_utility.rst} | 0 ...transformsutility.rst => transforms_utility.rst} | 0 doc/source/utilities/utilities.rst | 7 ++++--- 11 files changed, 17 insertions(+), 15 deletions(-) rename doc/source/examples/{exampledata/parserdata.zip => example_data/parser_data.zip} (100%) rename doc/source/examples/{parsersexample.rst => parsers_example.rst} (97%) rename doc/source/examples/{resampleexample.rst => resample_example.rst} (97%) rename doc/source/examples/{toolsexample.rst => tools_example.rst} (100%) rename doc/source/examples/{transformsexample.rst => transforms_example.rst} (82%) rename doc/source/utilities/{parsersutility.rst => parsers_utility.rst} (100%) rename doc/source/utilities/{resampleutility.rst => resample_utility.rst} (77%) rename doc/source/utilities/{toolsutility.rst => tools_utility.rst} (100%) rename doc/source/utilities/{transformsutility.rst => transforms_utility.rst} (100%) diff --git a/doc/source/examples/exampledata/parserdata.zip b/doc/source/examples/example_data/parser_data.zip similarity index 100% rename from doc/source/examples/exampledata/parserdata.zip rename to doc/source/examples/example_data/parser_data.zip diff --git a/doc/source/examples/examples.rst b/doc/source/examples/examples.rst index fefe843a..002a1e23 100644 --- a/doc/source/examples/examples.rst +++ b/doc/source/examples/examples.rst @@ -7,6 +7,7 @@ Examples Landing page for diffpy.utils examples. .. toctree:: - parsersexample - resampleexample - toolsexample + parsers_example + resample_example + tools_example + transforms_example diff --git a/doc/source/examples/parsersexample.rst b/doc/source/examples/parsers_example.rst similarity index 97% rename from doc/source/examples/parsersexample.rst rename to doc/source/examples/parsers_example.rst index b709de60..f9320885 100644 --- a/doc/source/examples/parsersexample.rst +++ b/doc/source/examples/parsers_example.rst @@ -8,7 +8,7 @@ Parsers Example This example will demonstrate how diffpy.utils lets us easily process and serialize files. Using the parsers module, we can load file data into simple and easy-to-work-with Python objects. -1) To begin, unzip :download:`parserdata<./exampledata/parserdata.zip>` and take a look at ``data.txt``. +1) To begin, unzip :download:`parser_data<./example_data/parser_data.zip>` and take a look at ``data.txt``. Our goal will be to extract and serialize the data table as well as the parameters listed in the header of this file. 2) To get the data table, we will use the ``loadData`` function. The default behavior of this diff --git a/doc/source/examples/resampleexample.rst b/doc/source/examples/resample_example.rst similarity index 97% rename from doc/source/examples/resampleexample.rst rename to doc/source/examples/resample_example.rst index 3166e42b..5e54fca1 100644 --- a/doc/source/examples/resampleexample.rst +++ b/doc/source/examples/resample_example.rst @@ -10,7 +10,7 @@ Specifically, we will resample the grid of one function to match another for us Then we will show how this resampling method lets us create a perfect reconstruction of certain functions given enough datapoints. -1) To start, unzip :download:`parserdata<./exampledata/parserdata.zip>`. Then, load the data table from ``Nickel.gr`` +1) To start, unzip :download:`parser_data<./example_data/parser_data.zip>`. Then, load the data table from ``Nickel.gr`` and ``NiTarget.gr``. These datasets are based on data from `Atomic Pair Distribution Function Analysis: A Primer `_. :: diff --git a/doc/source/examples/toolsexample.rst b/doc/source/examples/tools_example.rst similarity index 100% rename from doc/source/examples/toolsexample.rst rename to doc/source/examples/tools_example.rst diff --git a/doc/source/examples/transformsexample.rst b/doc/source/examples/transforms_example.rst similarity index 82% rename from doc/source/examples/transformsexample.rst rename to doc/source/examples/transforms_example.rst index 7877f866..9a47aee0 100644 --- a/doc/source/examples/transformsexample.rst +++ b/doc/source/examples/transforms_example.rst @@ -13,13 +13,13 @@ This example will demonstrate how to use the functions in the to convert it to ``2theta`` or ``d``. :: # Example: convert q to 2theta - from diffpy.utils.transformers import q_to_tth + from diffpy.utils.transforms import q_to_tth wavelength = 0.71 q = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) tth = q_to_tth(q, wavelength) # Example: convert q to d - from diffpy.utils.transformers import q_to_d + from diffpy.utils.transforms import q_to_d q = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) d = q_to_d(q) @@ -27,13 +27,13 @@ This example will demonstrate how to use the functions in the For a 1D ``2theta`` array, you can convert it to ``q`` or ``d`` in a similar way. :: # Example: convert 2theta to q - from diffpy.utils.transformers import tth_to_q + from diffpy.utils.transforms import tth_to_q wavelength = 0.71 tth = np.array([0, 30, 60, 90, 120, 180]) q = tth_to_q(tth, wavelength) # Example: convert 2theta to d - from diffpy.utils.transformers import tth_to_d + from diffpy.utils.transforms import tth_to_d wavelength = 0.71 tth = np.array([0, 30, 60, 90, 120, 180]) d = tth_to_d(tth, wavelength) @@ -42,12 +42,12 @@ This example will demonstrate how to use the functions in the For a 1D ``d`` array, you can convert it to ``q`` or ``2theta``. :: # Example: convert d to q - from diffpy.utils.transformers import tth_to_q + from diffpy.utils.transforms import tth_to_q d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) q = d_to_q(d) # Example: convert d to 2theta - from diffpy.utils.transformers import d_to_tth + from diffpy.utils.transforms import d_to_tth wavelength = 0.71 d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) tth = d_to_tth(d, wavelength) diff --git a/doc/source/utilities/parsersutility.rst b/doc/source/utilities/parsers_utility.rst similarity index 100% rename from doc/source/utilities/parsersutility.rst rename to doc/source/utilities/parsers_utility.rst diff --git a/doc/source/utilities/resampleutility.rst b/doc/source/utilities/resample_utility.rst similarity index 77% rename from doc/source/utilities/resampleutility.rst rename to doc/source/utilities/resample_utility.rst index 184cf22d..1631ca2b 100644 --- a/doc/source/utilities/resampleutility.rst +++ b/doc/source/utilities/resample_utility.rst @@ -3,7 +3,7 @@ Resample Utility ================ -- ``wsinterp()``: Allows users easily reample a PDF onto another grid. +- ``wsinterp()``: Allows users easily resample a PDF onto another grid. This makes use of the Whittaker-Shannon interpolation formula. To see the theory behind how this interpolation works and how to use it in practice, click :ref:`here `. diff --git a/doc/source/utilities/toolsutility.rst b/doc/source/utilities/tools_utility.rst similarity index 100% rename from doc/source/utilities/toolsutility.rst rename to doc/source/utilities/tools_utility.rst diff --git a/doc/source/utilities/transformsutility.rst b/doc/source/utilities/transforms_utility.rst similarity index 100% rename from doc/source/utilities/transformsutility.rst rename to doc/source/utilities/transforms_utility.rst diff --git a/doc/source/utilities/utilities.rst b/doc/source/utilities/utilities.rst index 5ec89656..147d7265 100644 --- a/doc/source/utilities/utilities.rst +++ b/doc/source/utilities/utilities.rst @@ -10,6 +10,7 @@ Check the :ref:`examples` provided for how to use these. .. contents:: :depth: 2 -.. include:: parsersutility.rst -.. include:: resampleutility.rst -.. include:: toolsutility.rst +.. include:: parsers_utility.rst +.. include:: resample_utility.rst +.. include:: tools_utility.rst +.. include:: transforms_utility.rst From 6606e6dae202844956d8c786035dcb475ccf1006 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 17 Dec 2024 17:53:54 -0500 Subject: [PATCH 224/445] fix more typos --- doc/source/examples/transforms_example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/examples/transforms_example.rst b/doc/source/examples/transforms_example.rst index 9a47aee0..63b2fef6 100644 --- a/doc/source/examples/transforms_example.rst +++ b/doc/source/examples/transforms_example.rst @@ -42,7 +42,7 @@ This example will demonstrate how to use the functions in the For a 1D ``d`` array, you can convert it to ``q`` or ``2theta``. :: # Example: convert d to q - from diffpy.utils.transforms import tth_to_q + from diffpy.utils.transforms import d_to_q d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) q = d_to_q(d) From c982502c696a52a61438337c02d2072af95918ae Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 17 Dec 2024 19:42:50 -0500 Subject: [PATCH 225/445] reformat transforms example --- doc/source/examples/transforms_example.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/source/examples/transforms_example.rst b/doc/source/examples/transforms_example.rst index 63b2fef6..df7666b9 100644 --- a/doc/source/examples/transforms_example.rst +++ b/doc/source/examples/transforms_example.rst @@ -10,7 +10,9 @@ This example will demonstrate how to use the functions in the 1) Converting from ``q`` to ``2theta`` or ``d``: If you have a 1D ``q``-array, you can use the ``q_to_tth`` and ``q_to_d`` functions - to convert it to ``2theta`` or ``d``. :: + to convert it to ``2theta`` or ``d``. + +.. code-block:: python # Example: convert q to 2theta from diffpy.utils.transforms import q_to_tth @@ -24,7 +26,9 @@ This example will demonstrate how to use the functions in the d = q_to_d(q) (2) Converting from ``2theta`` to ``q`` or ``d``: - For a 1D ``2theta`` array, you can convert it to ``q`` or ``d`` in a similar way. :: + For a 1D ``2theta`` array, you can convert it to ``q`` or ``d`` in a similar way. + +.. code-block:: python # Example: convert 2theta to q from diffpy.utils.transforms import tth_to_q @@ -39,7 +43,9 @@ This example will demonstrate how to use the functions in the d = tth_to_d(tth, wavelength) (3) Converting from ``d`` to ``q`` or ``2theta``: - For a 1D ``d`` array, you can convert it to ``q`` or ``2theta``. :: + For a 1D ``d`` array, you can convert it to ``q`` or ``2theta``. + +.. code-block:: python # Example: convert d to q from diffpy.utils.transforms import d_to_q From a2b785030946cd61b6b8c51ba330ff7a27775253 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 18 Dec 2024 11:57:28 -0500 Subject: [PATCH 226/445] add utility and example --- .../examples/diffraction_objects_example.rst | 126 ++++++++++++++++++ .../utilities/diffraction_objects_utility.rst | 30 +++++ 2 files changed, 156 insertions(+) create mode 100644 doc/source/examples/diffraction_objects_example.rst create mode 100644 doc/source/utilities/diffraction_objects_utility.rst diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst new file mode 100644 index 00000000..7d431fad --- /dev/null +++ b/doc/source/examples/diffraction_objects_example.rst @@ -0,0 +1,126 @@ +.. _Diffraction Objects Example: + +:tocdepth: -1 + +Diffraction Objects Example +########################### + +This example will demonstrate how to use the functions in the ``diffpy.utils.diffraction_objects`` module +to create a ``DiffractionObject`` instance and analyze your diffraction data using relevant functions. + +1) To create a ``DiffractionObject``, you need to specify the type of the independent variable + (referred to as ``xtype``, one of ``q``, ``tth``, or ``d``), + an ``xarray`` of the corresponding values, and a ``yarray`` of the intensity values. + It is strongly encouraged to specify the ``wavelength`` in order to access + most of the other functionalities in the class. + Additionally, you can specify the type of your scattering experiment using the ``scat_quantity`` parameter, + the name of your diffraction object using the ``name`` parameter, + and a ``metadata`` dictionary containing relevant information about the data. Here's an example: + +.. code-block:: python + 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 = { + "sample": "rock salt from the beach", + "composition": "NaCl", + "temperature": "300 K,", + "experimenters": "Phill, Sally" + } + do = DiffractionObject( + xarray=x, + yarray=y, + xtype="q", + wavelength=1.54, + scat_quantity="x-ray", + name="beach_rock_salt_1", + metadata=metadata + ) + print(do.metadata) + + By creating a ``DiffractionObject`` instance, you store not only the diffraction data + but also all the associated information for analysis. + +2) ``DiffractionObject`` automatically populates the ``xarray`` onto ``q``, ``tth``, and ``d``-spacing. + If you want to access your diffraction data in a specific spacing, you can do this: + +.. code-block:: python + q = do.on_xtype("q") + tth = do.on_xtype("tth") + d = do.on_xtype("d") + + This will return the ``xarray`` and ``yarray`` as a list of two 1D arrays, based on the specified ``xtype``. + +3) Once the ``DiffractionObject`` is created, you can use ``get_array_index`` to get the index of the closest value + in the ``xarray`` to a specified value. + This is useful for alignment or comparison tasks. + For example, assume you have created a ``DiffractionObject`` called ``do``, + and you want to find the closest index of ``tth=80``, you can type the following: :: + + index = do.get_array_index(80, xtype="tth") + + If you do not specify an ``xtype``, it will default to the ``xtype`` used when creating the ``DiffractionObject``. + For example, if you have created a ``DiffractionObject`` called ``do`` with ``xtype="q"``, + you can find its closest index for ``q=0.25`` by typing either of the following: :: + + index = do.get_array_index(0.25) # no input xtype, defaults to q + index = do.get_array_index(0.25, xtype="q") + +4) You can compare diffraction objects too. + For example, you can use the ``scale_to`` function to rescale one diffraction object to align its intensity values + with a second diffraction object at a (closest) specified value on a specified ``xarray``. + This makes it easier for visualizing and comparing two intensity curves on the same plot. + For example, to scale ``do1`` to match ``do2`` at ``tth=60``: + +.. code-block:: python + # Create Diffraction Objects do1 and do2 + do1 = DiffractionObject( + xarray=np.array([10, 15, 25, 30, 60, 140]), + yarray=np.array([10, 20, 25, 30, 60, 100]), + xtype="tth", + wavelength=2*np.pi + ) + do2 = DiffractionObject( + xarray=np.array([10, 20, 25, 30, 60, 140]), + yarray=np.array([2, 3, 4, 5, 6, 7]), + xtype="tth", + wavelength=2*np.pi + ) + do1_scaled = do1.scale_to(do2, tth=60) + + Here, the scaling factor is computed at ``tth=60``, aligning the intensity values. + ``do1_scaled`` will have the intensity array ``np.array([1, 2, 2.5, 3, 6, 10])``. + You can also scale based on other axes (e.g., ``q=0.2``): :: + + do1_scaled = do1.scale_to(do2, q=0.2) + + The function finds the closest indices for ``q=0.2`` and scales the ``yarray`` accordingly. + + Additionally, you can apply an ``offset`` to the scaled ``yarray``. For example: :: + + do1_scaled = do1.scale_to(do2, tth=60, offset=2) # add 2 to the scaled yarray + do1_scaled = do1.scale_to(do2, tth=60, offset=-2) # subtract 2 from the scaled yarray + + This allows you to shift the scaled data for easier comparison. + +5) You can create a copy of a diffraction object using the ``copy`` function, + when you want to preserve the original data while working with a modified version. :: + + # Create a copy of Diffraction Object do + do_copy = do.copy() + +6) The ``dump`` function saves the diffraction data and relevant information to a specified file. + You can choose one of the data axis (``q``, ``tth``, or ``d``) to export, with ``q`` as the default. + +.. code-block:: python + # Assume you have created a Diffraction Object do + file = "diffraction_data.xy" + do.dump(file, xtype="q") + + In the saved file "diffraction_data.xy", + the relevant information (name, scattering quantity, metadata, etc.) is included in the header. + Your analysis time and software version are automatically recorded as well. + The diffraction data is saved as two columns: the ``q`` values and corresponding intensity values. + This ensures your diffraction data, along with all other information, + is properly documented and saved for future reference. diff --git a/doc/source/utilities/diffraction_objects_utility.rst b/doc/source/utilities/diffraction_objects_utility.rst new file mode 100644 index 00000000..f7d291de --- /dev/null +++ b/doc/source/utilities/diffraction_objects_utility.rst @@ -0,0 +1,30 @@ +.. _Diffraction Objects Utility: + +Diffraction Objects Utility +=========================== + +The ``diffpy.utils.diffraction_objects`` module provides a set of powerful functions for analyzing diffraction data. + +- ``DiffractionObject()``: This function creates a diffraction object that stores your diffraction data + and associated information. If a ``wavelength`` is specified, it can automatically populate data + across different independent axes (e.g., ``q``, ``tth``, and ``d``). + +- ``on_xtype()``: This function allows developers to access diffraction data on different independent axes + (``q``, ``tth``, and ``d``). + It is useful when you need to convert or view the data between axes, + working with the axis that best fits your analysis. + +- ``get_array_index()``: This function finds the closest index in the independent variable axis (``xarray``) + to a targeted value. It simplifies the process of working with different spacing. + +- ``scale_to()``: This function rescales one diffraction object to align with another at a specific value. + This is helpful for comparing diffraction data with different intensity values or lengths, + ensuring they are directly comparable and visually aligned. + +- ``copy()``: This function creates a deep copy of a diffraction object, + allowing you to preserve the original data while making modifications to a separate copy. + +- ``dump()``: This function saves both diffraction data and all associated information to a file. + It also automatically tracks the analysis time and software version you used. + +For a more in-depth tutorial for how to use these tools, click :ref:`here `. From 15d03bad84bd9d421c0eb28bfab32080e9ad3bd3 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 18 Dec 2024 11:58:35 -0500 Subject: [PATCH 227/445] add references on main page --- doc/source/examples/examples.rst | 1 + doc/source/utilities/utilities.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/source/examples/examples.rst b/doc/source/examples/examples.rst index 002a1e23..5e285ea1 100644 --- a/doc/source/examples/examples.rst +++ b/doc/source/examples/examples.rst @@ -11,3 +11,4 @@ Landing page for diffpy.utils examples. resample_example tools_example transforms_example + diffraction_objects_example diff --git a/doc/source/utilities/utilities.rst b/doc/source/utilities/utilities.rst index 147d7265..9ef8f4ef 100644 --- a/doc/source/utilities/utilities.rst +++ b/doc/source/utilities/utilities.rst @@ -14,3 +14,4 @@ Check the :ref:`examples` provided for how to use these. .. include:: resample_utility.rst .. include:: tools_utility.rst .. include:: transforms_utility.rst +.. include:: diffraction_objects_utility.rst From 71d6a79b76ab9829b2e7c9a8b388003b45833970 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 18 Dec 2024 18:41:17 -0500 Subject: [PATCH 228/445] simon edits to docs --- .../examples/diffraction_objects_example.rst | 198 +++++++++++------- 1 file changed, 127 insertions(+), 71 deletions(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 7d431fad..891b86b8 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -8,27 +8,30 @@ Diffraction Objects Example This example will demonstrate how to use the functions in the ``diffpy.utils.diffraction_objects`` module to create a ``DiffractionObject`` instance and analyze your diffraction data using relevant functions. -1) To create a ``DiffractionObject``, you need to specify the type of the independent variable - (referred to as ``xtype``, one of ``q``, ``tth``, or ``d``), - an ``xarray`` of the corresponding values, and a ``yarray`` of the intensity values. - It is strongly encouraged to specify the ``wavelength`` in order to access - most of the other functionalities in the class. - Additionally, you can specify the type of your scattering experiment using the ``scat_quantity`` parameter, - the name of your diffraction object using the ``name`` parameter, - and a ``metadata`` dictionary containing relevant information about the data. Here's an example: +To create a ``DiffractionObject``, you need to specify the type of the independent variable +(referred to as ``xtype``, e.g., one of ``q``, ``tth``, or ``d``), +the ``xarray`` of the ``x`` values, and a ``yarray`` of the corresponding intensity values. +It is strongly encouraged to specify the ``wavelength`` in order to access +most of the other functionalities in the class. +Additionally, you can specify the type of your scattering experiment using the ``scat_quantity`` parameter, +the name of your diffraction object using the ``name`` parameter, +and a ``metadata`` dictionary containing relevant information about the data. Here's an example: .. code-block:: python + 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 = { "sample": "rock salt from the beach", "composition": "NaCl", "temperature": "300 K,", - "experimenters": "Phill, Sally" + "experimenters": ["Phil", "Sally"] } - do = DiffractionObject( + + my_do = DiffractionObject( xarray=x, yarray=y, xtype="q", @@ -37,90 +40,143 @@ to create a ``DiffractionObject`` instance and analyze your diffraction data usi name="beach_rock_salt_1", metadata=metadata ) - print(do.metadata) - By creating a ``DiffractionObject`` instance, you store not only the diffraction data - but also all the associated information for analysis. +By creating a ``DiffractionObject`` instance, you store not only the diffraction data +but also all the associated information for analysis. -2) ``DiffractionObject`` automatically populates the ``xarray`` onto ``q``, ``tth``, and ``d``-spacing. - If you want to access your diffraction data in a specific spacing, you can do this: +``DiffractionObject`` automatically populates the ``xarray`` onto each of ``q``, ``tth``, and ``d``-spacing. +Let's say you want to plot your data vs. Q. To do this you would type .. code-block:: python - q = do.on_xtype("q") - tth = do.on_xtype("tth") - d = do.on_xtype("d") - This will return the ``xarray`` and ``yarray`` as a list of two 1D arrays, based on the specified ``xtype``. + import matplotlib.pyplot as plt -3) Once the ``DiffractionObject`` is created, you can use ``get_array_index`` to get the index of the closest value - in the ``xarray`` to a specified value. - This is useful for alignment or comparison tasks. - For example, assume you have created a ``DiffractionObject`` called ``do``, - and you want to find the closest index of ``tth=80``, you can type the following: :: + plt.plot(my_do.on_q()[0], my_do.on_q()[1]) - index = do.get_array_index(80, xtype="tth") +and to plot the same data vs. two-theta type + +.. code-block:: python - If you do not specify an ``xtype``, it will default to the ``xtype`` used when creating the ``DiffractionObject``. - For example, if you have created a ``DiffractionObject`` called ``do`` with ``xtype="q"``, - you can find its closest index for ``q=0.25`` by typing either of the following: :: + plt.plot(my_do.on_tth()[0], my_do.on_tth()[1]) - index = do.get_array_index(0.25) # no input xtype, defaults to q - index = do.get_array_index(0.25, xtype="q") +These `on_q()`, `on_tth()`, etc., methods return a list with the x-array as the first element +and the intensity array as the second element. -4) You can compare diffraction objects too. - For example, you can use the ``scale_to`` function to rescale one diffraction object to align its intensity values - with a second diffraction object at a (closest) specified value on a specified ``xarray``. - This makes it easier for visualizing and comparing two intensity curves on the same plot. - For example, to scale ``do1`` to match ``do2`` at ``tth=60``: +We can also accomplish the same thing by passing the xtype as a string to the ``on_xtype()`` method, +i.e., + +.. code-block:: python + + data_on_q = my_do.on_xtype("q") + data_on_tth = my_do.on_xtype("tth") + data_on_d = my_do.on_xtype("d") + plt.plot(data_on_d[0], data_on_d[1]) + +This makes it very easy to compare a diffractioh pattern that was measured or calculated +on one ``xtype`` with one that was measured or calculated on another. E.g., suppose that you +have a calculated powder pattern from a CIF file that was calculated on a d-spacing grid using +some software package, and +you want to know if a diffraction pattern you have measured on a Q-grid is the same material. +You could simply load them both as diffraction objects and plot them together on the same grid. + +.. code-block:: python + + calculated = DiffractionObject(xcalc, ycalc, "d") + measured = DiffractionObject(xmeas, ymeas, "tth", wavelength=0.717) + plt.plot(calculated.on_q()[0], calculated.on_q()[1]) + plt.plot(measured.on_q()[0], measured.on_q()[1]) + plt.show + +Now, let's say that these two diffraction patterns were on very different scales. The measured one +has a peak intensity of 10,000, but the calculated one only goes to 1. +With diffraction objects this is easy to handle. We choose a point on the x-axis where +we want to scale the two together and we use the ``scale_to()`` method, + +Continuing the example above, if we wanted to scale the two patterns together at a position +Q=5.5 inverse angstroms, where for the sake of argument we assume the +calculated curve has a strong peak, +we would replace the code above with + +.. code-block:: python + + plt.plot(calculated.on_q()[0], calculated.on_q()[1]) + plt.plot(measured.on_q().scale_to(calculated, q=5.5)[0], measured.on_q().scale_to(calculated, q=5.5)[1]) + plt.show + +The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new +variable and make use of, .. code-block:: python - # Create Diffraction Objects do1 and do2 - do1 = DiffractionObject( - xarray=np.array([10, 15, 25, 30, 60, 140]), - yarray=np.array([10, 20, 25, 30, 60, 100]), - xtype="tth", - wavelength=2*np.pi - ) - do2 = DiffractionObject( - xarray=np.array([10, 20, 25, 30, 60, 140]), - yarray=np.array([2, 3, 4, 5, 6, 7]), - xtype="tth", - wavelength=2*np.pi - ) - do1_scaled = do1.scale_to(do2, tth=60) - Here, the scaling factor is computed at ``tth=60``, aligning the intensity values. - ``do1_scaled`` will have the intensity array ``np.array([1, 2, 2.5, 3, 6, 10])``. - You can also scale based on other axes (e.g., ``q=0.2``): :: + scaled_measured = measured.scale_to(calculated, q=5.5) - do1_scaled = do1.scale_to(do2, q=0.2) +For convenience, you can also apply an offset to the scaled new diffraction object with the optional +``offset`` argument, for example, - The function finds the closest indices for ``q=0.2`` and scales the ``yarray`` accordingly. +.. code-block:: python + + scaled_and_offset_measured = measured.scale_to(calculated, q=5.5, offset=0.5) + +DiffractionObject convenience functions +--------------------------------------- + +1) create a copy of a diffraction object using the ``copy`` method + when you want to preserve the original data while working with a modified version. + +.. code-block:: python + + copy_of_calculated = calculated.copy() + +2) test the equality of two diffraction objects. For example, + +.. code-block:: python - Additionally, you can apply an ``offset`` to the scaled ``yarray``. For example: :: + diff_object2 = diff_object1.copy() + diff_object2 == diff_object1 - do1_scaled = do1.scale_to(do2, tth=60, offset=2) # add 2 to the scaled yarray - do1_scaled = do1.scale_to(do2, tth=60, offset=-2) # subtract 2 from the scaled yarray + will return ``True`` - This allows you to shift the scaled data for easier comparison. +3) make arithmetic operations on the intensities of diffraction objects. e.g., + +.. code-block:: python + + doubled_object = 2 * diff_object1 # Double the intensities + sum_object = diff_object1 + diff_object2 # Sum the intensities + subtract_scaled = diff_object1 - 5 * diff_object2 # subtract 5 * obj2 from obj 1 + +4) get the value of the DiffractionObject at a given point in one of the xarrays + +.. code-block:: python + + tth_ninety_index = diff_object1.get_array_index(90, xtype="tth") + intensity_at_ninety = diff_object1.on_tth()[tth_ninety_index] + +If you do not specify an ``xtype``, it will default to the ``xtype`` used when creating the ``DiffractionObject``. +For example, if you have created a ``DiffractionObject`` called ``do`` with ``xtype="q"``, +you can find its closest index for ``q=0.25`` by typing either of the following: + +.. code-block:: python -5) You can create a copy of a diffraction object using the ``copy`` function, - when you want to preserve the original data while working with a modified version. :: + print(do._input_xtype) # remind ourselves which array was input. prints "q" in this case. + index = do.get_array_index(0.25) # no xtype passed, defaults to do._input_xtype, or in this example, q + index = do.get_array_index(0.25, xtype="q") # explicitly choose an xtype to specify a value - # Create a copy of Diffraction Object do - do_copy = do.copy() +5) The ``dump`` function saves the diffraction data and relevant information to an xy format file with headers +(widely used chi format used, for example, by Fit2D and diffpy. These files can be read by ``LoadData()`` +in ``diffpy.utils.parsers``). -6) The ``dump`` function saves the diffraction data and relevant information to a specified file. - You can choose one of the data axis (``q``, ``tth``, or ``d``) to export, with ``q`` as the default. +You can choose which of the data axes (``q``, ``tth``, or ``d``) to export, with ``q`` as the default. .. code-block:: python # Assume you have created a Diffraction Object do - file = "diffraction_data.xy" + file = "diffraction_data.chi" do.dump(file, xtype="q") - In the saved file "diffraction_data.xy", - the relevant information (name, scattering quantity, metadata, etc.) is included in the header. - Your analysis time and software version are automatically recorded as well. - The diffraction data is saved as two columns: the ``q`` values and corresponding intensity values. - This ensures your diffraction data, along with all other information, - is properly documented and saved for future reference. +In the saved file ``diffraction_data.chi``, +relevant metadata are also written in the header (``username``, ``name``, ``scattering quantity``, ``metadata``, etc.). +The datetime when the DiffractionObject was created and the version of the +software (see the Section on ``get_package_info()`` for more information) +is automatically recorded as well. +The diffraction data is saved as two columns: the ``q`` values and corresponding intensity values. +This ensures your diffraction data, along with all other information, +is properly documented and saved for future reference. From 6ee7a9506e21042934f5c414a5059bea71b67be3 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 18 Dec 2024 18:46:59 -0500 Subject: [PATCH 229/445] news --- news/doccln.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/doccln.rst diff --git a/news/doccln.rst b/news/doccln.rst new file mode 100644 index 00000000..24127849 --- /dev/null +++ b/news/doccln.rst @@ -0,0 +1,23 @@ +**Added:** + +* Example docs for basic DiffractionObject usage + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 6603bb1a5efc0514411537085151ca9da61dcecf Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 18 Dec 2024 21:46:07 -0500 Subject: [PATCH 230/445] fix typos --- doc/source/examples/diffraction_objects_example.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 891b86b8..23c87e18 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -72,7 +72,7 @@ i.e., data_on_d = my_do.on_xtype("d") plt.plot(data_on_d[0], data_on_d[1]) -This makes it very easy to compare a diffractioh pattern that was measured or calculated +This makes it very easy to compare a diffraction pattern that was measured or calculated on one ``xtype`` with one that was measured or calculated on another. E.g., suppose that you have a calculated powder pattern from a CIF file that was calculated on a d-spacing grid using some software package, and @@ -85,7 +85,7 @@ You could simply load them both as diffraction objects and plot them together on measured = DiffractionObject(xmeas, ymeas, "tth", wavelength=0.717) plt.plot(calculated.on_q()[0], calculated.on_q()[1]) plt.plot(measured.on_q()[0], measured.on_q()[1]) - plt.show + plt.show() Now, let's say that these two diffraction patterns were on very different scales. The measured one has a peak intensity of 10,000, but the calculated one only goes to 1. @@ -100,8 +100,8 @@ we would replace the code above with .. code-block:: python plt.plot(calculated.on_q()[0], calculated.on_q()[1]) - plt.plot(measured.on_q().scale_to(calculated, q=5.5)[0], measured.on_q().scale_to(calculated, q=5.5)[1]) - plt.show + plt.plot(measured.scale_to(calculated, q=5.5)[0], measured.scale_to(calculated, q=5.5)[1]) + plt.show() The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new variable and make use of, @@ -134,7 +134,7 @@ DiffractionObject convenience functions diff_object2 = diff_object1.copy() diff_object2 == diff_object1 - will return ``True`` +will return ``True``. 3) make arithmetic operations on the intensities of diffraction objects. e.g., @@ -149,7 +149,7 @@ DiffractionObject convenience functions .. code-block:: python tth_ninety_index = diff_object1.get_array_index(90, xtype="tth") - intensity_at_ninety = diff_object1.on_tth()[tth_ninety_index] + intensity_at_ninety = diff_object1.on_tth()[1][tth_ninety_index] If you do not specify an ``xtype``, it will default to the ``xtype`` used when creating the ``DiffractionObject``. For example, if you have created a ``DiffractionObject`` called ``do`` with ``xtype="q"``, @@ -168,6 +168,7 @@ in ``diffpy.utils.parsers``). You can choose which of the data axes (``q``, ``tth``, or ``d``) to export, with ``q`` as the default. .. code-block:: python + # Assume you have created a Diffraction Object do file = "diffraction_data.chi" do.dump(file, xtype="q") From 82677a49068365e7ecb000c54468e6f12586d746 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 18 Dec 2024 21:52:16 -0500 Subject: [PATCH 231/445] more fix --- doc/source/examples/diffraction_objects_example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 23c87e18..8621f50f 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -100,7 +100,7 @@ we would replace the code above with .. code-block:: python plt.plot(calculated.on_q()[0], calculated.on_q()[1]) - plt.plot(measured.scale_to(calculated, q=5.5)[0], measured.scale_to(calculated, q=5.5)[1]) + plt.plot(measured.scale_to(calculated, q=5.5).on_q()[0], measured.scale_to(calculated, q=5.5).on_q()[1]) plt.show() The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new From 91eac1669b74d576ea7fea267b09de41bf68f0c9 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 19 Dec 2024 01:11:58 -0500 Subject: [PATCH 232/445] Refactor test_scale_to function --- src/diffpy/utils/transforms.py | 4 +- tests/test_diffraction_objects.py | 443 +++++++++++++++--------------- 2 files changed, 228 insertions(+), 219 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index fa3e8747..0f21cbdd 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -14,9 +14,7 @@ "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_wmsg = ( - "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." -) +inf_output_wmsg = "The largest output value in the array is infinite. This is allowed, but it will not be plotted." def _validate_inputs(q, wavelength): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index dfb67a3d..3ce1bf66 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -10,132 +10,134 @@ from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject -params = [ - ( # Compare same attributes - { - "name": "same", - "scat_quantity": "x-ray", - "wavelength": 0.71, - "xtype": "q", - "xarray": np.array([1.0, 2.0]), - "yarray": np.array([100.0, 200.0]), - "metadata": {"thing1": 1}, - }, - { - "name": "same", - "scat_quantity": "x-ray", - "wavelength": 0.71, - "xtype": "q", - "xarray": np.array([1.0, 2.0]), - "yarray": np.array([100.0, 200.0]), - "metadata": {"thing1": 1}, - }, - True, - ), - ( # Different names - { - "name": "something", - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - { - "name": "something else", - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), - ( # Different wavelengths - { - "wavelength": 0.71, - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - { - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), - ( # Different wavelengths - { - "wavelength": 0.71, - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - { - "wavelength": 0.711, - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), - ( # Different scat_quantity - { - "scat_quantity": "x-ray", - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - { - "scat_quantity": "neutron", - "xtype": "tth", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), - ( # Different on_q - { - "xtype": "q", - "xarray": np.array([1.0, 2.0]), - "yarray": np.array([100.0, 200.0]), - }, - { - "xtype": "q", - "xarray": np.array([3.0, 4.0]), - "yarray": np.array([100.0, 200.0]), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), - ( # Different metadata - { - "xtype": "q", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 0, "thing2": "thing2"}, - }, - { - "xtype": "q", - "xarray": np.empty(0), - "yarray": np.empty(0), - "metadata": {"thing1": 1, "thing2": "thing2"}, - }, - False, - ), -] - -@pytest.mark.parametrize("inputs1, inputs2, expected", params) -def test_diffraction_objects_equality(inputs1, inputs2, expected): - do_1 = DiffractionObject(**inputs1) - do_2 = DiffractionObject(**inputs2) - assert (do_1 == do_2) == expected +@pytest.mark.parametrize( + "do_args_1, do_args_2, expected_equality", + [ + # Identical args, expect equality + ( + { + "name": "same", + "scat_quantity": "x-ray", + "wavelength": 0.71, + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1}, + }, + { + "name": "same", + "scat_quantity": "x-ray", + "wavelength": 0.71, + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1}, + }, + True, + ), + ( # Different names, expect inequality + { + "name": "something", + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "name": "something else", + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ( # One without wavelnegth, expect inequality + { + "wavelength": 0.71, + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ( # Different wavelengths, expect inequality + { + "wavelength": 0.71, + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "wavelength": 0.711, + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ( # Different scat_quantity, expect inequality + { + "scat_quantity": "x-ray", + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + { + "scat_quantity": "neutron", + "xtype": "tth", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ( # Different q xarray values, expect inequality + { + "xtype": "q", + "xarray": np.array([1.0, 2.0]), + "yarray": np.array([100.0, 200.0]), + }, + { + "xtype": "q", + "xarray": np.array([3.0, 4.0]), + "yarray": np.array([100.0, 200.0]), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ( # Different metadata, expect inequality + { + "xtype": "q", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 0, "thing2": "thing2"}, + }, + { + "xtype": "q", + "xarray": np.empty(0), + "yarray": np.empty(0), + "metadata": {"thing1": 1, "thing2": "thing2"}, + }, + False, + ), + ], +) +def test_diffraction_objects_equality(do_args_1, do_args_2, expected_equality): + do_1 = DiffractionObject(**do_args_1) + do_2 = DiffractionObject(**do_args_2) + assert (do_1 == do_2) == expected_equality @pytest.mark.parametrize( @@ -165,99 +167,108 @@ def test_init_invalid_xtype(): return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="invalid_type", wavelength=1.54) -params_scale_to = [ - # UC1: same x-array and y-array, check offset - ( - { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([2, 3, 4, 5, 6, 7]), - "xtype": "tth", - "wavelength": 2 * np.pi, - "target_xarray": np.array([10, 15, 25, 30, 60, 140]), - "target_yarray": np.array([2, 3, 4, 5, 6, 7]), - "target_xtype": "tth", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": 60, - "d": None, - "offset": 2.1, - }, - {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, - ), - # UC2: same length x-arrays with exact x-value match - ( - { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([10, 20, 25, 30, 60, 100]), - "xtype": "tth", - "wavelength": 2 * np.pi, - "target_xarray": np.array([10, 20, 25, 30, 60, 140]), - "target_yarray": np.array([2, 3, 4, 5, 6, 7]), - "target_xtype": "tth", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": 60, - "d": None, - "offset": 0, - }, - {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, - ), - # UC3: same length x-arrays with approximate x-value match - ( - { - "xarray": np.array([0.12, 0.24, 0.31, 0.4]), - "yarray": np.array([10, 20, 40, 60]), - "xtype": "q", - "wavelength": 2 * np.pi, - "target_xarray": np.array([0.14, 0.24, 0.31, 0.4]), - "target_yarray": np.array([1, 3, 4, 5]), - "target_xtype": "q", - "target_wavelength": 2 * np.pi, - "q": 0.1, - "tth": None, - "d": None, - "offset": 0, - }, - {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, - ), - # UC4: different x-array lengths with approximate x-value match - ( - { - "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), - "xtype": "tth", - "wavelength": 2 * np.pi, - "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "target_xtype": "tth", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": 60, - "d": None, - "offset": 0, - }, - # scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) - {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, - ), -] - - -@pytest.mark.parametrize("inputs, expected", params_scale_to) -def test_scale_to(inputs, expected): - orig_diff_object = DiffractionObject( - xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] - ) - target_diff_object = DiffractionObject( - xarray=inputs["target_xarray"], - yarray=inputs["target_yarray"], - xtype=inputs["target_xtype"], - wavelength=inputs["target_wavelength"], - ) - scaled_diff_object = orig_diff_object.scale_to( - target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] +@pytest.mark.parametrize( + "org_do_args, target_do_args, scale_inputs, expected", + [ + # UC1: same x-array and y-array, check offset + ( + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": None, + "offset": 2.1, + }, + {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, + ), + # UC2: same length x-arrays with exact x-value match + ( + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([10, 20, 25, 30, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([10, 20, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": None, + "offset": 0, + }, + {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, + ), + # UC3: same length x-arrays with approximate x-value match + ( + { + "xarray": np.array([0.12, 0.24, 0.31, 0.4]), + "yarray": np.array([10, 20, 40, 60]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([0.14, 0.24, 0.31, 0.4]), + "yarray": np.array([1, 3, 4, 5]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "q": 0.1, + "tth": None, + "d": None, + "offset": 0, + }, + {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, + ), + # UC4: different x-array lengths with approximate x-value match + ( + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": None, + "offset": 0, + }, + # Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) + {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, + ), + ], +) +def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): + original_do = DiffractionObject(**org_do_args) + target_do = DiffractionObject(**target_do_args) + scaled_do = original_do.scale_to( + target_do, q=scale_inputs["q"], tth=scale_inputs["tth"], d=scale_inputs["d"], offset=scale_inputs["offset"] ) # Check the intensity data is the same as expected - assert np.allclose(scaled_diff_object.on_xtype(expected["xtype"])[1], expected["yarray"]) + assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) params_scale_to_bad = [ From e8fd9d13620661bd6ab2ade5a4bd3b35e85f6a60 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 20 Dec 2024 10:46:17 -0500 Subject: [PATCH 233/445] Add no news --- news/pytest-warning.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/pytest-warning.rst diff --git a/news/pytest-warning.rst b/news/pytest-warning.rst new file mode 100644 index 00000000..8900165f --- /dev/null +++ b/news/pytest-warning.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 890f7299381340a2dc4a814802f2f984338d778c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 20 Dec 2024 10:46:38 -0500 Subject: [PATCH 234/445] Change UC to Case for unit testing --- tests/test_diffraction_objects.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 3ce1bf66..b9ee3f00 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -53,7 +53,7 @@ }, False, ), - ( # One without wavelnegth, expect inequality + ( # One without wavelength, expect inequality { "wavelength": 0.71, "xtype": "tth", @@ -170,7 +170,7 @@ def test_init_invalid_xtype(): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs, expected", [ - # UC1: same x-array and y-array, check offset + # Case 1: same x-array and y-array, check offset ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -192,7 +192,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - # UC2: same length x-arrays with exact x-value match + # Case 2: same length x-arrays with exact x-value match ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -214,7 +214,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - # UC3: same length x-arrays with approximate x-value match + # Case 3: same length x-arrays with approximate x-value match ( { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), @@ -236,7 +236,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - # UC4: different x-array lengths with approximate x-value match + # Case 4: different x-array lengths with approximate x-value match ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -256,7 +256,7 @@ def test_init_invalid_xtype(): "d": None, "offset": 0, }, - # Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) + # Case 5: Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), ], From 5080163ccf934979992fb4b3be11a26301399e97 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 20 Dec 2024 11:23:07 -0500 Subject: [PATCH 235/445] Remove 6 pytest warnings with capturing wavelength --- news/pytest-warnings-others.rst | 23 +++++++++++++++++++++++ tests/test_transforms.py | 18 ++++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 news/pytest-warnings-others.rst diff --git a/news/pytest-warnings-others.rst b/news/pytest-warnings-others.rst new file mode 100644 index 00000000..8900165f --- /dev/null +++ b/news/pytest-warnings-others.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_transforms.py b/tests/test_transforms.py index cf6a6ebb..0ed522ce 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -171,8 +171,13 @@ def test_d_to_q(d, expected_q): ), ], ) -def test_tth_to_d(wavelength, tth, expected_d): - actual_d = tth_to_d(tth, wavelength) +def test_tth_to_d(wavelength, tth, expected_d, wavelength_warning_msg): + if wavelength is None: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + actual_d = tth_to_d(tth, wavelength) + else: + actual_d = tth_to_d(tth, wavelength) + assert np.allclose(actual_d, expected_d) @@ -221,8 +226,13 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m ), ], ) -def test_d_to_tth(wavelength, d, expected_tth): - actual_tth = d_to_tth(d, wavelength) +def test_d_to_tth(wavelength, d, expected_tth, wavelength_warning_msg): + if wavelength is None: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + actual_tth = d_to_tth(d, wavelength) + else: + actual_tth = d_to_tth(d, wavelength) + assert np.allclose(actual_tth, expected_tth) From 2a3d7c89d83855342f3fc72ea8763a41fb6e5bbb Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 20 Dec 2024 11:53:21 -0500 Subject: [PATCH 236/445] Refactor expecting divison by zero test --- news/pytest-handle-warning.rst | 23 +++++++++++++++++++++++ tests/test_transforms.py | 25 +++++++++++++++++++------ 2 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 news/pytest-handle-warning.rst diff --git a/news/pytest-handle-warning.rst b/news/pytest-handle-warning.rst new file mode 100644 index 00000000..8900165f --- /dev/null +++ b/news/pytest-handle-warning.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_transforms.py b/tests/test_transforms.py index cf6a6ebb..fc4e4991 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -117,19 +117,32 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): @pytest.mark.parametrize( - "q, expected_d", + "q, expected_d, warning_expected", [ - # UC1: User specified empty q values - (np.array([]), np.array([])), - # UC2: User specified valid q values + # Case 1: empty q values, expect empty d values + (np.array([]), np.array([]), False), + + # Case 2: + # 1. valid q values, expect d values without warning + ( + np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), + np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), + False + ), + # 2. valid q values containing 0, expect d values with divide by zero warning ( np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), + True ), ], ) -def test_q_to_d(q, expected_d): - actual_d = q_to_d(q) +def test_q_to_d(q, expected_d, warning_expected): + if warning_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_d = q_to_d(q) + else: + actual_d = q_to_d(q) assert np.allclose(actual_d, expected_d) From f8704f1b0dcbf90082968e9e502f2969d090708b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:55:34 +0000 Subject: [PATCH 237/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index fc4e4991..fb1d0f17 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -121,19 +121,18 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): [ # Case 1: empty q values, expect empty d values (np.array([]), np.array([]), False), - # Case 2: # 1. valid q values, expect d values without warning ( np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), - False + False, ), # 2. valid q values containing 0, expect d values with divide by zero warning ( np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), - True + True, ), ], ) From 49a1f7a190ab1c9abb2054975a1726b564a851cd Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 20 Dec 2024 15:57:51 -0500 Subject: [PATCH 238/445] add INFO back to info message --- src/diffpy/utils/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 0f21cbdd..fbd481a0 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -14,7 +14,7 @@ "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_wmsg = "The largest output value in the array is infinite. This is allowed, but it will not be plotted." +inf_output_imsg = "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." def _validate_inputs(q, wavelength): From ba488b82882d0366cdd1da5525b1b7e68efdf6a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:01:14 +0000 Subject: [PATCH 239/445] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/transforms.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index fbd481a0..4b6c931e 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -14,7 +14,9 @@ "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_imsg = "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." +inf_output_imsg = ( + "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." +) def _validate_inputs(q, wavelength): From fbfd3be192d512848afc7b2a6fd0cdaf7fb63f78 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 20 Dec 2024 16:01:44 -0500 Subject: [PATCH 240/445] added high level meta-description of the group of tests in __eq__ --- tests/test_diffraction_objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index b9ee3f00..e561244e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -14,6 +14,7 @@ @pytest.mark.parametrize( "do_args_1, do_args_2, expected_equality", [ + # Test when __eqal__ returns True and False # Identical args, expect equality ( { From b3110bfcaef4a8176040a2d0d62cc303da2a3b0b Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 20 Dec 2024 16:04:15 -0500 Subject: [PATCH 241/445] meta description for scale_to tests --- tests/test_diffraction_objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index e561244e..26893904 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -171,6 +171,7 @@ def test_init_invalid_xtype(): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs, expected", [ + # Test that scale_to() scales to the correct values # Case 1: same x-array and y-array, check offset ( { From 206d39d182c40f4ab7ccd5ff1678cca6bfb43839 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Fri, 20 Dec 2024 16:11:09 -0500 Subject: [PATCH 242/445] fix info message name and change warn to print in q to do transforms. --- src/diffpy/utils/transforms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 4b6c931e..1d50aeab 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -135,7 +135,7 @@ def q_to_d(q): The array of :math:`d` values np.array([ds]). """ if 0 in q: - print(inf_output_wmsg) + print(inf_output_imsg) return 2.0 * np.pi / copy(q) @@ -169,7 +169,7 @@ def tth_to_d(tth, wavelength): d[i] = i return d if 0 in q: - warnings.warn(inf_output_wmsg) + print(inf_output_imsg) return 2.0 * np.pi / copy(q) @@ -189,7 +189,7 @@ def d_to_q(d): The units of q must be reciprocal of the units of wavelength. """ if 0 in d: - warnings.warn(inf_output_wmsg) + print(inf_output_imsg) return 2.0 * np.pi / copy(d) From 12e2b89444edc933546cddacef9b74bd1149ffb3 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 20 Dec 2024 18:41:00 -0500 Subject: [PATCH 243/445] Add higher-level comment to test func and add news --- news/pytest-handle-warning.rst | 2 +- tests/test_transforms.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/news/pytest-handle-warning.rst b/news/pytest-handle-warning.rst index 8900165f..81bf78bf 100644 --- a/news/pytest-handle-warning.rst +++ b/news/pytest-handle-warning.rst @@ -1,6 +1,6 @@ **Added:** -* No news added +* catch division by zero warning messages in tests **Changed:** diff --git a/tests/test_transforms.py b/tests/test_transforms.py index fb1d0f17..14a126ae 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -119,8 +119,10 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): @pytest.mark.parametrize( "q, expected_d, warning_expected", [ + # Test conversion of q to d with valid values # Case 1: empty q values, expect empty d values (np.array([]), np.array([]), False), + # Case 2: # 1. valid q values, expect d values without warning ( From 765c9e21920c231305b846f020fcaa706fe2cdde Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 23:41:09 +0000 Subject: [PATCH 244/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 14a126ae..188ff150 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -122,7 +122,6 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): # Test conversion of q to d with valid values # Case 1: empty q values, expect empty d values (np.array([]), np.array([]), False), - # Case 2: # 1. valid q values, expect d values without warning ( From e9a3113b21b07a1e8ee0709d60d18d2d63f36a29 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 01:29:22 -0500 Subject: [PATCH 245/445] Catch expected wavelength warnings --- tests/test_diffraction_objects.py | 22 +++++++++++++++++----- tests/test_transforms.py | 1 - 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 26893904..382f5776 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -12,9 +12,9 @@ @pytest.mark.parametrize( - "do_args_1, do_args_2, expected_equality", + "do_args_1, do_args_2, expected_equality, warning_expected", [ - # Test when __eqal__ returns True and False + # Test when __eq__ returns True and False # Identical args, expect equality ( { @@ -36,6 +36,7 @@ "metadata": {"thing1": 1}, }, True, + False ), ( # Different names, expect inequality { @@ -53,6 +54,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + True ), ( # One without wavelength, expect inequality { @@ -69,6 +71,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + True ), ( # Different wavelengths, expect inequality { @@ -86,6 +89,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + False ), ( # Different scat_quantity, expect inequality { @@ -103,6 +107,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + True ), ( # Different q xarray values, expect inequality { @@ -117,6 +122,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + True ), ( # Different metadata, expect inequality { @@ -132,12 +138,18 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, + True ), ], ) -def test_diffraction_objects_equality(do_args_1, do_args_2, expected_equality): - do_1 = DiffractionObject(**do_args_1) - do_2 = DiffractionObject(**do_args_2) +def test_diffraction_objects_equality(do_args_1, do_args_2, expected_equality, warning_expected, wavelength_warning_msg): + if warning_expected: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + do_1 = DiffractionObject(**do_args_1) + do_2 = DiffractionObject(**do_args_2) + else: + do_1 = DiffractionObject(**do_args_1) + do_2 = DiffractionObject(**do_args_2) assert (do_1 == do_2) == expected_equality diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 0ed522ce..5493ea19 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -27,7 +27,6 @@ ], ) def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): - if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): actual_tth = q_to_tth(q, wavelength) From 968219fb742a2a07b2a312b0870f0f8c3951333a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 01:29:29 -0500 Subject: [PATCH 246/445] Add news --- news/pytest-wavelength-warnings.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/pytest-wavelength-warnings.rst diff --git a/news/pytest-wavelength-warnings.rst b/news/pytest-wavelength-warnings.rst new file mode 100644 index 00000000..3446720a --- /dev/null +++ b/news/pytest-wavelength-warnings.rst @@ -0,0 +1,23 @@ +**Added:** + +* unit test for expected warning when no wavelength is provided for DiffractionObject init + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 13b42b527b42e674324bab6005f1628007705c80 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 06:35:35 +0000 Subject: [PATCH 247/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 382f5776..220a4aad 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -36,7 +36,7 @@ "metadata": {"thing1": 1}, }, True, - False + False, ), ( # Different names, expect inequality { @@ -54,7 +54,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - True + True, ), ( # One without wavelength, expect inequality { @@ -71,7 +71,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - True + True, ), ( # Different wavelengths, expect inequality { @@ -89,7 +89,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - False + False, ), ( # Different scat_quantity, expect inequality { @@ -107,7 +107,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - True + True, ), ( # Different q xarray values, expect inequality { @@ -122,7 +122,7 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - True + True, ), ( # Different metadata, expect inequality { @@ -138,11 +138,13 @@ "metadata": {"thing1": 1, "thing2": "thing2"}, }, False, - True + True, ), ], ) -def test_diffraction_objects_equality(do_args_1, do_args_2, expected_equality, warning_expected, wavelength_warning_msg): +def test_diffraction_objects_equality( + do_args_1, do_args_2, expected_equality, warning_expected, wavelength_warning_msg +): if warning_expected: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): do_1 = DiffractionObject(**do_args_1) From b7ad8104f20009562749a3bace7ea40d5eb3a218 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 13:23:03 -0500 Subject: [PATCH 248/445] catch divide by zero in dump func --- tests/test_diffraction_objects.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 220a4aad..fe4cac4e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -373,15 +373,16 @@ def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - do = DiffractionObject( - wavelength=1.54, - name="test", - scat_quantity="x-ray", - xarray=np.array(x), - yarray=np.array(y), - xtype="q", - metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, - ) + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + do = DiffractionObject( + wavelength=1.54, + name="test", + scat_quantity="x-ray", + xarray=np.array(x), + yarray=np.array(y), + xtype="q", + metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, + ) mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): do.dump(file, "q") From 25cb8750daf2f272c04f35f09be5f86b41b1a700 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 13:34:32 -0500 Subject: [PATCH 249/445] Catch warnings in zero to test_tth_to_d --- tests/test_transforms.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 7a3bdeef..774bb781 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -163,33 +163,38 @@ def test_d_to_q(d, expected_q): @pytest.mark.parametrize( - "wavelength, tth, expected_d", - [ - # UC0: User specified empty tth values (without wavelength) - (None, np.array([]), np.array([])), - # UC1: User specified empty tth values (with wavelength) - (4 * np.pi, np.array([]), np.array([])), - # UC2: User specified valid tth values between 0-180 degrees (without wavelength) + "wavelength, tth, expected_d, divide_by_zero_warning_expected", + [ + # Test conversion of q to d with valid values + # Case 1: empty tth values, no, expect empty d values + (None, np.array([]), np.array([]), False), + # Case 2: empty tth values, wavelength provided, expect empty d values + (4 * np.pi, np.array([]), np.array([]), False), + # Case 3: User specified valid tth values between 0-180 degrees (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), + False ), - # UC3: User specified valid tth values between 0-180 degrees (with wavelength) + # Case 4: User specified valid tth values between 0-180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), + True ), ], ) -def test_tth_to_d(wavelength, tth, expected_d, wavelength_warning_msg): +def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, wavelength_warning_msg): if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): actual_d = tth_to_d(tth, wavelength) + elif divide_by_zero_warning_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_d = tth_to_d(tth, wavelength) else: actual_d = tth_to_d(tth, wavelength) - assert np.allclose(actual_d, expected_d) From 9c8fd9524f4db9fa2fe18f0b10b3cb6fcdc43fef Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 13:59:13 -0500 Subject: [PATCH 250/445] Fix another divide by zero warning in transform --- tests/test_diffraction_objects.py | 185 ++++++++++++++---------------- tests/test_transforms.py | 24 ++-- 2 files changed, 95 insertions(+), 114 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index fe4cac4e..cff847a2 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -403,114 +403,101 @@ def test_dump(tmp_path, mocker): assert actual == expected -test_init_valid_params = [ - ( # instantiate just array attributes - { - "xarray": np.array([0.0, 90.0, 180.0]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "tth", - "wavelength": 4.0 * np.pi, - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "tth", - "name": "", - "scat_quantity": "", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), - ( # instantiate just array attributes - { - "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "d", - "wavelength": 4.0 * np.pi, - "scat_quantity": "x-ray", - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "d", - "name": "", - "scat_quantity": "x-ray", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), -] - - @pytest.mark.parametrize( - "init_args, expected_do_dict", - test_init_valid_params, + "do_init_args, expected_do_dict", + [ + ( # instantiate just array attributes + { + "xarray": np.array([0.0, 90.0, 180.0]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "tth", + "wavelength": 4.0 * np.pi, + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "tth", + "name": "", + "scat_quantity": "", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ( # instantiate just array attributes + { + "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "d", + "wavelength": 4.0 * np.pi, + "scat_quantity": "x-ray", + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "d", + "name": "", + "scat_quantity": "x-ray", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + ), + ], ) -def test_init_valid(init_args, expected_do_dict): - actual_do_dict = DiffractionObject(**init_args).__dict__ +def test_init_valid(do_init_args, expected_do_dict): + actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']" ) assert diff == {} -test_init_invalid_params = [ - ( # UC1: no arguments provided - {}, - "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", - ), - ( # UC2: only xarray and yarray provided - {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, - "missing 1 required positional argument: 'xtype'", - ), -] - - -@pytest.mark.parametrize("init_args, expected_error_msg", test_init_invalid_params) +@pytest.mark.parametrize( + "do_init_args, expected_error_msg", + [ + ( # Case 1: no arguments provided + {}, + "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ), + ( # Case 2: only xarray and yarray provided + {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, + "missing 1 required positional argument: 'xtype'", + ), + ], +) def test_init_invalid_args( - init_args, + do_init_args, expected_error_msg, ): with pytest.raises(TypeError, match=expected_error_msg): - DiffractionObject(**init_args) + DiffractionObject(**do_init_args) -def test_all_array_getter(): - actual_do = DiffractionObject( - xarray=np.array([0.0, 90.0, 180.0]), - yarray=np.array([1.0, 2.0, 3.0]), - xtype="tth", - wavelength=4.0 * np.pi, - ) - expected_all_arrays = np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ) +def test_all_array_getter(do_minimal_tth): + actual_do = do_minimal_tth + print(actual_do.all_arrays) + expected_all_arrays = [[1, 0.51763809, 30, 12.13818192], [2, 1, 60, 6.28318531]] assert np.allclose(actual_do.all_arrays, expected_all_arrays) @@ -575,14 +562,8 @@ def test_input_xtype_setter_error(do_minimal): do.input_xtype = "q" -def test_copy_object(): - do = DiffractionObject( - name="test", - wavelength=4.0 * np.pi, - xarray=np.array([0.0, 90.0, 180.0]), - yarray=np.array([1.0, 2.0, 3.0]), - xtype="tth", - ) +def test_copy_object(do_minimal): + do = do_minimal do_copy = do.copy() assert do == do_copy assert id(do) != id(do_copy) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 774bb781..9962b359 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -146,43 +146,43 @@ def test_q_to_d(q, expected_d, warning_expected): @pytest.mark.parametrize( - "d, expected_q", + "d, expected_q, zero_divide_error_expected", [ # UC1: User specified empty d values - (np.array([]), np.array([])), + (np.array([]), np.array([]), False), # UC2: User specified valid d values ( np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), + True, ), ], ) -def test_d_to_q(d, expected_q): - actual_q = d_to_q(d) +def test_d_to_q(d, expected_q, zero_divide_error_expected): + if zero_divide_error_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_q = d_to_q(d) + else: + actual_q = d_to_q(d) assert np.allclose(actual_q, expected_q) @pytest.mark.parametrize( "wavelength, tth, expected_d, divide_by_zero_warning_expected", - [ + [ # Test conversion of q to d with valid values # Case 1: empty tth values, no, expect empty d values (None, np.array([]), np.array([]), False), # Case 2: empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), # Case 3: User specified valid tth values between 0-180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 180]), - np.array([0, 1, 2, 3, 4, 5]), - False - ), + (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), # Case 4: User specified valid tth values between 0-180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), - True + True, ), ], ) From 102310be1b70abd684c2e4349e3b9ba9745e3fca Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 14:09:47 -0500 Subject: [PATCH 251/445] Fix remaining divide by zero --- tests/test_diffraction_objects.py | 12 +++++++++--- tests/test_transforms.py | 23 ++++++++++++----------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index cff847a2..6d9bdf28 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -404,7 +404,7 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( - "do_init_args, expected_do_dict", + "do_init_args, expected_do_dict, divide_by_zero_warning_expected", [ ( # instantiate just array attributes { @@ -433,6 +433,7 @@ def test_dump(tmp_path, mocker): "dmax": np.float64(np.inf), "wavelength": 4.0 * np.pi, }, + True ), ( # instantiate just array attributes { @@ -462,11 +463,16 @@ def test_dump(tmp_path, mocker): "dmax": np.float64(np.inf), "wavelength": 4.0 * np.pi, }, + False ), ], ) -def test_init_valid(do_init_args, expected_do_dict): - actual_do_dict = DiffractionObject(**do_init_args).__dict__ +def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expected): + if divide_by_zero_warning_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_do_dict = DiffractionObject(**do_init_args).__dict__ + else: + actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']" ) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 9962b359..9b85a6de 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -223,30 +223,31 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m @pytest.mark.parametrize( - "wavelength, d, expected_tth", + "wavelength, d, expected_tth, divide_by_zero_warning_expected", [ # UC1: Empty d values, no wavelength, return empty arrays - (None, np.empty((0)), np.empty((0))), + (None, np.empty((0)), np.empty((0)), False), # UC2: Empty d values, wavelength specified, return empty arrays - (4 * np.pi, np.empty((0)), np.empty(0)), + (4 * np.pi, np.empty((0)), np.empty(0), False), # UC3: User specified valid d values, no wavelength, return empty arrays - ( - None, - np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), - np.array([0, 1, 2, 3, 4, 5]), - ), + (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), # UC4: User specified valid d values (with wavelength) ( 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), + False ), ], ) -def test_d_to_tth(wavelength, d, expected_tth, wavelength_warning_msg): - if wavelength is None: +def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, wavelength_warning_msg): + if wavelength is None and not divide_by_zero_warning_expected: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): actual_tth = d_to_tth(d, wavelength) + elif wavelength is None and divide_by_zero_warning_expected: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_tth = d_to_tth(d, wavelength) else: actual_tth = d_to_tth(d, wavelength) @@ -259,7 +260,7 @@ def test_d_to_tth(wavelength, d, expected_tth, wavelength_warning_msg): # UC1: user specified invalid d values that result in tth > 180 degrees (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), # UC2: user specified a wrong wavelength that result in tth > 180 degrees - (100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), ValueError), + (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) def test_d_to_tth_bad(wavelength, d, expected_error_type, invalid_q_or_d_or_wavelength_error_msg): From efb0585ebc0668c620bd91328da5ddeb8330390e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 14:10:14 -0500 Subject: [PATCH 252/445] Apply pre-commit --- tests/test_diffraction_objects.py | 4 ++-- tests/test_transforms.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6d9bdf28..4576b257 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -433,7 +433,7 @@ def test_dump(tmp_path, mocker): "dmax": np.float64(np.inf), "wavelength": 4.0 * np.pi, }, - True + True, ), ( # instantiate just array attributes { @@ -463,7 +463,7 @@ def test_dump(tmp_path, mocker): "dmax": np.float64(np.inf), "wavelength": 4.0 * np.pi, }, - False + False, ), ], ) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 9b85a6de..65f437ac 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -236,7 +236,7 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), - False + False, ), ], ) From 3287747e5bf68cf69f9e6766d5a06f7ddd3cf1a4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 14:12:47 -0500 Subject: [PATCH 253/445] Add news --- news/pytest-warning-divide-zero.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/pytest-warning-divide-zero.rst diff --git a/news/pytest-warning-divide-zero.rst b/news/pytest-warning-divide-zero.rst new file mode 100644 index 00000000..4b84dba2 --- /dev/null +++ b/news/pytest-warning-divide-zero.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: previous news created for catching divide by zero warnings in pytest + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 5d22f1c6561b0ab5c37663c62469a22108b883c1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 14:55:22 -0500 Subject: [PATCH 254/445] Apply prettier pre-commit --- .codecov.yml | 8 ++--- .github/ISSUE_TEMPLATE/release_checklist.md | 8 ++--- .../workflows/build-wheel-release-upload.yml | 2 +- .github/workflows/check-news-item.yml | 2 +- .pre-commit-config.yaml | 31 ++++++++++++------- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 5a94096e..4af5eb24 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,14 +1,14 @@ coverage: status: - project: # more options at https://docs.codecov.com/docs/commit-status + project: # more options at https://docs.codecov.com/docs/commit-status default: target: auto # use the coverage from the base commit, fail if coverage is lower - threshold: 0% # allow the coverage to drop by + threshold: 0% # allow the coverage to drop by comment: layout: " diff, flags, files" behavior: default require_changes: false - require_base: false # [true :: must have a base report to post] - require_head: false # [true :: must have a head report to post] + require_base: false # [true :: must have a base report to post] + require_head: false # [true :: must have a head report to post] hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 11df804d..fa94779e 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -12,11 +12,11 @@ assignees: "" - [ ] All the badges on the README are passing. - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are - missing), tutorials, and other human-written text is up-to-date with any changes in the code. + missing), tutorials, and other human-written text is up-to-date with any changes in the code. - [ ] Installation instructions in the README, documentation, and the website (e.g., diffpy.org) are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). -- [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. +- [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: @@ -42,5 +42,5 @@ Please let @sbillinge know that all checks are done and the package is ready for -- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. -- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. +- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. +- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 0c9f1357..841d1a76 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: push: tags: - - '*' # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml + - "*" # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml jobs: release: diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index be549960..aa040f09 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -3,7 +3,7 @@ name: Check for News on: pull_request_target: branches: - - main + - main jobs: check-news-item: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cf0556f..9518b6eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,14 @@ default_language_version: - python: python3 + python: python3 ci: - autofix_commit_msg: | - [pre-commit.ci] auto fixes from pre-commit hooks - autofix_prs: true - autoupdate_branch: 'pre-commit-autoupdate' - autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate' - autoupdate_schedule: monthly - skip: [no-commit-to-branch] - submodules: false + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit hooks + autofix_prs: true + autoupdate_branch: "pre-commit-autoupdate" + autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate" + autoupdate_schedule: monthly + skip: [no-commit-to-branch] + submodules: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 @@ -47,6 +47,13 @@ repos: - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - - id: codespell - additional_dependencies: - - tomli + - id: codespell + additional_dependencies: + - tomli + # prettier - multi formatter + - repo: https://github.com/pre-commit/mirrors-prettier + rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 + hooks: + - id: prettier + additional_dependencies: + - "prettier@^3.2.4" From 2f620bc16bf471939bd4d7219b8aaaf322c871ba Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 15:08:22 -0500 Subject: [PATCH 255/445] Add comment for codespell and prettier --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9518b6eb..b7fb3631 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,13 +44,14 @@ repos: name: Prevent Commit to Main Branch args: ["--branch", "main"] stages: [pre-commit] + # codespell - spell checker for source code - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - id: codespell additional_dependencies: - tomli - # prettier - multi formatter + # prettier - multi formatter for json, yaml, md - repo: https://github.com/pre-commit/mirrors-prettier rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 hooks: From f79e88eadfcd7b58e84c6caa591a960d79689ba9 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 15:13:52 -0500 Subject: [PATCH 256/445] Add news --- news/prettier-pre-commit.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/prettier-pre-commit.rst diff --git a/news/prettier-pre-commit.rst b/news/prettier-pre-commit.rst new file mode 100644 index 00000000..d61d93ed --- /dev/null +++ b/news/prettier-pre-commit.rst @@ -0,0 +1,23 @@ +**Added:** + +* prettier pre-commit hook for automatic linting of .yml, .json, and .md files + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From ab0fc8a9f39d5e0bfbf876b5919442bf76b323d2 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 21 Dec 2024 16:08:38 -0500 Subject: [PATCH 257/445] refactor to separate getting info and creating config files --- news/userinfo.rst | 24 +++++++++ src/diffpy/utils/tools.py | 46 ++++++++--------- tests/conftest.py | 12 +++-- tests/test_tools.py | 104 ++++++++++++++++++++++++++++---------- 4 files changed, 129 insertions(+), 57 deletions(-) create mode 100644 news/userinfo.rst diff --git a/news/userinfo.rst b/news/userinfo.rst new file mode 100644 index 00000000..124b49f8 --- /dev/null +++ b/news/userinfo.rst @@ -0,0 +1,24 @@ +**Added:** + +* + +**Changed:** + +* Refactor get_user_info to separate the tasks of getting the info from config files + and creating config files when they are missing. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3fc10031..234aedb2 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,6 +1,5 @@ import importlib.metadata import json -import os from copy import copy from pathlib import Path @@ -56,7 +55,7 @@ def load_config(file_path): Returns ------- dict: - The configuration dictionary or None if file does not exist. + The configuration dictionary or {} if the config file does not exist. """ config_file = Path(file_path).resolve() @@ -65,7 +64,7 @@ def load_config(file_path): config = json.load(f) return config else: - return None + return {} def _sorted_merge(*dicts): @@ -91,9 +90,9 @@ def _create_global_config(args): return return_bool -def get_user_info(args=None): +def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): """ - Get username and email configuration. + Get username, email and orcid configuration. First attempts to load config file from global and local paths. If neither exists, creates a global config file. @@ -111,27 +110,26 @@ def get_user_info(args=None): The dictionary containing username and email with corresponding values. """ - config_bool = True + runtime_info = {"owner_name": owner_name, "owner_email": owner_email, "owner_orcid": owner_orcid} + for key, value in copy(runtime_info).items(): + if value is None or value == "": + del runtime_info[key] global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") - if global_config is None and local_config is None: - print( - "No global configuration file was found containing " - "information about the user to associate with the data.\n" - "By following the prompts below you can add your name and email to this file on the current " - "computer and your name will be automatically associated with subsequent diffpy data by default.\n" - "This is not recommended on a shared or public computer. " - "You will only have to do that once.\n" - "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" - ) - config_bool = _create_global_config(args) - global_config = load_config(Path().home() / "diffpyconfig.json") - config = _sorted_merge(clean_dict(global_config), clean_dict(local_config), clean_dict(args)) - if config_bool is False: - os.remove(Path().home() / "diffpyconfig.json") - config = {"username": "", "email": ""} - - return config + # if global_config is None and local_config is None: + # print( + # "No global configuration file was found containing " + # "information about the user to associate with the data.\n" + # "By following the prompts below you can add your name and email to this file on the current " + # "computer and your name will be automatically associated with subsequent diffpy data by default.\n" + # "This is not recommended on a shared or public computer. " + # "You will only have to do that once.\n" + # "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" + # ) + user_info = global_config + user_info.update(local_config) + user_info.update(runtime_info) + return user_info def get_package_info(package_names, metadata=None): diff --git a/tests/conftest.py b/tests/conftest.py index aa76715f..7f8de460 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,14 +12,16 @@ def user_filesystem(tmp_path): base_dir = Path(tmp_path) home_dir = base_dir / "home_dir" home_dir.mkdir(parents=True, exist_ok=True) - cwd_dir = base_dir / "cwd_dir" + cwd_dir = home_dir / "cwd_dir" cwd_dir.mkdir(parents=True, exist_ok=True) - - home_config_data = {"username": "home_username", "email": "home@email.com"} + home_config_data = { + "owner_name": "home_ownername", + "owner_email": "home@email.com", + "owner_orcid": "home_orcid", + } with open(home_dir / "diffpyconfig.json", "w") as f: json.dump(home_config_data, f) - - yield tmp_path + yield home_dir, cwd_dir @pytest.fixture diff --git a/tests/test_tools.py b/tests/test_tools.py index 0a42332f..c98acb07 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -8,11 +8,9 @@ from diffpy.utils.tools import get_package_info, get_user_info -def _setup_dirs(monkeypatch, user_filesystem): - cwd = Path(user_filesystem) - home_dir = cwd / "home_dir" - monkeypatch.setattr("pathlib.Path.home", lambda _: home_dir) - os.chdir(cwd) +def _setup_dirs(user_filesystem): + home_dir, cwd_dir = user_filesystem.home_dir, user_filesystem.cwd_dir + os.chdir(cwd_dir) return home_dir @@ -24,15 +22,6 @@ def _run_tests(inputs, expected): assert config.get("email") == expected_email -params_user_info_with_home_conf_file = [ - (["", ""], ["home_username", "home@email.com"]), - (["cli_username", ""], ["cli_username", "home@email.com"]), - (["", "cli@email.com"], ["home_username", "cli@email.com"]), - ([None, None], ["home_username", "home@email.com"]), - (["cli_username", None], ["cli_username", "home@email.com"]), - ([None, "cli@email.com"], ["home_username", "cli@email.com"]), - (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), -] params_user_info_with_local_conf_file = [ (["", ""], ["cwd_username", "cwd@email.com"]), (["cli_username", ""], ["cli_username", "cwd@email.com"]), @@ -84,21 +73,80 @@ def _run_tests(inputs, expected): ] -@pytest.mark.parametrize("inputs, expected", params_user_info_with_home_conf_file) -def test_get_user_info_with_home_conf_file(monkeypatch, inputs, expected, user_filesystem): - _setup_dirs(monkeypatch, user_filesystem) - _run_tests(inputs, expected) - - -@pytest.mark.parametrize("inputs, expected", params_user_info_with_local_conf_file) -def test_get_user_info_with_local_conf_file(monkeypatch, inputs, expected, user_filesystem): - _setup_dirs(monkeypatch, user_filesystem) - local_config_data = {"username": "cwd_username", "email": "cwd@email.com"} - with open(Path(user_filesystem) / "diffpyconfig.json", "w") as f: +@pytest.mark.parametrize( + "runtime_inputs, expected", + [ # config file in home is present, no config in cwd. various runtime values passed + # C1: nothing passed in, expect uname, email, orcid from home_config + ({}, {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}), + # C2: empty strings passed in, expect uname, email, orcid from home_config + ( + {"owner_name": "", "owner_email": "", "owner_orcid": ""}, + {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, + ), + # C3: just owner name passed in at runtime. expect runtime_oname but others from config + ( + {"owner_name": "runtime_ownername"}, + {"owner_name": "runtime_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, + ), + # C4: just owner email passed in at runtime. expect runtime_email but others from config + ( + {"owner_email": "runtime@email.com"}, + {"owner_name": "home_ownername", "owner_email": "runtime@email.com", "owner_orcid": "home_orcid"}, + ), + # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config + ( + {"owner_orcid": "runtime_orcid"}, + {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "runtime_orcid"}, + ), + ], +) +def test_get_user_info_with_home_conf_file(runtime_inputs, expected, user_filesystem, mocker): + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] + # is tmp_dir/cwd_dir + mocker.patch.object(Path, "home", return_value=user_filesystem[0]) + os.chdir(user_filesystem[1]) + actual = get_user_info(**runtime_inputs) + assert actual == expected + + +@pytest.mark.parametrize( + "runtime_inputs, expected", + [ # tests as before but now config file present in cwd and home but orcid + # missing in the cwd config + # C1: nothing passed in, expect uname, email from local config, orcid from home_config + ({}, {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}), + # C2: empty strings passed in, expect uname, email, orcid from home_config + ( + {"owner_name": "", "owner_email": "", "owner_orcid": ""}, + {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}, + ), + # C3: just owner name passed in at runtime. expect runtime_oname but others from config + ( + {"owner_name": "runtime_ownername"}, + {"owner_name": "runtime_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}, + ), + # C4: just owner email passed in at runtime. expect runtime_email but others from config + ( + {"owner_email": "runtime@email.com"}, + {"owner_name": "cwd_ownername", "owner_email": "runtime@email.com", "owner_orcid": "home_orcid"}, + ), + # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config + ( + {"owner_orcid": "runtime_orcid"}, + {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "runtime_orcid"}, + ), + ], +) +def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_filesystem, mocker): + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] i + # s tmp_dir/cwd_dir + mocker.patch.object(Path, "home", return_value=user_filesystem[0]) + os.chdir(user_filesystem[1]) + local_config_data = {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com"} + with open(user_filesystem[1] / "diffpyconfig.json", "w") as f: json.dump(local_config_data, f) - _run_tests(inputs, expected) - os.remove(Path().home() / "diffpyconfig.json") - _run_tests(inputs, expected) + actual = get_user_info(**runtime_inputs) + assert actual == expected @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) From b819f70349115b2a859eb0190a73b57bfa869b5e Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 21 Dec 2024 16:23:06 -0500 Subject: [PATCH 258/445] commenting out broken tests that will be fixed in make_config PR --- tests/test_tools.py | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index c98acb07..cf730ddd 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -7,11 +7,11 @@ from diffpy.utils.tools import get_package_info, get_user_info - -def _setup_dirs(user_filesystem): - home_dir, cwd_dir = user_filesystem.home_dir, user_filesystem.cwd_dir - os.chdir(cwd_dir) - return home_dir +# def _setup_dirs(monkeypatch, user_filesystem): +# home_dir, cwd_dir = user_filesystem.home_dir, user_filesystem.cwd_dir +# os.chdir(cwd_dir) +# return home_dir +# def _run_tests(inputs, expected): @@ -138,8 +138,8 @@ def test_get_user_info_with_home_conf_file(runtime_inputs, expected, user_filesy ], ) def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_filesystem, mocker): - # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] i - # s tmp_dir/cwd_dir + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] + # is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) local_config_data = {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com"} @@ -149,27 +149,27 @@ def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_files assert actual == expected -@pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) -def test_get_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): - _setup_dirs(monkeypatch, user_filesystem) - os.remove(Path().home() / "diffpyconfig.json") - inp_iter = iter(inputsb) - monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) - _run_tests(inputsa, expected) - confile = Path().home() / "diffpyconfig.json" - assert confile.is_file() - - -@pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs) -def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): - _setup_dirs(monkeypatch, user_filesystem) - os.remove(Path().home() / "diffpyconfig.json") - inp_iter = iter(inputsb) - monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) - _run_tests(inputsa, expected) - confile = Path().home() / "diffpyconfig.json" - assert confile.exists() is False - +# @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) +# def test_get_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): +# _setup_dirs(monkeypatch, user_filesystem) +# os.remove(Path().home() / "diffpyconfig.json") +# inp_iter = iter(inputsb) +# monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) +# _run_tests(inputsa, expected) +# confile = Path().home() / "diffpyconfig.json" +# assert confile.is_file() +# +# +# @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs) +# def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): +# _setup_dirs(monkeypatch, user_filesystem) +# os.remove(Path().home() / "diffpyconfig.json") +# inp_iter = iter(inputsb) +# monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) +# _run_tests(inputsa, expected) +# confile = Path().home() / "diffpyconfig.json" +# assert confile.exists() is False +# params_package_info = [ (["diffpy.utils", None], {"package_info": {"diffpy.utils": "3.3.0"}}), From c2ac17305026af44d1a1cc1b0d312a324d4f2713 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 21 Dec 2024 18:49:28 -0500 Subject: [PATCH 259/445] docs --- doc/source/examples/tools_example.rst | 106 ++++++++++++-------------- src/diffpy/utils/tools.py | 36 ++++++--- 2 files changed, 74 insertions(+), 68 deletions(-) diff --git a/doc/source/examples/tools_example.rst b/doc/source/examples/tools_example.rst index a56f76d3..60746e6f 100644 --- a/doc/source/examples/tools_example.rst +++ b/doc/source/examples/tools_example.rst @@ -13,11 +13,11 @@ Automatically Capture User Info One task we would like to do is to capture and propagate useful metadata that describes the diffraction data. Some is essential such as wavelength and radiation type. Other metadata is useful such as information about the sample, co-workers and so on. However, one of the most important bits of information is the name of the data owner. -For example, in ``DiffractionObjects`` this is stored in the ``metadata`` dictionary as ``username``, ``user_email``, -and ``user_orcid``. +For example, in ``DiffractionObjects`` this is stored in the ``metadata`` dictionary as ``owner_name``, ``owner_email``, +and ``owner_orcid``. To reduce experimenter overhead when collecting this information, we have developed an infrastructure that helps -to capture this information automatically when you are using `DiffractionObjects` and other diffpy tools. +to capture this information automatically when you are using ``DiffractionObjects`` and other diffpy tools. You may also reuse this infrastructure for your own projects using tools in this tutorial. This example will demonstrate how ``diffpy.utils`` allows us to conveniently load and manage user and package information. @@ -28,8 +28,9 @@ Load user info into your program To use this functionality in your own code make use of the ``get_user_info`` function in ``diffpy.utils.tools`` which will search for information about the user, parse it, and return -it in a dictionary object e.g. if the user is "Jane Doe" with email "janedoe@gmail.com" and the -function can find the information, if you type this +it in a dictionary object e.g. if the user is "Jane Doe" with email "janedoe@gmail.com" and ORCID +"0000-0000-0000-0000", and if the +function can find the information (more on this below), if you type this .. code-block:: python @@ -40,7 +41,7 @@ The function will return .. code-block:: python - {"email": "janedoe@email.com", "username": "Jane Doe"} + {"owner_email": "janedoe@email.com", "owner_name": "Jane Doe", "owner_orcid": "0000-0000-0000-0000"} Where does ``get_user_info()`` get the user information from? @@ -48,8 +49,9 @@ Where does ``get_user_info()`` get the user information from? The function will first attempt to load the information from configuration files with the name ``diffpyconfig.json`` on your hard-drive. -It looks first for the file in the current working directory. If it cannot find it there it will look -user's home, i.e., login, directory. To find this directory, open a terminal and a unix or mac system type :: +It looks for files in the current working directory and in the computer-user's home (i.e., login) directory. +For example, it might be in C:/Users/yourname`` or something like that, but to find this directory, open +a terminal and a unix or mac system type :: cd ~ pwd @@ -58,67 +60,55 @@ Or type ``Echo $HOME``. On a Windows computer :: echo %USERPROFILE%" +It is also possible to override the values in the config files at run-time by passing values directly into the +function according to ``get_user_info``, for example, +``get_user_info(owner_name="Janet Doe", owner_email="janetdoe@email.com", owner_orcid="1111-1111-1111-1111")``. +The information to pass into ``get_user_info`` could be entered by a user through a command-line interface +or into a gui. + What if no config files exist yet? ----------------------------------- -If no configuration files can be found, the function attempts to create one in the user's home -directory. The function will pause execution and ask for a user-response to enter the information. -It will then write the config file in the user's home directory. - -In this way, the next, and subsequent times the program is run, it will no longer have to prompt the user -as it will successfully find the new config file. - -Getting user data with no config files and with no interruption of execution ----------------------------------------------------------------------------- +If no configuration files can be found, they can be created using a text editor, or by using a diffpy tool +called ``check_and_build_global_config()`` which, if no global config file can be found, prompts the user for the +information then writes the config file in the user's home directory. -If you would like get run ``get_user_data()`` but without execution interruption even if it cannot find -an input file, type +When building an application where you want to capture data-owner information, we recommend you execute +``check_and_build_global_config()`` first followed by ``get_user_info`` in your app workflow. E.g., .. code-block:: python - - user_data = get_user_data(skip_config_creation=True) - -Passing user information directly to ``get_user_data()`` --------------------------------------------------------- - -It can be passed user information which fully or partially overrides looking in config files -For example, in this way it would be possible to pass in information -that is entered through a gui or command line interface. E.g., - - .. code-block:: python - - new_user_info = get_user_info({"username": "new_username", "email": "new@example.com"}) - -This returns ``{"username": "new_username", "email": "new@example.com"}`` (and so, effectively, does nothing) -However, You can update only the username or email individually, for example - -.. code-block:: python - - new_user_info = get_user_info({"username": new_username}) - -will return ``{"username": "new_username", "email": "janedoe@gmail.com"}`` -if it found ``janedoe@gmail.com`` as the email in the config file. -Similarly, you can update only the email in the returned dictionary, - -.. code-block:: python - - new_user_info = get_user_info({"email": new@email.com}) - -which will return ``{"username": "Jane Doe", "email": "new@email.com"}`` -if it found ``Jane Doe`` as the user in the config file. - -I entered the wrong information in my config file so it always loads incorrect information ------------------------------------------------------------------------------------------- - -You can use of the above methods to temporarily override the incorrect information in your -global config file. However, it is easy to fix this simply by editing that file using a text + from diffpy.utils.tools import check_and_build_global_config, get_user_info + from datetime import datetime + import json + + def my_cool_data_enhancer_app_main(data, filepath): + check_and_build_global_config() + metadata_enhanced_data = get_user_info() + metadata_enhanced_data.update({"creation_time": datetime.now(), + "data": data}) + with open(filepath, "w") as f: + json.dump(metadata_enhanced_data, f) + +``check_and_build_global_config()`` only +interrupts execution if it can't find a valid config file, and so if the user enters valid information +it will only run once. However, if you want to bypass this behavior, +``check_and_build_global_config()`` takes an optional boolean ``skip_config_creation`` parameter that +could be set to ``True`` at runtime to override the config creation. + +I entered the wrong information in my config file so it always loads incorrect information, how do I fix that? +-------------------------------------------------------------------------------------------------------------- + +It is easy to fix this simply by deleting the global and/or local config files, which will allow +you to re-enter the information during the ``check_and_build_global_config()`` initialization +workflow. You can also simply editi the ``diffpyconfig.json`` file directly using a text editor. Locate the file ``diffpyconfig.json``, in your home directory and open it in an editor :: { - "username": "John Doe", - "email": "john.doe@example.com" + "owner_name": "John Doe", + "owner_email": "john.doe@example.com" + "owner_orcid": "0000-0000-4321-1234" } Then you can edit the username and email as needed, make sure to save your edits. diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 234aedb2..91505e6f 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -92,22 +92,38 @@ def _create_global_config(args): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): """ - Get username, email and orcid configuration. - - First attempts to load config file from global and local paths. - If neither exists, creates a global config file. - It prioritizes values from args, then local, then global. - Removes invalid global config file if creation is needed, replacing it with empty username and email. + Get name, email and orcid of the owner/user from various sources and return it as a metadata dictionary + + The function looks for the information in json format configuration files with the name 'diffpyconfig.json'. + These can be in the user's home directory and in the current working directory. The information in the + config files are combined, with the local config overriding the home-directory one. Values for + owner_name, owner_email, and owner_orcid may be passed in to the function and these override the values + in the config files. + + A template for the config file is below. Create a text file called 'diffpyconfig.json' in your home directory + and copy-paste the template into it, editing it with your real information. + { + "owner_name": ">", + "owner_email": ">@email.com", + "owner_orcid": ">" + } + You may also store any other gloabl-level information that you would like associated with your + diffraction data in this file Parameters ---------- - args argparse.Namespace - The arguments from the parser, default is None. + owner_name: string, optional, default is the value stored in the global or local config file. + The name of the user who will show as owner in the metadata that is stored with the data + owner_email: string, optional, default is the value stored in the global or local config file. + The email of the user/owner + owner_name: string, optional, default is the value stored in the global or local config file. + The ORCID id of the user/owner Returns ------- - dict or None: - The dictionary containing username and email with corresponding values. + dict: + The dictionary containing username, email and orcid of the user/owner, and any other information + stored in the global or local config files. """ runtime_info = {"owner_name": owner_name, "owner_email": owner_email, "owner_orcid": owner_orcid} From f96728a922b5169a61fdc12a09affce4ce86ed1e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 20:25:05 -0500 Subject: [PATCH 260/445] Add class docstring for diffraction object and news --- news/class-docstring.rst | 23 +++++ src/diffpy/utils/diffraction_objects.py | 113 +++++++++++++++--------- 2 files changed, 96 insertions(+), 40 deletions(-) create mode 100644 news/class-docstring.rst diff --git a/news/class-docstring.rst b/news/class-docstring.rst new file mode 100644 index 00000000..a31cc6d6 --- /dev/null +++ b/news/class-docstring.rst @@ -0,0 +1,23 @@ +**Added:** + +* class docstring for `DiffractionObject` + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index d72a2889..602434f9 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -36,50 +36,38 @@ def _setter_wmsg(attribute): class DiffractionObject: """ - Initialize a DiffractionObject instance. + A class to represent diffraction data for various scientific experiments involving scattering + techniques such as X-ray, neutron, and electron diffraction. This object can manage diffraction + data including transformations between different scattering quantities like q (scattering vector), + 2θ (two-theta angle), and d (interplanar spacing), and perform various operations like scaling, addition, + and subtraction of diffraction patterns. - Parameters + Attributes ---------- - 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 + all_arrays : ndarray + The array containing the quantity of q, tth, d values. + input_xtype : str + The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES} + id : uuid + The unique identifier for the diffraction object. + scat_quantity : str The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". - name : str, optional + wavelength : float + The wavelength of the incoming beam, specified in angstroms (Å). Default is none. + name: str 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 = { - ... "sample": "rock salt from the beach", - ... "composition": "NaCl", - ... "temperature": "300 K,", - ... "experimenters": "Phill, Sally" - ... } - >>> do = DiffractionObject( - ... xarray=x, - ... yarray=y, - ... xtype="q", - ... wavelength=1.54, - ... scat_quantity="x-ray", - ... name="beach_rock_salt_1", - ... metadata=metadata - ... ) - >>> print(do.metadata) + qmin : float + The minimum q value. + qmax : float + The maximum q value. + tthmin : float + The minimum two-theta value. + tthmax : float + The maximum two-theta value. + dmin : float + The minimum d-spacing value. + dmax : float + The maximum d-spacing value. """ def __init__( @@ -92,6 +80,51 @@ def __init__( name="", metadata={}, ): + """ + Initialize a DiffractionObject instance. + + Parameters + ---------- + xarray : ndarray + The independent variable array containing "q", "tth", or "d" values. + yarray : ndarray + 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 = { + ... "sample": "rock salt from the beach", + ... "composition": "NaCl", + ... "temperature": "300 K,", + ... "experimenters": "Phill, Sally" + ... } + >>> do = DiffractionObject( + ... xarray=x, + ... yarray=y, + ... xtype="q", + ... wavelength=1.54, + ... scat_quantity="x-ray", + ... name="beach_rock_salt_1", + ... metadata=metadata + ... ) + >>> print(do.metadata) + """ self._id = uuid.uuid4() self._input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) From 2dfb953d13d926f3f161a48a6ea32627c4f11bca Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 21:22:18 -0500 Subject: [PATCH 261/445] Continue refactor test functions, replace UC to C --- tests/test_diffraction_objects.py | 147 +++++++++++++++--------------- tests/test_transforms.py | 68 +++++++------- 2 files changed, 112 insertions(+), 103 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 4576b257..d1d66ee6 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -186,7 +186,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test that scale_to() scales to the correct values - # Case 1: same x-array and y-array, check offset + # C1: same x-array and y-array, check offset ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -208,7 +208,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - # Case 2: same length x-arrays with exact x-value match + # C2: same length x-arrays with exact x-value match ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -230,7 +230,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - # Case 3: same length x-arrays with approximate x-value match + # C3: same length x-arrays with approximate x-value match ( { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), @@ -252,7 +252,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - # Case 4: different x-array lengths with approximate x-value match + # C4: different x-array lengths with approximate x-value match ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -272,7 +272,7 @@ def test_init_invalid_xtype(): "d": None, "offset": 0, }, - # Case 5: Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) + # C5: Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), ], @@ -287,76 +287,81 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) -params_scale_to_bad = [ - # UC1: user did not specify anything - ( - { - "xarray": np.array([0.1, 0.2, 0.3]), - "yarray": np.array([1, 2, 3]), - "xtype": "q", - "wavelength": 2 * np.pi, - "target_xarray": np.array([0.05, 0.1, 0.2, 0.3]), - "target_yarray": np.array([5, 10, 20, 30]), - "target_xtype": "q", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": None, - "d": None, - "offset": 0, - } - ), - # UC2: user specified more than one of q, tth, and d - ( - { - "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), - "xtype": "tth", - "wavelength": 2 * np.pi, - "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "target_xtype": "tth", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": 60, - "d": 10, - "offset": 0, - } - ), -] - - -@pytest.mark.parametrize("inputs", params_scale_to_bad) -def test_scale_to_bad(inputs): - orig_diff_object = DiffractionObject( - xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] - ) - target_diff_object = DiffractionObject( - xarray=inputs["target_xarray"], - yarray=inputs["target_yarray"], - xtype=inputs["target_xtype"], - wavelength=inputs["target_wavelength"], - ) +@pytest.mark.parametrize( + "org_do_args, target_do_args, scale_inputs", + [ + # UC1: user did not specify anything + ( + { + "xarray": np.array([0.1, 0.2, 0.3]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([0.05, 0.1, 0.2, 0.3]), + "yarray": np.array([5, 10, 20, 30]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": None, + "d": None, + "offset": 0, + }, + ), + # UC2: user specified more than one of q, tth, and d + ( + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": 10, + "offset": 0, + }, + ), + ], +) +def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): + original_do = DiffractionObject(**org_do_args) + target_do = DiffractionObject(**target_do_args) with pytest.raises( ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ): - orig_diff_object.scale_to( - target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] + original_do.scale_to( + target_do, + q=scale_inputs["q"], + tth=scale_inputs["tth"], + d=scale_inputs["d"], + offset=scale_inputs["offset"], ) -params_index = [ - # UC1: exact match - (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # UC2: target value lies in the array, returns the (first) closest index - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # UC3: target value out of the range, returns the closest index - (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), -] - - -@pytest.mark.parametrize("wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", params_index) +@pytest.mark.parametrize( + "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", + [ + # UC1: exact match + (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), + # UC2: target value lies in the array, returns the (first) closest index + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), + # UC3: target value out of the range, returns the closest index + (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), + ], +) def test_get_array_index(wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index): do = DiffractionObject(wavelength=wavelength, xarray=xarray, yarray=yarray, xtype=xtype_1) actual_index = do.get_array_index(value=value, xtype=xtype_2) @@ -482,11 +487,11 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - ( # Case 1: no arguments provided + ( # C1: no arguments provided {}, "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", ), - ( # Case 2: only xarray and yarray provided + ( # C2: only xarray and yarray provided {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, "missing 1 required positional argument: 'xtype'", ), diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 65f437ac..26887607 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -9,20 +9,23 @@ @pytest.mark.parametrize( "wavelength, q, expected_tth", [ - # Case 1: Allow empty arrays for q - # 1. Empty q values, no wavelength, return empty arrays + # Test conversion of q to tth with valid values + # C1. Empty q values + # 1. No wavelength, expect empty arrays (None, np.empty((0)), np.empty((0))), - # 2. Empty q values, wavelength specified, return empty arrays + # 2. 4π wavelength, expect empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # Case 2: Allow wavelength to be missing. - # Valid q values, no wavelength, return index array + + # C2. Non-empty q values + # 2. No wavelength, expect value tth values ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # Case 3: Correctly specified q and wavelength - # Expected tth values are 2*arcsin(q) in degrees + + # C3: Both valid q and wavelength provided + # 1. Expected tth values are 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) @@ -38,14 +41,15 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, q, expected_error_type", - [ - # UC1: user specified invalid q values that result in tth > 180 degrees + [ + # Test error messages in q to tth conversion with invalid Two theta values. + # C1: Invalid q values that result in tth > 180 degrees ( 4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), ValueError, ), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees + # C2: Wrong wavelength that results in tth > 180 degrees ( 100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), @@ -62,17 +66,17 @@ def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wave @pytest.mark.parametrize( "wavelength, tth, expected_q", [ - # UC0: user specified empty tth values (without wavelength) + # C0: Empty tth values (without wavelength) (None, np.array([]), np.array([])), - # UC1: user specified empty tth values (with wavelength) + # C1: Empty tth values (with wavelength) (4 * np.pi, np.array([]), np.array([])), - # UC2: user specified valid tth values between 0-180 degrees (without wavelength) + # C2: valid tth values between 0-180 degrees (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), - # UC3: user specified valid tth values between 0-180 degrees (with wavelength) + # C3: valid tth values between 0-180 degrees (with wavelength) # expected q values are sin15, sin30, sin45, sin60, sin90 ( 4 * np.pi, @@ -94,14 +98,14 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) + # C1: invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) + # C2: invalid tth value of > 180 degrees with wavelength, expect two theta ValueError ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -119,9 +123,9 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): "q, expected_d, warning_expected", [ # Test conversion of q to d with valid values - # Case 1: empty q values, expect empty d values + # C1: empty q values, expect empty d values (np.array([]), np.array([]), False), - # Case 2: + # C2: # 1. valid q values, expect d values without warning ( np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), @@ -148,9 +152,9 @@ def test_q_to_d(q, expected_d, warning_expected): @pytest.mark.parametrize( "d, expected_q, zero_divide_error_expected", [ - # UC1: User specified empty d values + # C1: User specified empty d values (np.array([]), np.array([]), False), - # UC2: User specified valid d values + # C2: User specified valid d values ( np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), @@ -171,13 +175,13 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): "wavelength, tth, expected_d, divide_by_zero_warning_expected", [ # Test conversion of q to d with valid values - # Case 1: empty tth values, no, expect empty d values + # C1: empty tth values, no, expect empty d values (None, np.array([]), np.array([]), False), - # Case 2: empty tth values, wavelength provided, expect empty d values + # C2: empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), - # Case 3: User specified valid tth values between 0-180 degrees (without wavelength) + # C3: User specified valid tth values between 0-180 degrees (without wavelength) (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), - # Case 4: User specified valid tth values between 0-180 degrees (with wavelength) + # C4: User specified valid tth values between 0-180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), @@ -201,14 +205,14 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) + # C1: user specified an invalid tth value of > 180 degrees (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) + # C2: user specified an invalid tth value of > 180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -225,13 +229,13 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m @pytest.mark.parametrize( "wavelength, d, expected_tth, divide_by_zero_warning_expected", [ - # UC1: Empty d values, no wavelength, return empty arrays + # C1: Empty d values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0)), False), - # UC2: Empty d values, wavelength specified, return empty arrays + # C2: Empty d values, wavelength specified, return empty arrays (4 * np.pi, np.empty((0)), np.empty(0), False), - # UC3: User specified valid d values, no wavelength, return empty arrays + # C3: User specified valid d values, no wavelength, return empty arrays (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), - # UC4: User specified valid d values (with wavelength) + # C4: User specified valid d values (with wavelength) ( 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), @@ -257,9 +261,9 @@ def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, d, expected_error_type", [ - # UC1: user specified invalid d values that result in tth > 180 degrees + # C1: user specified invalid d values that result in tth > 180 degrees (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees + # C2: user specified a wrong wavelength that result in tth > 180 degrees (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) From 65d9d61066f69ada1bcb0cd1c083986b2d730d3d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 21:23:21 -0500 Subject: [PATCH 262/445] Add news containing no news --- news/pytest-test-refactor.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/pytest-test-refactor.rst diff --git a/news/pytest-test-refactor.rst b/news/pytest-test-refactor.rst new file mode 100644 index 00000000..08d3819b --- /dev/null +++ b/news/pytest-test-refactor.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: refactoring tests that have been mentioned in previous news + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From edd44ee29a01ac50276925015b065e0a89ce8fb2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 02:24:13 +0000 Subject: [PATCH 263/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 26887607..cdad5bf9 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -15,7 +15,6 @@ (None, np.empty((0)), np.empty((0))), # 2. 4π wavelength, expect empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # C2. Non-empty q values # 2. No wavelength, expect value tth values ( @@ -23,7 +22,6 @@ np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # C3: Both valid q and wavelength provided # 1. Expected tth values are 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), @@ -41,7 +39,7 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, q, expected_error_type", - [ + [ # Test error messages in q to tth conversion with invalid Two theta values. # C1: Invalid q values that result in tth > 180 degrees ( From 47750a006368b0ae57a87735c4024a51df164a1a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 21:33:37 -0500 Subject: [PATCH 264/445] Fix minor typo from C. to C: --- tests/test_transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index cdad5bf9..1c91e182 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -10,12 +10,12 @@ "wavelength, q, expected_tth", [ # Test conversion of q to tth with valid values - # C1. Empty q values + # C1: Empty q values # 1. No wavelength, expect empty arrays (None, np.empty((0)), np.empty((0))), # 2. 4π wavelength, expect empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # C2. Non-empty q values + # C2: Non-empty q values # 2. No wavelength, expect value tth values ( None, From b61eaca80a06c771c33eca282a632eedbc0f5113 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 21:35:56 -0500 Subject: [PATCH 265/445] Start comment with a cap letter --- tests/test_diffraction_objects.py | 26 +++++++++++++------------- tests/test_transforms.py | 22 +++++++++++----------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index d1d66ee6..63f349eb 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -186,7 +186,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test that scale_to() scales to the correct values - # C1: same x-array and y-array, check offset + # C1: Same x-array and y-array, check offset ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -208,7 +208,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - # C2: same length x-arrays with exact x-value match + # C2: Same length x-arrays with exact x-value match ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -230,7 +230,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - # C3: same length x-arrays with approximate x-value match + # C3: Same length x-arrays with approximate x-value match ( { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), @@ -252,7 +252,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - # C4: different x-array lengths with approximate x-value match + # C4: Different x-array lengths with approximate x-value match ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -290,7 +290,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs", [ - # UC1: user did not specify anything + # UC1: User did not specify anything ( { "xarray": np.array([0.1, 0.2, 0.3]), @@ -311,7 +311,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # UC2: user specified more than one of q, tth, and d + # UC2: User specified more than one of q, tth, and d ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -352,12 +352,12 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): @pytest.mark.parametrize( "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", [ - # UC1: exact match + # UC1: Exact match (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # UC2: target value lies in the array, returns the (first) closest index + # UC2: Target value lies in the array, returns the (first) closest index (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # UC3: target value out of the range, returns the closest index + # UC3: Target value out of the range, returns the closest index (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), ], @@ -411,7 +411,7 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( "do_init_args, expected_do_dict, divide_by_zero_warning_expected", [ - ( # instantiate just array attributes + ( # Instantiate just array attributes { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -440,7 +440,7 @@ def test_dump(tmp_path, mocker): }, True, ), - ( # instantiate just array attributes + ( # Instantiate just array attributes { "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -487,11 +487,11 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - ( # C1: no arguments provided + ( # C1: No arguments provided {}, "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", ), - ( # C2: only xarray and yarray provided + ( # C2: Only xarray and yarray provided {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, "missing 1 required positional argument: 'xtype'", ), diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 1c91e182..036e2361 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -96,14 +96,14 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError + # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # C2: invalid tth value of > 180 degrees with wavelength, expect two theta ValueError + # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -121,16 +121,16 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): "q, expected_d, warning_expected", [ # Test conversion of q to d with valid values - # C1: empty q values, expect empty d values + # C1: Empty q values, expect empty d values (np.array([]), np.array([]), False), # C2: - # 1. valid q values, expect d values without warning + # 1. Valid q values, expect d values without warning ( np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), False, ), - # 2. valid q values containing 0, expect d values with divide by zero warning + # 2. Valid q values containing 0, expect d values with divide by zero warning ( np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), @@ -173,9 +173,9 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): "wavelength, tth, expected_d, divide_by_zero_warning_expected", [ # Test conversion of q to d with valid values - # C1: empty tth values, no, expect empty d values + # C1: Empty tth values, no, expect empty d values (None, np.array([]), np.array([]), False), - # C2: empty tth values, wavelength provided, expect empty d values + # C2: Empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), # C3: User specified valid tth values between 0-180 degrees (without wavelength) (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), @@ -203,14 +203,14 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: user specified an invalid tth value of > 180 degrees (without wavelength) + # C1: User specified an invalid tth value of > 180 degrees (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # C2: user specified an invalid tth value of > 180 degrees (with wavelength) + # C2: User specified an invalid tth value of > 180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -259,9 +259,9 @@ def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, d, expected_error_type", [ - # C1: user specified invalid d values that result in tth > 180 degrees + # C1: User specified invalid d values that result in tth > 180 degrees (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), - # C2: user specified a wrong wavelength that result in tth > 180 degrees + # C2: User specified a wrong wavelength that result in tth > 180 degrees (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) From 65700613f8ec1bd23b2a5885012f0731935d97da Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 21:44:31 -0500 Subject: [PATCH 266/445] Refactor test_q_to_tth with full sentence --- tests/test_transforms.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 036e2361..f069437a 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -10,20 +10,20 @@ "wavelength, q, expected_tth", [ # Test conversion of q to tth with valid values - # C1: Empty q values - # 1. No wavelength, expect empty arrays - (None, np.empty((0)), np.empty((0))), - # 2. 4π wavelength, expect empty arrays + # Case 1: Empty q values are provided (1) with and (2) without wavelength + # 1. Expect empty arrays (4 * np.pi, np.empty((0)), np.empty(0)), - # C2: Non-empty q values - # 2. No wavelength, expect value tth values + # 2. Expect empty arrays with wavelength warning message + (None, np.empty((0)), np.empty((0))), + # Case 2: Non-empty q values are provided without wavelength + # 1. Expect valid tth values in degrees. ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # C3: Both valid q and wavelength provided - # 1. Expected tth values are 2*arcsin(q) in degrees + # Case 3: Both valid q and wavelength are provided. + # 1. Expect tth values of 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) From df0f5390d0ee088d52b2c4a4d89e8cb6820ea3ea Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 22:08:29 -0500 Subject: [PATCH 267/445] Refine test comment using Expect --- tests/test_transforms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index f069437a..96c9a64c 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -10,10 +10,10 @@ "wavelength, q, expected_tth", [ # Test conversion of q to tth with valid values - # Case 1: Empty q values are provided (1) with and (2) without wavelength - # 1. Expect empty arrays + # Case 1: Allow empty arrays for q + # 1. Expect empty arrays when wavelength is provided (4 * np.pi, np.empty((0)), np.empty(0)), - # 2. Expect empty arrays with wavelength warning message + # 2. Expect empty arrays and wavelength warning when no wavelength is provided (None, np.empty((0)), np.empty((0))), # Case 2: Non-empty q values are provided without wavelength # 1. Expect valid tth values in degrees. From 9188171548821e5e1ad04bc0d9216c1face184f6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 22:27:44 -0500 Subject: [PATCH 268/445] Another round of test comment fix --- tests/test_transforms.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 96c9a64c..f0e7a14b 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -10,20 +10,19 @@ "wavelength, q, expected_tth", [ # Test conversion of q to tth with valid values - # Case 1: Allow empty arrays for q - # 1. Expect empty arrays when wavelength is provided + # C1: Allow empty array q to compute tth with or without wavelength + # 1. Wavelength provided, expect empty array of tth (4 * np.pi, np.empty((0)), np.empty(0)), - # 2. Expect empty arrays and wavelength warning when no wavelength is provided + # 2. No wavelength provided, expected empty tth and wavelength UserWarning (None, np.empty((0)), np.empty((0))), - # Case 2: Non-empty q values are provided without wavelength - # 1. Expect valid tth values in degrees. + # C2: Use non-empty q values to compute tth with or without + # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # Case 3: Both valid q and wavelength are provided. - # 1. Expect tth values of 2*arcsin(q) in degrees + # 2. Wavelength provided, expect tth values of 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) From 704ac0fe38505794e0f2d979345c9408568c2a3d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 03:27:55 +0000 Subject: [PATCH 269/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index f0e7a14b..3acf3f3a 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -15,7 +15,7 @@ (4 * np.pi, np.empty((0)), np.empty(0)), # 2. No wavelength provided, expected empty tth and wavelength UserWarning (None, np.empty((0)), np.empty((0))), - # C2: Use non-empty q values to compute tth with or without + # C2: Use non-empty q values to compute tth with or without # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning ( None, From d5e2cfd18123ce0ef3cf3d0c187bea275793b519 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 22:36:50 -0500 Subject: [PATCH 270/445] Continue to refactor test_tth_to_q_bad --- tests/test_transforms.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index f0e7a14b..6d24baa9 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -9,13 +9,13 @@ @pytest.mark.parametrize( "wavelength, q, expected_tth", [ - # Test conversion of q to tth with valid values + # Test conversion of q to tth with q and wavelength # C1: Allow empty array q to compute tth with or without wavelength # 1. Wavelength provided, expect empty array of tth (4 * np.pi, np.empty((0)), np.empty(0)), - # 2. No wavelength provided, expected empty tth and wavelength UserWarning + # 2. No wavelength provided, expected empty array of tth and wavelength UserWarning (None, np.empty((0)), np.empty((0))), - # C2: Use non-empty q values to compute tth with or without + # C2: Use non-empty q values to compute tth with or without # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning ( None, @@ -39,14 +39,14 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, q, expected_error_type", [ - # Test error messages in q to tth conversion with invalid Two theta values. - # C1: Invalid q values that result in tth > 180 degrees + # Test ValeuError in q to tth conversion with invalid two-theta values. + # C1: Invalid q values that result in tth > 180 degrees, expect ValueError ( 4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), ValueError, ), - # C2: Wrong wavelength that results in tth > 180 degrees + # C2: Wrong wavelength that results in tth > 180 degrees, expect ValueError ( 100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), @@ -63,18 +63,20 @@ def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wave @pytest.mark.parametrize( "wavelength, tth, expected_q", [ - # C0: Empty tth values (without wavelength) + # Test conversion of q to tth with q and wavelength + # C1: Allow empty tth values to compute 1, with or without wavelength + # 1. Wavelength provided, expect empty array of q (None, np.array([]), np.array([])), - # C1: Empty tth values (with wavelength) + # 2. No wavelength provided, expected empty array of q and wavelength UserWarning (4 * np.pi, np.array([]), np.array([])), - # C2: valid tth values between 0-180 degrees (without wavelength) + # C2: Use non-empty tth values between 0-180 degrees to compute q, with or without wavelength + # 1. No wavelength provided, expect valid q values between 0-1 ( None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), - # C3: valid tth values between 0-180 degrees (with wavelength) - # expected q values are sin15, sin30, sin45, sin60, sin90 + # 2. Wavelength provided, expect expected q values are sin15, sin30, sin45, sin60, sin90 ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), @@ -95,14 +97,15 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError + # C1: Invalid tth value of > 180 degrees provided, with or without wavelength + # 1. No wavelength provided, expect two theta ValueError ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError + # 2. Wavelength provided, expect two theta ValueError ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), From 17c87f0ca35029a242e3deb0b0dddf44021ec470 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 21 Dec 2024 22:41:00 -0500 Subject: [PATCH 271/445] Add warning to one of the comments --- tests/test_transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 6d24baa9..2a40805d 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -15,7 +15,7 @@ (4 * np.pi, np.empty((0)), np.empty(0)), # 2. No wavelength provided, expected empty array of tth and wavelength UserWarning (None, np.empty((0)), np.empty((0))), - # C2: Use non-empty q values to compute tth with or without + # C2: Use non-empty q values to compute tth with or without wavelength # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning ( None, From f064217187ece84eb10dd57e130112cf3ea19cef Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 22 Dec 2024 07:13:13 -0500 Subject: [PATCH 272/445] initial commit of tests for the config updater. I need to get the updated user_filesystem fixture so merging main next --- news/configupdate.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/tools.py | 5 ++++- tests/test_tools.py | 32 +++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 news/configupdate.rst diff --git a/news/configupdate.rst b/news/configupdate.rst new file mode 100644 index 00000000..0bc3ffa9 --- /dev/null +++ b/news/configupdate.rst @@ -0,0 +1,23 @@ +**Added:** + +* no news added: covered in the news from the get_user_info work + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3fc10031..98db017e 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -130,10 +130,13 @@ def get_user_info(args=None): if config_bool is False: os.remove(Path().home() / "diffpyconfig.json") config = {"username": "", "email": ""} - return config +def check_and_build_global_config(): + return + + def get_package_info(package_names, metadata=None): """ Fetches package version and updates it into (given) metadata. diff --git a/tests/test_tools.py b/tests/test_tools.py index 0a42332f..117a8565 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -5,7 +5,7 @@ import pytest -from diffpy.utils.tools import get_package_info, get_user_info +from diffpy.utils.tools import check_and_build_global_config, get_package_info, get_user_info def _setup_dirs(monkeypatch, user_filesystem): @@ -123,6 +123,36 @@ def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, exp assert confile.exists() is False +@pytest.mark.parametrize( + "test_inputs,expected", + [ # Check check_and_build_global_config() builds correct config when config is found missing + ( # C1: user inputs valid name, email and orcid + {"user_inputs": ["input_name", "input@email.com", "input_orcid"]}, + {"owner_email": "input@email.com", "owner_orcid": "input_orcid", "owner_name": "input_name"}, + ), + # ( # C2: empty strings passed in, expect uname, email, orcid from home_config + # {"owner_name": "", "owner_email": "", "owner_orcid": ""}, + # {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, + # ), + ], +) +def test_check_and_build_global_config(test_inputs, expected, user_filesystem, mocker): + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] + # is tmp_dir/cwd_dir + mocker.patch.object(Path, "home", return_value=user_filesystem[0]) + os.chdir(user_filesystem[1]) + # remove the config file from home that came with user_filesystem + old_confile = user_filesystem[0] / "diffpyconfig.json" + os.remove(old_confile) + check_and_build_global_config() + inp_iter = iter(test_inputs["user_inputs"]) + mocker.patch("builtins.input", lambda _: next(inp_iter)) + with open(old_confile, "r") as f: + actual = json.load(f) + print(actual) + assert actual == expected + + params_package_info = [ (["diffpy.utils", None], {"package_info": {"diffpy.utils": "3.3.0"}}), (["package1", None], {"package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), From ad05e37e7c0c9707d6270ff6c1c77734aee913eb Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 22 Dec 2024 09:18:48 -0500 Subject: [PATCH 273/445] check_and_build_global_config added wih tests --- src/diffpy/utils/tools.py | 70 +++++++++++++++++++++++---------------- tests/test_tools.py | 66 ++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 62 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 1fd0ed70..59c5d31d 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -74,22 +74,6 @@ def _sorted_merge(*dicts): return merged -def _create_global_config(args): - username = input( - f"Please enter the name you would want future work to be credited to " f"[{args.get('username', '')}]: " - ).strip() or args.get("username", "") - email = input(f"Please enter the your email " f"[{args.get('email', '')}]: ").strip() or args.get("email", "") - return_bool = False if username is None or email is None else True - with open(Path().home() / "diffpyconfig.json", "w") as f: - f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) - print( - f"You can manually edit the config file at {Path().home() / 'diffpyconfig.json'} using any text editor.\n" - f"Or you can update the config file by passing new values to get_user_info(), " - f"see examples here: https://www.diffpy.org/diffpy.utils/examples/toolsexample.html" - ) - return return_bool - - def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): """ Get name, email and orcid of the owner/user from various sources and return it as a metadata dictionary @@ -107,7 +91,7 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): "owner_email": ">@email.com", "owner_orcid": ">" } - You may also store any other gloabl-level information that you would like associated with your + You may also store any other global-level information that you would like associated with your diffraction data in this file Parameters @@ -132,24 +116,52 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): del runtime_info[key] global_config = load_config(Path().home() / "diffpyconfig.json") local_config = load_config(Path().cwd() / "diffpyconfig.json") - # if global_config is None and local_config is None: - # print( - # "No global configuration file was found containing " - # "information about the user to associate with the data.\n" - # "By following the prompts below you can add your name and email to this file on the current " - # "computer and your name will be automatically associated with subsequent diffpy data by default.\n" - # "This is not recommended on a shared or public computer. " - # "You will only have to do that once.\n" - # "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" - # ) user_info = global_config user_info.update(local_config) user_info.update(runtime_info) return user_info -def check_and_build_global_config(): - return +def _get_value(mystring): + return mystring.strip() + + +def check_and_build_global_config(skip_config_creation=False): + config_path = Path().home() / "diffpyconfig.json" + if skip_config_creation: + return + if config_path.is_file(): + return + intro_text = ( + "No global configuration file was found containing information about the user to " + "associate with the data.\n By following the prompts below you can add your name " + "and email to this file on the current " + "computer and your name will be automatically associated with subsequent diffpy data by default.\n" + "This is not recommended on a shared or public computer. " + "You will only have to do that once.\n" + "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" + ) + print(intro_text) + username = input("Please enter the name you would want future work to be credited to: ").strip() + email = input("Please enter your email: ").strip() + orcid = input("Please enter your orcid ID if you know it: ").strip() + config = {"owner_name": stringify(username), "owner_email": stringify(email), "owner_orcid": stringify(orcid)} + if email != "" or orcid != "" or username != "": + config["owner_orcid"] = stringify(orcid) + with open(config_path, "w") as f: + f.write(json.dumps(config)) + outro_text = ( + f"The config file at {Path().home() / 'diffpyconfig.json'} has been created. " + f"The values {config} were entered.\n" + f"These values will be inserted as metadata with your data in apps that use " + f"diffpy.get_user_info(). If you would like to update these values, either " + f"delete the config file and this workflow will rerun next time you run this " + f"program. Or you may open the config file in a text editor and manually edit the" + f"entries. For more information, see: " + f"https://diffpy.githu.io/diffpy.utils/examples/tools_example.html" + ) + print(outro_text) + return def get_package_info(package_names, metadata=None): diff --git a/tests/test_tools.py b/tests/test_tools.py index 3169f01f..88e81566 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -149,29 +149,6 @@ def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_files assert actual == expected -# @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_with_no_home_conf_file) -# def test_get_user_info_with_no_home_conf_file(monkeypatch, inputsa, inputsb, expected, user_filesystem): -# _setup_dirs(monkeypatch, user_filesystem) -# os.remove(Path().home() / "diffpyconfig.json") -# inp_iter = iter(inputsb) -# monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) -# _run_tests(inputsa, expected) -# confile = Path().home() / "diffpyconfig.json" -# assert confile.is_file() -# -# -# @pytest.mark.parametrize("inputsa, inputsb, expected", params_user_info_no_conf_file_no_inputs) -# def test_get_user_info_no_conf_file_no_inputs(monkeypatch, inputsa, inputsb, expected, user_filesystem): -# _setup_dirs(monkeypatch, user_filesystem) -# os.remove(Path().home() / "diffpyconfig.json") -# inp_iter = iter(inputsb) -# monkeypatch.setattr("builtins.input", lambda _: next(inp_iter)) -# _run_tests(inputsa, expected) -# confile = Path().home() / "diffpyconfig.json" -# assert confile.exists() is False -# - - @pytest.mark.parametrize( "test_inputs,expected", [ # Check check_and_build_global_config() builds correct config when config is found missing @@ -179,10 +156,11 @@ def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_files {"user_inputs": ["input_name", "input@email.com", "input_orcid"]}, {"owner_email": "input@email.com", "owner_orcid": "input_orcid", "owner_name": "input_name"}, ), - # ( # C2: empty strings passed in, expect uname, email, orcid from home_config - # {"owner_name": "", "owner_email": "", "owner_orcid": ""}, - # {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, - # ), + ({"user_inputs": ["", "", ""]}, None), # C2: empty strings passed in, expect no config file created + ( # C3: just username input, expect config file but with some empty values + {"user_inputs": ["input_name", "", ""]}, + {"owner_email": "", "owner_orcid": "", "owner_name": "input_name"}, + ), ], ) def test_check_and_build_global_config(test_inputs, expected, user_filesystem, mocker): @@ -190,18 +168,40 @@ def test_check_and_build_global_config(test_inputs, expected, user_filesystem, m # is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) + confile = user_filesystem[0] / "diffpyconfig.json" # remove the config file from home that came with user_filesystem - old_confile = user_filesystem[0] / "diffpyconfig.json" - os.remove(old_confile) + os.remove(confile) + mocker.patch("builtins.input", side_effect=test_inputs["user_inputs"]) check_and_build_global_config() - inp_iter = iter(test_inputs["user_inputs"]) - mocker.patch("builtins.input", lambda _: next(inp_iter)) - with open(old_confile, "r") as f: + try: + with open(confile, "r") as f: + actual = json.load(f) + except FileNotFoundError: + actual = None + assert actual == expected + + +def test_check_and_build_global_config_file_exists(user_filesystem, mocker): + mocker.patch.object(Path, "home", return_value=user_filesystem[0]) + os.chdir(user_filesystem[1]) + confile = user_filesystem[0] / "diffpyconfig.json" + expected = {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"} + check_and_build_global_config() + with open(confile, "r") as f: actual = json.load(f) - print(actual) assert actual == expected +def test_check_and_build_global_config_skipped(user_filesystem, mocker): + mocker.patch.object(Path, "home", return_value=user_filesystem[0]) + os.chdir(user_filesystem[1]) + confile = user_filesystem[0] / "diffpyconfig.json" + # remove the config file from home that came with user_filesystem + os.remove(confile) + check_and_build_global_config(skip_config_creation=True) + assert not confile.exists() + + params_package_info = [ (["diffpy.utils", None], {"package_info": {"diffpy.utils": "3.3.0"}}), (["package1", None], {"package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), From 552dd32f8b516577e6dfd3040a26b242c56c0eed Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 22 Dec 2024 09:25:15 -0500 Subject: [PATCH 274/445] delete unneeded _get_value() function and unneeded test parms --- src/diffpy/utils/tools.py | 4 --- tests/test_tools.py | 65 --------------------------------------- 2 files changed, 69 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 59c5d31d..27e49ff8 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -122,10 +122,6 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): return user_info -def _get_value(mystring): - return mystring.strip() - - def check_and_build_global_config(skip_config_creation=False): config_path = Path().home() / "diffpyconfig.json" if skip_config_creation: diff --git a/tests/test_tools.py b/tests/test_tools.py index 88e81566..d364feb9 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -7,71 +7,6 @@ from diffpy.utils.tools import check_and_build_global_config, get_package_info, get_user_info -# def _setup_dirs(monkeypatch, user_filesystem): -# home_dir, cwd_dir = user_filesystem.home_dir, user_filesystem.cwd_dir -# os.chdir(cwd_dir) -# return home_dir -# - - -def _run_tests(inputs, expected): - args = {"username": inputs[0], "email": inputs[1]} - expected_username, expected_email = expected - config = get_user_info(args) - assert config.get("username") == expected_username - assert config.get("email") == expected_email - - -params_user_info_with_local_conf_file = [ - (["", ""], ["cwd_username", "cwd@email.com"]), - (["cli_username", ""], ["cli_username", "cwd@email.com"]), - (["", "cli@email.com"], ["cwd_username", "cli@email.com"]), - ([None, None], ["cwd_username", "cwd@email.com"]), - (["cli_username", None], ["cli_username", "cwd@email.com"]), - ([None, "cli@email.com"], ["cwd_username", "cli@email.com"]), - (["cli_username", "cli@email.com"], ["cli_username", "cli@email.com"]), -] -params_user_info_with_no_home_conf_file = [ - ( - [None, None], - ["input_username", "input@email.com"], - ["input_username", "input@email.com"], - ), - ( - ["cli_username", None], - ["", "input@email.com"], - ["cli_username", "input@email.com"], - ), - ( - [None, "cli@email.com"], - ["input_username", ""], - ["input_username", "cli@email.com"], - ), - ( - ["", ""], - ["input_username", "input@email.com"], - ["input_username", "input@email.com"], - ), - ( - ["cli_username", ""], - ["", "input@email.com"], - ["cli_username", "input@email.com"], - ), - ( - ["", "cli@email.com"], - ["input_username", ""], - ["input_username", "cli@email.com"], - ), - ( - ["cli_username", "cli@email.com"], - ["input_username", "input@email.com"], - ["cli_username", "cli@email.com"], - ), -] -params_user_info_no_conf_file_no_inputs = [ - ([None, None], ["", ""], ["", ""]), -] - @pytest.mark.parametrize( "runtime_inputs, expected", From 1af98b49dce1cd0c3e25ec82b4a756e6000c5345 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 22 Dec 2024 15:23:49 -0500 Subject: [PATCH 275/445] Fix docstring for DiffractionObject with PEP256 --- src/diffpy/utils/diffraction_objects.py | 33 ++++++++++++------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 602434f9..6870a68f 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -35,12 +35,13 @@ def _setter_wmsg(attribute): class DiffractionObject: - """ - A class to represent diffraction data for various scientific experiments involving scattering - techniques such as X-ray, neutron, and electron diffraction. This object can manage diffraction - data including transformations between different scattering quantities like q (scattering vector), - 2θ (two-theta angle), and d (interplanar spacing), and perform various operations like scaling, addition, - and subtraction of diffraction patterns. + """Class for storing and manipulating diffraction data. + + DiffractionObjct stores data produced from X-ray, neutron, and + electron scattering experiment. The object can transform between different + scattering quantities like q (scattering vector), 2θ (two-theta angle), + and d (interplanar spacing), and perform various operations like scaling, + addition, subtraction, and equality between diffraction objects. Attributes ---------- @@ -49,7 +50,7 @@ class DiffractionObject: input_xtype : str The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES} id : uuid - The unique identifier for the diffraction object. + The unique identifier for the diffraction object. scat_quantity : str The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". wavelength : float @@ -80,8 +81,7 @@ def __init__( name="", metadata={}, ): - """ - Initialize a DiffractionObject instance. + """Initialize a DiffractionObject instance. Parameters ---------- @@ -306,8 +306,8 @@ def id(self, _): raise AttributeError(_setter_wmsg("id")) def get_array_index(self, value, xtype=None): - """ - Return the index of the closest value in the array associated with the specified xtype. + """Return the index of the closest value in the array associated with + the specified xtype. Parameters ---------- @@ -370,8 +370,8 @@ def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): - """ - returns a new diffraction object which is the current object but rescaled in y to the target + """Returns a new diffraction object which is the current object but + rescaled in y to the target. The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. @@ -412,8 +412,8 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): return scaled def on_xtype(self, xtype): - """ - Return a list of two 1D np array with x and y data, raise an error if the specified xtype is invalid + """Return a list of two 1D np array with x and y data, raise an error + if the specified xtype is invalid. Parameters ---------- @@ -458,8 +458,7 @@ def dump(self, filepath, xtype=None): np.savetxt(f, data_to_save, delimiter=" ") def copy(self): - """ - Create a deep copy of the DiffractionObject instance. + """Create a deep copy of the DiffractionObject instance. Returns ------- From 90672eed6dce72f62d712735ce803d6c8b221253 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 22 Dec 2024 15:26:32 -0500 Subject: [PATCH 276/445] Fix small typo in diffractionobjet --- src/diffpy/utils/diffraction_objects.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6870a68f..92ad17dc 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -37,11 +37,12 @@ def _setter_wmsg(attribute): class DiffractionObject: """Class for storing and manipulating diffraction data. - DiffractionObjct stores data produced from X-ray, neutron, and - electron scattering experiment. The object can transform between different - scattering quantities like q (scattering vector), 2θ (two-theta angle), - and d (interplanar spacing), and perform various operations like scaling, - addition, subtraction, and equality between diffraction objects. + DiffractionObject stores data produced from X-ray, neutron, + and electron scattering experiments. The object can transform + between different scattering quantities such as q (scattering vector), + 2θ (two-theta angle), and d (interplanar spacing), and perform various + operations like scaling, addition, subtraction, and comparison for equality + between diffraction objects. Attributes ---------- From 77314a170362ead463e2a3bc7a8e2506d478b4b1 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 22 Dec 2024 20:27:47 -0500 Subject: [PATCH 277/445] fix typo and add examples --- doc/source/examples/tools_example.rst | 27 +++++++++++++++++++++++--- doc/source/utilities/tools_utility.rst | 9 +++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/doc/source/examples/tools_example.rst b/doc/source/examples/tools_example.rst index 60746e6f..51cb4470 100644 --- a/doc/source/examples/tools_example.rst +++ b/doc/source/examples/tools_example.rst @@ -41,7 +41,7 @@ The function will return .. code-block:: python - {"owner_email": "janedoe@email.com", "owner_name": "Jane Doe", "owner_orcid": "0000-0000-0000-0000"} + {"owner_name": "Jane Doe", "owner_email": "janedoe@email.com", "owner_orcid": "0000-0000-0000-0000"} Where does ``get_user_info()`` get the user information from? @@ -77,6 +77,7 @@ When building an application where you want to capture data-owner information, w ``check_and_build_global_config()`` first followed by ``get_user_info`` in your app workflow. E.g., .. code-block:: python + from diffpy.utils.tools import check_and_build_global_config, get_user_info from datetime import datetime import json @@ -95,12 +96,32 @@ it will only run once. However, if you want to bypass this behavior, ``check_and_build_global_config()`` takes an optional boolean ``skip_config_creation`` parameter that could be set to ``True`` at runtime to override the config creation. +What happens when you run ``check_and_build_global_config()``? +-------------------------------------------------------------- + +When you set ``skip_config_creation`` to ``False`` and there is no existing global configuration file, +the function will prompt you for inputs (name, email, ORCID). +An example of the prompts you may see is: + +.. code-block:: python + + Please enter the name you would want future work to be credited to: Jane Doe + Please enter your email: janedoe@example.com + Please enter your orcid ID if you know it: 0000-0000-0000-0000 + + +After receiving the inputs, the function will write the following to the file: + +.. code-block:: python + {"owner_name": "Jane Doe", "owner_email": "janedoe@email.com", "owner_orcid": "0000-0000-0000-0000"} + + I entered the wrong information in my config file so it always loads incorrect information, how do I fix that? -------------------------------------------------------------------------------------------------------------- It is easy to fix this simply by deleting the global and/or local config files, which will allow you to re-enter the information during the ``check_and_build_global_config()`` initialization -workflow. You can also simply editi the ``diffpyconfig.json`` file directly using a text +workflow. You can also simply edit the ``diffpyconfig.json`` file directly using a text editor. Locate the file ``diffpyconfig.json``, in your home directory and open it in an editor :: @@ -111,7 +132,7 @@ Locate the file ``diffpyconfig.json``, in your home directory and open it in an "owner_orcid": "0000-0000-4321-1234" } - Then you can edit the username and email as needed, make sure to save your edits. +Then you can edit the username and email as needed, make sure to save your edits. Automatically Capture Info about a Software Package Being Used ============================================================== diff --git a/doc/source/utilities/tools_utility.rst b/doc/source/utilities/tools_utility.rst index 01685a93..e0562faf 100644 --- a/doc/source/utilities/tools_utility.rst +++ b/doc/source/utilities/tools_utility.rst @@ -5,12 +5,17 @@ Tools Utility The ``diffpy.utils.tools`` module provides tool functions for use with diffpy apps. -- ``get_user_info()``: This function is designed for managing and tracking username and email information. - +- ``get_user_info()``: This function is designed for managing and tracking user information (name, email, orcid). Developers can use this function to simplify the process of loading, merging, and saving information consistently and easily. Additionally, it saves the effort of re-entering information, and allows overriding current information by passing parameters. +- ``check_and_build_global_config()``: This function helps create a global configuration file + that can be used by ``get_user_info()``. + If no existing configuration file is found, and the user allows inputs, this function prompts for information. + The provided inputs are then saved to a global configuration file. + This file can be reused later by ``get_user_info()`` to ensure that the work credits and user information are consistently stored. + - ``get_package_info()``: This function loads package name and version information into a dictionary. It updates the package information under the key "package_info" in the format {"package_name": "version_number"}, resulting in an entry in the passed metadata dictionary that looks like From ddf7180afdde32c7bbc9993a38eecb582ad14858 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 22 Dec 2024 20:28:24 -0500 Subject: [PATCH 278/445] fix typos --- src/diffpy/utils/tools.py | 41 ++++++--------------------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 91505e6f..87d69a64 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -4,29 +4,7 @@ from pathlib import Path -def clean_dict(obj): - """ - Remove keys from the dictionary where the corresponding value is None. - - Parameters - ---------- - obj: dict - The dictionary to clean. If None, initialize as an empty dictionary. - - Returns - ------- - dict: - The cleaned dictionary with keys removed where the value is None. - - """ - obj = obj if obj is not None else {} - for key, value in copy(obj).items(): - if not value: - del obj[key] - return obj - - -def stringify(obj): +def _stringify(obj): """ Convert None to an empty string. @@ -43,7 +21,7 @@ def stringify(obj): return obj if obj is not None else "" -def load_config(file_path): +def _load_config(file_path): """ Load configuration from a .json file. @@ -67,13 +45,6 @@ def load_config(file_path): return {} -def _sorted_merge(*dicts): - merged = {} - for d in dicts: - merged.update(d) - return merged - - def _create_global_config(args): username = input( f"Please enter the name you would want future work to be credited to " f"[{args.get('username', '')}]: " @@ -81,7 +52,7 @@ def _create_global_config(args): email = input(f"Please enter the your email " f"[{args.get('email', '')}]: ").strip() or args.get("email", "") return_bool = False if username is None or email is None else True with open(Path().home() / "diffpyconfig.json", "w") as f: - f.write(json.dumps({"username": stringify(username), "email": stringify(email)})) + f.write(json.dumps({"username": _stringify(username), "email": _stringify(email)})) print( f"You can manually edit the config file at {Path().home() / 'diffpyconfig.json'} using any text editor.\n" f"Or you can update the config file by passing new values to get_user_info(), " @@ -116,7 +87,7 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): The name of the user who will show as owner in the metadata that is stored with the data owner_email: string, optional, default is the value stored in the global or local config file. The email of the user/owner - owner_name: string, optional, default is the value stored in the global or local config file. + owner_orcid: string, optional, default is the value stored in the global or local config file. The ORCID id of the user/owner Returns @@ -130,8 +101,8 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): for key, value in copy(runtime_info).items(): if value is None or value == "": del runtime_info[key] - global_config = load_config(Path().home() / "diffpyconfig.json") - local_config = load_config(Path().cwd() / "diffpyconfig.json") + global_config = _load_config(Path().home() / "diffpyconfig.json") + local_config = _load_config(Path().cwd() / "diffpyconfig.json") # if global_config is None and local_config is None: # print( # "No global configuration file was found containing " From c29053dacc307a9e3fbca8bf59679aed8ee45d1a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 23 Dec 2024 10:21:50 -0500 Subject: [PATCH 279/445] remove unnecessary function --- src/diffpy/utils/tools.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 87d69a64..a6c30ed3 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -45,22 +45,6 @@ def _load_config(file_path): return {} -def _create_global_config(args): - username = input( - f"Please enter the name you would want future work to be credited to " f"[{args.get('username', '')}]: " - ).strip() or args.get("username", "") - email = input(f"Please enter the your email " f"[{args.get('email', '')}]: ").strip() or args.get("email", "") - return_bool = False if username is None or email is None else True - with open(Path().home() / "diffpyconfig.json", "w") as f: - f.write(json.dumps({"username": _stringify(username), "email": _stringify(email)})) - print( - f"You can manually edit the config file at {Path().home() / 'diffpyconfig.json'} using any text editor.\n" - f"Or you can update the config file by passing new values to get_user_info(), " - f"see examples here: https://www.diffpy.org/diffpy.utils/examples/toolsexample.html" - ) - return return_bool - - def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): """ Get name, email and orcid of the owner/user from various sources and return it as a metadata dictionary From cc8cf059cc9dee12c9c1eb8501d4b621bb088257 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 23 Dec 2024 10:23:45 -0500 Subject: [PATCH 280/445] no news added --- news/user_info_doc.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/user_info_doc.rst diff --git a/news/user_info_doc.rst b/news/user_info_doc.rst new file mode 100644 index 00000000..43bee6bd --- /dev/null +++ b/news/user_info_doc.rst @@ -0,0 +1,23 @@ +**Added:** + +* no news added: simply adding documentation for config update workflow + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 1a2214fc14916eef8f1737ca70bd4ff38f82980d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 14:03:55 -0500 Subject: [PATCH 281/445] Rename id to uuid in DiffractionObjet --- news/uuid-rename.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/diffraction_objects.py | 14 +++++++------- tests/test_diffraction_objects.py | 24 +++++++++++++----------- 3 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 news/uuid-rename.rst diff --git a/news/uuid-rename.rst b/news/uuid-rename.rst new file mode 100644 index 00000000..ecd7f22d --- /dev/null +++ b/news/uuid-rename.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* DiffractionObject's "id" property renamed to "uuid" + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 92ad17dc..2922cdbd 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -50,7 +50,7 @@ class DiffractionObject: The array containing the quantity of q, tth, d values. input_xtype : str The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES} - id : uuid + _uuid : uuid The unique identifier for the diffraction object. scat_quantity : str The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". @@ -127,7 +127,7 @@ def __init__( >>> print(do.metadata) """ - self._id = uuid.uuid4() + self._uuid = uuid.uuid4() self._input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, metadata): @@ -299,12 +299,12 @@ def input_xtype(self, _): raise AttributeError(_setter_wmsg("input_xtype")) @property - def id(self): - return self._id + def uuid(self): + return self._uuid - @id.setter - def id(self, _): - raise AttributeError(_setter_wmsg("id")) + @uuid.setter + def uuid(self, _): + raise AttributeError(_setter_wmsg("uuid")) def get_array_index(self, value, xtype=None): """Return the index of the closest value in the array associated with diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 63f349eb..33675b9e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -479,7 +479,7 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec else: actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( - actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']" + actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_uuid']" ) assert diff == {} @@ -523,27 +523,29 @@ def test_all_array_setter(do_minimal): do.all_arrays = np.empty((4, 4)) -def test_id_getter(do_minimal): +def test_uuid_getter(do_minimal): do = do_minimal - assert hasattr(do, "id") - assert isinstance(do.id, UUID) - assert len(str(do.id)) == 36 + assert hasattr(do, "uuid") + assert isinstance(do.uuid, UUID) + assert len(str(do.uuid)) == 36 -def test_id_getter_with_mock(mocker, do_minimal): - mocker.patch.object(DiffractionObject, "id", new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87")) +def test_uuid_getter_with_mock(mocker, do_minimal): + mocker.patch.object( + DiffractionObject, "uuid", new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") + ) do = do_minimal - assert do.id == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") + assert do.uuid == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") -def test_id_setter_error(do_minimal): +def test_uuid_setter_error(do_minimal): do = do_minimal with pytest.raises( AttributeError, - match="Direct modification of attribute 'id' is not allowed. Please use 'input_data' to modify 'id'.", + match="Direct modification of attribute 'uuid' is not allowed. Please use 'input_data' to modify 'uuid'.", ): - do.id = uuid.uuid4() + do.uuid = uuid.uuid4() def test_xarray_yarray_length_mismatch(): From 576afb2b35a5a1e79d191f1b655f9e766e633d3e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 14:28:16 -0500 Subject: [PATCH 282/445] No manual edits made to reformat docstrings --- .pre-commit-config.yaml | 7 +++++++ src/diffpy/__init__.py | 1 - src/diffpy/utils/__init__.py | 3 +-- src/diffpy/utils/parsers/__init__.py | 4 +--- src/diffpy/utils/parsers/serialization.py | 4 ++-- src/diffpy/utils/resampler.py | 4 ++-- src/diffpy/utils/tools.py | 20 ++++++------------ src/diffpy/utils/transforms.py | 20 +++++++----------- src/diffpy/utils/version.py | 1 - src/diffpy/utils/wx/__init__.py | 4 +--- src/diffpy/utils/wx/gridutils.py | 25 ++++++++++++----------- tests/test_loaddata.py | 9 ++++---- tests/test_version.py | 6 +++--- 13 files changed, 47 insertions(+), 61 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b7fb3631..6dca6f1e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -58,3 +58,10 @@ repos: - id: prettier additional_dependencies: - "prettier@^3.2.4" + # docformatter - formats docstrings using PEP 257 + - repo: https://github.com/s-weigand/docformatter + rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c + hooks: + - id: docformatter + additional_dependencies: [tomli] + args: [--in-place, --config, ./pyproject.toml] diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 11a4204c..00208e23 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -16,7 +16,6 @@ # See LICENSE.rst for license information. # ############################################################################## - """diffpy - tools for structure analysis by diffraction. Blank namespace package. diff --git a/src/diffpy/utils/__init__.py b/src/diffpy/utils/__init__.py index c9909a8a..12f4a49d 100644 --- a/src/diffpy/utils/__init__.py +++ b/src/diffpy/utils/__init__.py @@ -12,8 +12,7 @@ # See LICENSE.rst for license information. # ############################################################################## - -"""Shared utilities for diffpy packages""" +"""Shared utilities for diffpy packages.""" # package version from diffpy.utils.version import __version__ diff --git a/src/diffpy/utils/parsers/__init__.py b/src/diffpy/utils/parsers/__init__.py index bab9943c..a0278e27 100644 --- a/src/diffpy/utils/parsers/__init__.py +++ b/src/diffpy/utils/parsers/__init__.py @@ -12,6 +12,4 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Various utilities related to data parsing and manipulation. -""" +"""Various utilities related to data parsing and manipulation.""" diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index 46d4b8ff..20b34b31 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -33,8 +33,8 @@ def serialize_data( show_path=True, serial_file=None, ): - """Serialize file data into a dictionary. Can also save dictionary into a serial language file. Dictionary is - formatted as {filename: data}. + """Serialize file data into a dictionary. Can also save dictionary into a + serial language file. Dictionary is formatted as {filename: data}. Requires hdata and data_table (can be generated by loadData). diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 21fabf56..115087a2 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Various utilities related to data parsing and manipulation.""" import warnings @@ -80,7 +79,8 @@ def wsinterp(x, xp, fp, left=None, right=None): def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): - """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon grid. + """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon + grid. Takes a band-limited function fp and original grid xp and resamples fp on the NS grid. Uses the minimum number of points N required by the Nyquist sampling theorem. diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 91505e6f..8b021ad1 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -5,8 +5,7 @@ def clean_dict(obj): - """ - Remove keys from the dictionary where the corresponding value is None. + """Remove keys from the dictionary where the corresponding value is None. Parameters ---------- @@ -17,7 +16,6 @@ def clean_dict(obj): ------- dict: The cleaned dictionary with keys removed where the value is None. - """ obj = obj if obj is not None else {} for key, value in copy(obj).items(): @@ -27,8 +25,7 @@ def clean_dict(obj): def stringify(obj): - """ - Convert None to an empty string. + """Convert None to an empty string. Parameters ---------- @@ -44,8 +41,7 @@ def stringify(obj): def load_config(file_path): - """ - Load configuration from a .json file. + """Load configuration from a .json file. Parameters ---------- @@ -56,7 +52,6 @@ def load_config(file_path): ------- dict: The configuration dictionary or {} if the config file does not exist. - """ config_file = Path(file_path).resolve() if config_file.is_file(): @@ -91,8 +86,8 @@ def _create_global_config(args): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): - """ - Get name, email and orcid of the owner/user from various sources and return it as a metadata dictionary + """Get name, email and orcid of the owner/user from various sources and + return it as a metadata dictionary. The function looks for the information in json format configuration files with the name 'diffpyconfig.json'. These can be in the user's home directory and in the current working directory. The information in the @@ -124,7 +119,6 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): dict: The dictionary containing username, email and orcid of the user/owner, and any other information stored in the global or local config files. - """ runtime_info = {"owner_name": owner_name, "owner_email": owner_email, "owner_orcid": owner_orcid} for key, value in copy(runtime_info).items(): @@ -149,8 +143,7 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): def get_package_info(package_names, metadata=None): - """ - Fetches package version and updates it into (given) metadata. + """Fetches package version and updates it into (given) metadata. Package info stored in metadata as {'package_info': {'package_name': 'version_number'}}. @@ -164,7 +157,6 @@ def get_package_info(package_names, metadata=None): ------- dict: The updated metadata dict with package info inserted. - """ if metadata is None: metadata = {} diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 1d50aeab..f2956879 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -29,8 +29,7 @@ def _validate_inputs(q, wavelength): def q_to_tth(q, wavelength): - r""" - Helper function to convert q to two-theta. + r"""Helper function to convert q to two-theta. If wavelength is missing, returns x-values that are integer indexes @@ -72,9 +71,7 @@ def q_to_tth(q, wavelength): def tth_to_q(tth, wavelength): - r""" - - Helper function to convert two-theta to q on independent variable axis. + r"""Helper function to convert two-theta to q on independent variable axis. If wavelength is missing, returns independent variable axis as integer indexes. @@ -120,8 +117,8 @@ def tth_to_q(tth, wavelength): def q_to_d(q): - r""" - Helper function to convert q to d on independent variable axis, using :math:`d = \frac{2 \pi}{q}`. + r"""Helper function to convert q to d on independent variable axis, using + :math:`d = \frac{2 \pi}{q}`. Parameters ---------- @@ -140,8 +137,7 @@ def q_to_d(q): def tth_to_d(tth, wavelength): - r""" - Helper function to convert two-theta to d on independent variable axis. + r"""Helper function to convert two-theta to d on independent variable axis. The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. @@ -174,8 +170,7 @@ def tth_to_d(tth, wavelength): def d_to_q(d): - r""" - Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`. + r"""Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`. Parameters ---------- @@ -194,8 +189,7 @@ def d_to_q(d): def d_to_tth(d, wavelength): - r""" - Helper function to convert d to two-theta on independent variable axis. + r"""Helper function to convert d to two-theta on independent variable axis. The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index 0bc397e4..e74c47bd 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -12,7 +12,6 @@ # See LICENSE.rst for license information. # ############################################################################## - """Definition of __version__.""" # We do not use the other three variables, but can be added back if needed. diff --git a/src/diffpy/utils/wx/__init__.py b/src/diffpy/utils/wx/__init__.py index 3f7417ef..e2f08735 100644 --- a/src/diffpy/utils/wx/__init__.py +++ b/src/diffpy/utils/wx/__init__.py @@ -12,6 +12,4 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Utilities related wx Python GUIs. -""" +"""Utilities related wx Python GUIs.""" diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index c459ac30..a01e8f92 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -12,9 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Common functions for manipulating wx.grid.Grid. -""" +"""Common functions for manipulating wx.grid.Grid.""" import wx @@ -53,7 +51,9 @@ def getSelectionColumns(grid): def getSelectedCells(grid): """Get list of (row, col) pairs of all selected cells. - Unlike grid.GetSelectedCells this returns them all no matter how they were selected. + + Unlike grid.GetSelectedCells this returns them all no matter how + they were selected. """ rows = grid.GetNumberRows() cols = grid.GetNumberCols() @@ -75,8 +75,8 @@ def getSelectedCells(grid): def limitSelectionToRows(grid, indices): - """Limit selection to the specified row indices. - No action for empty indices. + """Limit selection to the specified row indices. No action for empty + indices. Parameters ---------- @@ -112,10 +112,10 @@ def limitSelectionToRows(grid, indices): def quickResizeColumns(grid, indices): """Resize the columns that were recently affected by cell changes. - This is faster than the normal grid AutoSizeColumns, since the latter loops - over the entire grid. In addition, this will not cause a - EVT_GRID_CMD_CELL_CHANGE event to be thrown, which can cause recursion. - This method will only increase column size. + This is faster than the normal grid AutoSizeColumns, since the + latter loops over the entire grid. In addition, this will not cause + a EVT_GRID_CMD_CELL_CHANGE event to be thrown, which can cause + recursion. This method will only increase column size. """ # Get the columns and maximum text width in each one dc = wx.ScreenDC() @@ -140,8 +140,9 @@ def quickResizeColumns(grid, indices): def _indicesToBlocks(indices): - """Convert a list of integer indices to a list of (start, stop) tuples. - The (start, stop) tuple defines a continuous block, where the stop index is included in the block. + """Convert a list of integer indices to a list of (start, stop) tuples. The + (start, stop) tuple defines a continuous block, where the stop index is + included in the block. Parameters ---------- diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index a7e273e7..f825139c 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -"""Unit tests for diffpy.utils.parsers.loaddata -""" +"""Unit tests for diffpy.utils.parsers.loaddata.""" import numpy as np import pytest @@ -10,7 +9,7 @@ def test_loadData_default(datafile): - """check loadData() with default options""" + """Check loadData() with default options.""" loaddata01 = datafile("loaddata01.txt") d2c = np.array([[3, 31], [4, 32], [5, 33]]) @@ -38,7 +37,7 @@ def test_loadData_default(datafile): def test_loadData_1column(datafile): - """check loading of one-column data.""" + """Check loading of one-column data.""" loaddata01 = datafile("loaddata01.txt") d1c = np.arange(1, 6) @@ -54,7 +53,7 @@ def test_loadData_1column(datafile): def test_loadData_headers(datafile): - """check loadData() with headers options enabled""" + """Check loadData() with headers options enabled.""" expected = { "wavelength": 0.1, "dataformat": "Qnm", diff --git a/tests/test_version.py b/tests/test_version.py index 421a96e4..4152a197 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,10 +1,10 @@ -"""Unit tests for __version__.py -""" +"""Unit tests for __version__.py.""" import diffpy.utils 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.utils, "__version__") assert diffpy.utils.__version__ != "0.0.0" From 660f8a1987298c19f001c5c2e265566aef9f4f3a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 14:31:15 -0500 Subject: [PATCH 283/445] Add news for docformatt --- news/docformatter.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/docformatter.rst diff --git a/news/docformatter.rst b/news/docformatter.rst new file mode 100644 index 00000000..56368125 --- /dev/null +++ b/news/docformatter.rst @@ -0,0 +1,23 @@ +**Added:** + +* docforamtter in pre-commit for automatic formatting of docstrings to PEP 257 + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From c39b301488ba3f912a6b4a9a1a4817a4a9896e51 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 14:50:24 -0500 Subject: [PATCH 284/445] Refactor test_on_xtype --- tests/test_diffraction_objects.py | 35 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 63f349eb..8c739a2f 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -12,10 +12,10 @@ @pytest.mark.parametrize( - "do_args_1, do_args_2, expected_equality, warning_expected", + "do_args_1, do_args_2, expected_equality, wavelength_warning_expected", [ # Test when __eq__ returns True and False - # Identical args, expect equality + # C1: Identical args, expect equality ( { "name": "same", @@ -56,7 +56,7 @@ False, True, ), - ( # One without wavelength, expect inequality + ( # C2: One without wavelength, expect inequality { "wavelength": 0.71, "xtype": "tth", @@ -73,7 +73,7 @@ False, True, ), - ( # Different wavelengths, expect inequality + ( # C3: Different wavelength values, expect inequality { "wavelength": 0.71, "xtype": "tth", @@ -91,7 +91,7 @@ False, False, ), - ( # Different scat_quantity, expect inequality + ( # C4: Different scat_quantity, expect inequality { "scat_quantity": "x-ray", "xtype": "tth", @@ -109,7 +109,7 @@ False, True, ), - ( # Different q xarray values, expect inequality + ( # C5: Different q xarray values, expect inequality { "xtype": "q", "xarray": np.array([1.0, 2.0]), @@ -124,7 +124,7 @@ False, True, ), - ( # Different metadata, expect inequality + ( # C6: Different metadata, expect inequality { "xtype": "q", "xarray": np.empty(0), @@ -143,9 +143,9 @@ ], ) def test_diffraction_objects_equality( - do_args_1, do_args_2, expected_equality, warning_expected, wavelength_warning_msg + do_args_1, do_args_2, expected_equality, wavelength_warning_expected, wavelength_warning_msg ): - if warning_expected: + if wavelength_warning_expected: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): do_1 = DiffractionObject(**do_args_1) do_2 = DiffractionObject(**do_args_2) @@ -158,9 +158,16 @@ def test_diffraction_objects_equality( @pytest.mark.parametrize( "xtype, expected_xarray", [ + # Test whether on_xtype returns the correct xarray values. + # The test DO instance is initialized with tth. + # C1: tth to tth, expect no change in xaray value + # 1. "tth" provided, expect tth + # 2. "2theta" provided, expect tth ("tth", np.array([30, 60])), ("2theta", np.array([30, 60])), + # C2: "q" provided, expect q ("q", np.array([0.51764, 1])), + # C3: "d" provided, expect d ("d", np.array([12.13818, 6.28319])), ], ) @@ -290,7 +297,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs", [ - # UC1: User did not specify anything + # C1: User did not specify anything ( { "xarray": np.array([0.1, 0.2, 0.3]), @@ -311,7 +318,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # UC2: User specified more than one of q, tth, and d + # C2: User specified more than one of q, tth, and d ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -352,12 +359,12 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): @pytest.mark.parametrize( "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", [ - # UC1: Exact match + # U1: Exact match (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # UC2: Target value lies in the array, returns the (first) closest index + # U2: Target value lies in the array, returns the (first) closest index (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # UC3: Target value out of the range, returns the closest index + # U3: Target value out of the range, returns the closest index (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), ], From 697e7996fcd61748455df06de14ca75df3ad16de Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 14:55:51 -0500 Subject: [PATCH 285/445] Refactor test_scale_to() with improved test cases --- tests/test_diffraction_objects.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 8c739a2f..65cdc6ad 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -165,9 +165,9 @@ def test_diffraction_objects_equality( # 2. "2theta" provided, expect tth ("tth", np.array([30, 60])), ("2theta", np.array([30, 60])), - # C2: "q" provided, expect q + # C2: "q" provided, expect q converted from tth ("q", np.array([0.51764, 1])), - # C3: "d" provided, expect d + # C3: "d" provided, expect d converted from tth ("d", np.array([12.13818, 6.28319])), ], ) @@ -192,7 +192,7 @@ def test_init_invalid_xtype(): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs, expected", [ - # Test that scale_to() scales to the correct values + # Test whether scale_to() scales to the expected values # C1: Same x-array and y-array, check offset ( { @@ -297,7 +297,8 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs", [ - # C1: User did not specify anything + # Test expected errors produced from scale_to() with invalid inputs + # C1: none of q, tth, d, provided, expect ValueError ( { "xarray": np.array([0.1, 0.2, 0.3]), @@ -318,7 +319,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # C2: User specified more than one of q, tth, and d + # C2: more than one of either q, tth, d, provided, expect ValueError ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -359,12 +360,13 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): @pytest.mark.parametrize( "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", [ - # U1: Exact match + # Test whether get_array_index() returns the expected index + # C1: Exact match (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # U2: Target value lies in the array, returns the (first) closest index + # C2: Target value lies in the array, returns the (first) closest index (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # U3: Target value out of the range, returns the closest index + # C3: Target value out of the range, returns the closest index (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), ], From 13742d0ada437cd21e207e1df96048feb97a7945 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 23 Dec 2024 15:11:52 -0500 Subject: [PATCH 286/445] edit docs --- doc/source/examples/tools_example.rst | 6 ++---- doc/source/utilities/tools_utility.rst | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/source/examples/tools_example.rst b/doc/source/examples/tools_example.rst index 51cb4470..268c26dd 100644 --- a/doc/source/examples/tools_example.rst +++ b/doc/source/examples/tools_example.rst @@ -110,10 +110,8 @@ An example of the prompts you may see is: Please enter your orcid ID if you know it: 0000-0000-0000-0000 -After receiving the inputs, the function will write the following to the file: - -.. code-block:: python - {"owner_name": "Jane Doe", "owner_email": "janedoe@email.com", "owner_orcid": "0000-0000-0000-0000"} +After receiving the inputs, the function will write the information to +the `diffpyconfig.json` file in your home directory. I entered the wrong information in my config file so it always loads incorrect information, how do I fix that? diff --git a/doc/source/utilities/tools_utility.rst b/doc/source/utilities/tools_utility.rst index e0562faf..e524a65b 100644 --- a/doc/source/utilities/tools_utility.rst +++ b/doc/source/utilities/tools_utility.rst @@ -11,8 +11,8 @@ The ``diffpy.utils.tools`` module provides tool functions for use with diffpy ap passing parameters. - ``check_and_build_global_config()``: This function helps create a global configuration file - that can be used by ``get_user_info()``. - If no existing configuration file is found, and the user allows inputs, this function prompts for information. + that can be used by, for example, ``get_user_info()``. + If no existing configuration file is found, this function prompts for information. The provided inputs are then saved to a global configuration file. This file can be reused later by ``get_user_info()`` to ensure that the work credits and user information are consistently stored. From 7543305c0a1cf8a13e9d7ed2e1b35cfb917180a5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:19:25 -0500 Subject: [PATCH 287/445] Refactor test.parameter test cases using BG standard --- tests/test_diffraction_objects.py | 119 ++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 24 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 65cdc6ad..71c11d4a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -38,7 +38,8 @@ True, False, ), - ( # Different names, expect inequality + # Different names, expect inequality + ( { "name": "something", "xtype": "tth", @@ -56,7 +57,8 @@ False, True, ), - ( # C2: One without wavelength, expect inequality + # C2: One without wavelength, expect inequality + ( { "wavelength": 0.71, "xtype": "tth", @@ -73,7 +75,8 @@ False, True, ), - ( # C3: Different wavelength values, expect inequality + # C3: Different wavelength values, expect inequality + ( { "wavelength": 0.71, "xtype": "tth", @@ -91,7 +94,8 @@ False, False, ), - ( # C4: Different scat_quantity, expect inequality + # C4: Different scat_quantity, expect inequality + ( { "scat_quantity": "x-ray", "xtype": "tth", @@ -109,7 +113,8 @@ False, True, ), - ( # C5: Different q xarray values, expect inequality + # C5: Different q xarray values, expect inequality + ( { "xtype": "q", "xarray": np.array([1.0, 2.0]), @@ -124,7 +129,8 @@ False, True, ), - ( # C6: Different metadata, expect inequality + # C6: Different metadata, expect inequality + ( { "xtype": "q", "xarray": np.empty(0), @@ -358,22 +364,84 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): @pytest.mark.parametrize( - "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", + "do_args, get_array_index_inputs, expected_index", [ - # Test whether get_array_index() returns the expected index - # C1: Exact match - (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # C2: Target value lies in the array, returns the (first) closest index - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # C3: Target value out of the range, returns the closest index - (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), + # Test get_array_index() returns the expected index given xtype and value + # C1: Target value is in the xarray and xtype is identical, expect exact index match + ( + { + "wavelength": 4 * np.pi, + "xarray": np.array([30.005, 60]), + "yarray": np.array([1, 2]), + "xtype": "tth", + }, + { + "xtype": "tth", + "value": 30.005, + }, + [0], + ), + # C2: Target value lies in the array, expect the (first) closest index + ( + { + "wavelength": 4 * np.pi, + "xarray": np.array([30, 60]), + "yarray": np.array([1, 2]), + "xtype": "tth", + }, + { + "xtype": "tth", + "value": 45, + }, + [0], + ), + ( + { + "wavelength": 4 * np.pi, + "xarray": np.array([30, 60]), + "yarray": np.array([1, 2]), + "xtype": "tth", + }, + { + "xtype": "q", + "value": 0.25, + }, + [0], + ), + # C3: Target value out of the range, expect the closest index + # 1. Test with xtype of "q" + ( + { + "wavelength": 4 * np.pi, + "xarray": np.array([0.25, 0.5, 0.71]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + }, + { + "xtype": "q", + "value": 0.1, + }, + [0], + ), + # 2. Test with xtype of "tth" + ( + { + "wavelength": 4 * np.pi, + "xarray": np.array([30, 60]), + "yarray": np.array([1, 2]), + "xtype": "tth", + }, + { + "xtype": "tth", + "value": 63, + }, + [1], + ), ], ) -def test_get_array_index(wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index): - do = DiffractionObject(wavelength=wavelength, xarray=xarray, yarray=yarray, xtype=xtype_1) - actual_index = do.get_array_index(value=value, xtype=xtype_2) +def test_get_array_index(do_args, get_array_index_inputs, expected_index): + do = DiffractionObject(**do_args) + actual_index = do.get_array_index(get_array_index_inputs["value"], get_array_index_inputs["xtype"]) assert actual_index == expected_index @@ -420,7 +488,9 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( "do_init_args, expected_do_dict, divide_by_zero_warning_expected", [ - ( # Instantiate just array attributes + # Test __dict__ of DiffractionObject instance initialized with valid arguments + ( + # C1: Minimum arguments provided for init, expect all attributes set without None { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -449,7 +519,8 @@ def test_dump(tmp_path, mocker): }, True, ), - ( # Instantiate just array attributes + # C2: Initialize with an optional scat_quantity argument, expect non-empty string for scat_quantity + ( { "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -496,11 +567,12 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - ( # C1: No arguments provided + # Test expected error messages due to required arguments in DiffractionObject constructor + ( # C1: No arguments provided, expect 3 required positional arguments error {}, "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", ), - ( # C2: Only xarray and yarray provided + ( # C2: Only xarray and yarray provided, expect 1 required positional argument error {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, "missing 1 required positional argument: 'xtype'", ), @@ -523,7 +595,6 @@ def test_all_array_getter(do_minimal_tth): def test_all_array_setter(do_minimal): do = do_minimal - # Attempt to directly modify the property with pytest.raises( AttributeError, match="Direct modification of attribute 'all_arrays' is not allowed. " From 625b69b3eb9deba8f32072f739d0aeb14ec0c104 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:28:33 -0500 Subject: [PATCH 288/445] Enhance last bit of comment --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 71c11d4a..91b7f603 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -567,7 +567,7 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - # Test expected error messages due to required arguments in DiffractionObject constructor + # Test expected error messages when 3 required arguments not provided in DiffractionObject init ( # C1: No arguments provided, expect 3 required positional arguments error {}, "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", From 406175d6313d2750dfd93ea100a703cdc7786bb1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:36:40 -0500 Subject: [PATCH 289/445] Add news and scale to need to be more refined --- news/pytest-comment.rst | 23 +++++++++++++++++++++++ tests/test_diffraction_objects.py | 3 +-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 news/pytest-comment.rst diff --git a/news/pytest-comment.rst b/news/pytest-comment.rst new file mode 100644 index 00000000..9d0f81bb --- /dev/null +++ b/news/pytest-comment.rst @@ -0,0 +1,23 @@ +**Added:** + +* Group's Pytest practices for using @pytest.mark.parametrize in test_diffraction_objects.py + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 91b7f603..39aa0d47 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -165,7 +165,6 @@ def test_diffraction_objects_equality( "xtype, expected_xarray", [ # Test whether on_xtype returns the correct xarray values. - # The test DO instance is initialized with tth. # C1: tth to tth, expect no change in xaray value # 1. "tth" provided, expect tth # 2. "2theta" provided, expect tth @@ -199,7 +198,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether scale_to() scales to the expected values - # C1: Same x-array and y-array, check offset + # C1: Same x-array and y-array with 2.1 offset, expect yarray shifted by 2.1 offset ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), From be0dd64e81d4f2fbdeda448c510dd7322cfcb17e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:37:20 -0500 Subject: [PATCH 290/445] Fix small typo in aray --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 39aa0d47..c3802b63 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -165,7 +165,7 @@ def test_diffraction_objects_equality( "xtype, expected_xarray", [ # Test whether on_xtype returns the correct xarray values. - # C1: tth to tth, expect no change in xaray value + # C1: tth to tth, expect no change in xarray value # 1. "tth" provided, expect tth # 2. "2theta" provided, expect tth ("tth", np.array([30, 60])), From 66e24bf636ac0383c3a2f36d7f2c258266bad989 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:42:25 -0500 Subject: [PATCH 291/445] improve C2 under test_scale_to_bad --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index c3802b63..46b23173 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -324,7 +324,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # C2: more than one of either q, tth, d, provided, expect ValueError + # C2: tth and d both provided, expect ValueError ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), From 499a1c37930073c11d2b6fc2f2e6ef17ac0bd06d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:42:54 -0500 Subject: [PATCH 292/445] improve clarify for test_scale_to --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 46b23173..6fa843d8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -324,7 +324,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # C2: tth and d both provided, expect ValueError + # C2: tth and d both provided, expect ValueErrort ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), From 1d64d28fabc44a5bf72d1ef0a7842c23203d0c31 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 23 Dec 2024 15:57:42 -0500 Subject: [PATCH 293/445] Refactor test_transform.py --- news/pytest-reformat-transform.rst | 23 +++++++++++++++++++++++ tests/test_transforms.py | 18 ++++++++---------- 2 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 news/pytest-reformat-transform.rst diff --git a/news/pytest-reformat-transform.rst b/news/pytest-reformat-transform.rst new file mode 100644 index 00000000..2bd19e87 --- /dev/null +++ b/news/pytest-reformat-transform.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: previous PR already contains has an news item for refactoring test_transform.py + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 2a40805d..ff202384 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -32,7 +32,6 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): actual_tth = q_to_tth(q, wavelength) else: actual_tth = q_to_tth(q, wavelength) - assert np.allclose(expected_tth, actual_tth) @@ -205,14 +204,14 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: User specified an invalid tth value of > 180 degrees (without wavelength) + # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # C2: User specified an invalid tth value of > 180 degrees (with wavelength) + # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -229,13 +228,13 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m @pytest.mark.parametrize( "wavelength, d, expected_tth, divide_by_zero_warning_expected", [ - # C1: Empty d values, no wavelength, return empty arrays + # C1: Empty d values, no wavelength, expect empty tth values (None, np.empty((0)), np.empty((0)), False), - # C2: Empty d values, wavelength specified, return empty arrays + # C2: Empty d values with wavelength, expect empty tth values (4 * np.pi, np.empty((0)), np.empty(0), False), - # C3: User specified valid d values, no wavelength, return empty arrays + # C3: Valid d values, no wavelength, expect valid and non-empty tth values (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), - # C4: User specified valid d values (with wavelength) + # C4: Valid d values with wavelength, expect valid and non-empty thh values ( 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), @@ -254,16 +253,15 @@ def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, actual_tth = d_to_tth(d, wavelength) else: actual_tth = d_to_tth(d, wavelength) - assert np.allclose(actual_tth, expected_tth) @pytest.mark.parametrize( "wavelength, d, expected_error_type", [ - # C1: User specified invalid d values that result in tth > 180 degrees + # C1: Invalid d values that result in tth > 180 degrees, expect invalid q, d, or wavelength ValueError (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), - # C2: User specified a wrong wavelength that result in tth > 180 degrees + # C2: Wrong wavelength that result in tth > 180 degreesm, expect invalid q, d, or wavelength ValueError (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) From f498a0740d26627175ffa8c073311ca3db0c2969 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 25 Dec 2024 07:18:22 -0500 Subject: [PATCH 294/445] docstring for create global config and have it return a bool --- src/diffpy/utils/tools.py | 40 ++++++++++++++++++++++++++++++++++----- tests/test_tools.py | 11 ++++++++--- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 27e49ff8..e7d44d12 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -123,11 +123,40 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): def check_and_build_global_config(skip_config_creation=False): + """ + Checks for a global diffpu config file in user's home directory and creates one if it is missing + + The file it looks for is called diffpyconfig.json. This can contain anything in json format, but + minimally contains information about the computer owner. The information is used + when diffpy objects are created and saved to files or databases to retain ownership information + of datasets. For example, it is used by diffpy.utils.tools.get_user_info(). + + If the function finds no config file in the user's home directory it interrupts execution + and prompts the user for name, email, and orcid information. It then creates the config file + with this information inside it. + + The function returns True if the file exists and False otherwise. + + If you would like to check for a file but not run the file creation workflow you can set + the optional argument skip_config_creation to True. + + Parameters + ---------- + skip_config_creation: bool, optional, Default is False + The bool that will override the creation workflow even if no config file exists. + + Returns + ------- + bool: True if the file exists and False otherwise. + + """ + config_exists = False config_path = Path().home() / "diffpyconfig.json" - if skip_config_creation: - return if config_path.is_file(): - return + config_exists = True + return config_exists + if skip_config_creation: + return config_exists intro_text = ( "No global configuration file was found containing information about the user to " "associate with the data.\n By following the prompts below you can add your name " @@ -154,10 +183,11 @@ def check_and_build_global_config(skip_config_creation=False): f"delete the config file and this workflow will rerun next time you run this " f"program. Or you may open the config file in a text editor and manually edit the" f"entries. For more information, see: " - f"https://diffpy.githu.io/diffpy.utils/examples/tools_example.html" + f"https://diffpy.github.io/diffpy.utils/examples/tools_example.html" ) print(outro_text) - return + config_exists = True + return config_exists def get_package_info(package_names, metadata=None): diff --git a/tests/test_tools.py b/tests/test_tools.py index d364feb9..3808537d 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -107,13 +107,16 @@ def test_check_and_build_global_config(test_inputs, expected, user_filesystem, m # remove the config file from home that came with user_filesystem os.remove(confile) mocker.patch("builtins.input", side_effect=test_inputs["user_inputs"]) - check_and_build_global_config() + actual_bool = check_and_build_global_config() try: with open(confile, "r") as f: actual = json.load(f) + expected_bool = True except FileNotFoundError: + expected_bool = False actual = None assert actual == expected + assert actual_bool == expected_bool def test_check_and_build_global_config_file_exists(user_filesystem, mocker): @@ -121,7 +124,8 @@ def test_check_and_build_global_config_file_exists(user_filesystem, mocker): os.chdir(user_filesystem[1]) confile = user_filesystem[0] / "diffpyconfig.json" expected = {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"} - check_and_build_global_config() + actual_bool = check_and_build_global_config() + assert actual_bool is True with open(confile, "r") as f: actual = json.load(f) assert actual == expected @@ -133,7 +137,8 @@ def test_check_and_build_global_config_skipped(user_filesystem, mocker): confile = user_filesystem[0] / "diffpyconfig.json" # remove the config file from home that came with user_filesystem os.remove(confile) - check_and_build_global_config(skip_config_creation=True) + actual_bool = check_and_build_global_config(skip_config_creation=True) + assert actual_bool is False assert not confile.exists() From 7b99d258409ab4c21e64235a1fc822ecedfd7c95 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 25 Dec 2024 08:26:19 -0500 Subject: [PATCH 295/445] update docs with new return value for create_config --- doc/source/examples/tools_example.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/source/examples/tools_example.rst b/doc/source/examples/tools_example.rst index 60746e6f..27de73b5 100644 --- a/doc/source/examples/tools_example.rst +++ b/doc/source/examples/tools_example.rst @@ -95,6 +95,10 @@ it will only run once. However, if you want to bypass this behavior, ``check_and_build_global_config()`` takes an optional boolean ``skip_config_creation`` parameter that could be set to ``True`` at runtime to override the config creation. +``check_and_build_global_config()`` returns ``True`` if the config file exists (whether it created it or not) +and ``False`` if the config file does not exist in the user's home allowing you to develop your own +workflow for handling missing config files after running it with ``skip_config_creation=True``. + I entered the wrong information in my config file so it always loads incorrect information, how do I fix that? -------------------------------------------------------------------------------------------------------------- From 7688cb4094eaf4ae106d031012b0b582525a86cb Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 25 Dec 2024 12:05:26 -0500 Subject: [PATCH 296/445] Use compact test comment style for each case under @pytest.mark.parametrize --- news/test-func-format-compact.rst | 23 ++++++++++++ tests/test_diffraction_objects.py | 59 +++++++++++-------------------- tests/test_transforms.py | 46 ++++++++++-------------- 3 files changed, 61 insertions(+), 67 deletions(-) create mode 100644 news/test-func-format-compact.rst diff --git a/news/test-func-format-compact.rst b/news/test-func-format-compact.rst new file mode 100644 index 00000000..ecb92ac0 --- /dev/null +++ b/news/test-func-format-compact.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* test comment format with compact style without extra line for each comment + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6fa843d8..9dee757d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -15,8 +15,7 @@ "do_args_1, do_args_2, expected_equality, wavelength_warning_expected", [ # Test when __eq__ returns True and False - # C1: Identical args, expect equality - ( + ( # C1: Identical args, expect equality { "name": "same", "scat_quantity": "x-ray", @@ -38,8 +37,7 @@ True, False, ), - # Different names, expect inequality - ( + ( # C2: Different names, expect inequality { "name": "something", "xtype": "tth", @@ -57,8 +55,7 @@ False, True, ), - # C2: One without wavelength, expect inequality - ( + ( # C3: One without wavelength, expect inequality { "wavelength": 0.71, "xtype": "tth", @@ -75,8 +72,7 @@ False, True, ), - # C3: Different wavelength values, expect inequality - ( + ( # C4: Different wavelength values, expect inequality { "wavelength": 0.71, "xtype": "tth", @@ -94,8 +90,7 @@ False, False, ), - # C4: Different scat_quantity, expect inequality - ( + ( # C5: Different scat_quantity, expect inequality { "scat_quantity": "x-ray", "xtype": "tth", @@ -113,8 +108,7 @@ False, True, ), - # C5: Different q xarray values, expect inequality - ( + ( # C6: Different q xarray values, expect inequality { "xtype": "q", "xarray": np.array([1.0, 2.0]), @@ -129,8 +123,7 @@ False, True, ), - # C6: Different metadata, expect inequality - ( + ( # C7: Different metadata, expect inequality { "xtype": "q", "xarray": np.empty(0), @@ -167,8 +160,8 @@ def test_diffraction_objects_equality( # Test whether on_xtype returns the correct xarray values. # C1: tth to tth, expect no change in xarray value # 1. "tth" provided, expect tth - # 2. "2theta" provided, expect tth ("tth", np.array([30, 60])), + # 2. "2theta" provided, expect tth ("2theta", np.array([30, 60])), # C2: "q" provided, expect q converted from tth ("q", np.array([0.51764, 1])), @@ -198,8 +191,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether scale_to() scales to the expected values - # C1: Same x-array and y-array with 2.1 offset, expect yarray shifted by 2.1 offset - ( + ( # C1: Same x-array and y-array with 2.1 offset, expect yarray shifted by 2.1 offset { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([2, 3, 4, 5, 6, 7]), @@ -220,8 +212,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - # C2: Same length x-arrays with exact x-value match - ( + ( # C2: Same length x-arrays with exact x-value match { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([10, 20, 25, 30, 60, 100]), @@ -242,8 +233,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - # C3: Same length x-arrays with approximate x-value match - ( + ( # C3: Same length x-arrays with approximate x-value match { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), "yarray": np.array([10, 20, 40, 60]), @@ -264,8 +254,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - # C4: Different x-array lengths with approximate x-value match - ( + ( # C4: Different x-array lengths with approximate x-value match { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), @@ -303,8 +292,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "org_do_args, target_do_args, scale_inputs", [ # Test expected errors produced from scale_to() with invalid inputs - # C1: none of q, tth, d, provided, expect ValueError - ( + ( # C1: none of q, tth, d, provided, expect ValueError { "xarray": np.array([0.1, 0.2, 0.3]), "yarray": np.array([1, 2, 3]), @@ -324,8 +312,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "offset": 0, }, ), - # C2: tth and d both provided, expect ValueErrort - ( + ( # C2: tth and d both provided, expect ValueErrort { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), @@ -366,8 +353,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "do_args, get_array_index_inputs, expected_index", [ # Test get_array_index() returns the expected index given xtype and value - # C1: Target value is in the xarray and xtype is identical, expect exact index match - ( + ( # C1: Target value is in the xarray and xtype is identical, expect exact index match { "wavelength": 4 * np.pi, "xarray": np.array([30.005, 60]), @@ -380,8 +366,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, [0], ), - # C2: Target value lies in the array, expect the (first) closest index - ( + ( # C2: Target value lies in the array, expect the (first) closest index { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -408,8 +393,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): [0], ), # C3: Target value out of the range, expect the closest index - # 1. Test with xtype of "q" - ( + ( # 1. Test with xtype of "q" { "wavelength": 4 * np.pi, "xarray": np.array([0.25, 0.5, 0.71]), @@ -422,8 +406,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, [0], ), - # 2. Test with xtype of "tth" - ( + ( # 2. Test with xtype of "tth" { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -488,8 +471,7 @@ def test_dump(tmp_path, mocker): "do_init_args, expected_do_dict, divide_by_zero_warning_expected", [ # Test __dict__ of DiffractionObject instance initialized with valid arguments - ( - # C1: Minimum arguments provided for init, expect all attributes set without None + ( # C1: Minimum arguments provided for init, expect all attributes set without None { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -518,8 +500,7 @@ def test_dump(tmp_path, mocker): }, True, ), - # C2: Initialize with an optional scat_quantity argument, expect non-empty string for scat_quantity - ( + ( # C2: Initialize with an optional scat_quantity argument, expect non-empty string for scat_quantity { "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), "yarray": np.array([1.0, 2.0, 3.0]), diff --git a/tests/test_transforms.py b/tests/test_transforms.py index ff202384..60f87c2e 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -16,14 +16,16 @@ # 2. No wavelength provided, expected empty array of tth and wavelength UserWarning (None, np.empty((0)), np.empty((0))), # C2: Use non-empty q values to compute tth with or without wavelength - # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning - ( + ( # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # 2. Wavelength provided, expect tth values of 2*arcsin(q) in degrees - (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), + ( # 2. Wavelength provided, expect tth values of 2*arcsin(q) in degrees + 4 * np.pi, + np.array([0, 1 / np.sqrt(2), 1.0]), + np.array([0, 90.0, 180.0]), + ), ], ) def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): @@ -39,14 +41,12 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): "wavelength, q, expected_error_type", [ # Test ValeuError in q to tth conversion with invalid two-theta values. - # C1: Invalid q values that result in tth > 180 degrees, expect ValueError - ( + ( # C1: Invalid q values that result in tth > 180 degrees, expect ValueError 4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), ValueError, ), - # C2: Wrong wavelength that results in tth > 180 degrees, expect ValueError - ( + ( # C2: Wrong wavelength that results in tth > 180 degrees, expect ValueError 100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), ValueError, @@ -69,14 +69,12 @@ def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wave # 2. No wavelength provided, expected empty array of q and wavelength UserWarning (4 * np.pi, np.array([]), np.array([])), # C2: Use non-empty tth values between 0-180 degrees to compute q, with or without wavelength - # 1. No wavelength provided, expect valid q values between 0-1 - ( + ( # 1. No wavelength provided, expect valid q values between 0-1 None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), - # 2. Wavelength provided, expect expected q values are sin15, sin30, sin45, sin60, sin90 - ( + ( # 2. Wavelength provided, expect expected q values are sin15, sin30, sin45, sin60, sin90 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), @@ -97,15 +95,13 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): "wavelength, tth, expected_error_type, expected_error_msg", [ # C1: Invalid tth value of > 180 degrees provided, with or without wavelength - # 1. No wavelength provided, expect two theta ValueError - ( + ( # 1. No wavelength provided, expect two theta ValueError None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # 2. Wavelength provided, expect two theta ValueError - ( + ( # 2. Wavelength provided, expect two theta ValueError 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), ValueError, @@ -125,14 +121,12 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): # C1: Empty q values, expect empty d values (np.array([]), np.array([]), False), # C2: - # 1. Valid q values, expect d values without warning - ( + ( # 1. Valid q values, expect d values without warning np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), False, ), - # 2. Valid q values containing 0, expect d values with divide by zero warning - ( + ( # 2. Valid q values containing 0, expect d values with divide by zero warning np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), True, @@ -180,8 +174,7 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): (4 * np.pi, np.array([]), np.array([]), False), # C3: User specified valid tth values between 0-180 degrees (without wavelength) (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), - # C4: User specified valid tth values between 0-180 degrees (with wavelength) - ( + ( # C4: User specified valid tth values between 0-180 degrees (with wavelength) 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), @@ -204,15 +197,13 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError - ( + ( # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError - ( + ( # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), ValueError, @@ -234,8 +225,7 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m (4 * np.pi, np.empty((0)), np.empty(0), False), # C3: Valid d values, no wavelength, expect valid and non-empty tth values (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), - # C4: Valid d values with wavelength, expect valid and non-empty thh values - ( + ( # C4: Valid d values with wavelength, expect valid and non-empty thh values 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), From 03241387b40ea89a3dc94df53b282ae480e7e057 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 17:09:50 +0000 Subject: [PATCH 297/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 60f87c2e..0396c420 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -225,7 +225,7 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m (4 * np.pi, np.empty((0)), np.empty(0), False), # C3: Valid d values, no wavelength, expect valid and non-empty tth values (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), - ( # C4: Valid d values with wavelength, expect valid and non-empty thh values + ( # C4: Valid d values with wavelength, expect valid and non-empty thh values 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), From 2a9e1c34dc414fc1572047724a8e595f004833ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 22:16:45 +0000 Subject: [PATCH 298/445] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tools.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 248230eb..b7c89b4c 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -4,7 +4,6 @@ from pathlib import Path - def clean_dict(obj): """Remove keys from the dictionary where the corresponding value is None. @@ -26,9 +25,7 @@ def clean_dict(obj): def _stringify(obj): - """ - Convert None to an empty string. - + """Convert None to an empty string. Parameters ---------- @@ -44,9 +41,8 @@ def _stringify(obj): def _load_config(file_path): - """ - Load configuration from a .json file. ->>>>>>> de55560eb525ef412c38bb31d21d43d9b170d3f6 + """Load configuration from a .json file. >>>>>>> + de55560eb525ef412c38bb31d21d43d9b170d3f6. Parameters ---------- From 32c00188343d035dd4625541adf4d338cfd38141 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 25 Dec 2024 17:19:37 -0500 Subject: [PATCH 299/445] Remove >>>> extra due to merge conflict in tools --- src/diffpy/utils/tools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index b7c89b4c..f2f4e136 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -41,8 +41,7 @@ def _stringify(obj): def _load_config(file_path): - """Load configuration from a .json file. >>>>>>> - de55560eb525ef412c38bb31d21d43d9b170d3f6. + """Load configuration from a .json file. Parameters ---------- From 209a1945c33950ddfff1d1eaf8e086c16695995a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 25 Dec 2024 23:10:38 -0500 Subject: [PATCH 300/445] Add separate docstring for @property methods --- src/diffpy/utils/diffraction_objects.py | 37 ++++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 2922cdbd..a1b1bd13 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -46,12 +46,6 @@ class DiffractionObject: Attributes ---------- - all_arrays : ndarray - The array containing the quantity of q, tth, d values. - input_xtype : str - The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES} - _uuid : uuid - The unique identifier for the diffraction object. scat_quantity : str The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". wavelength : float @@ -284,6 +278,20 @@ def __rtruediv__(self, other): @property def all_arrays(self): + """The array containing `xarray` values in q, d, tth, and `yarray`. + + Returns + ------- + ndarray + The 2D matrix containing the `xarray` objects values and `yarray`. + + Examples + -------- + >>> my_do.all_arrays[:, 0] # yarray + >>> my_do.all_arrays[:, 1] # `xarray` in q + >>> my_do.all_arrays[:, 2] # `xarray` in tth + >>> my_do.all_arrays[:, 3] # `xarray` in d + """ return self._all_arrays @all_arrays.setter @@ -292,6 +300,13 @@ def all_arrays(self, _): @property def input_xtype(self): + """The type of the independent variable in `xarray`. + + Returns + ------- + str + The type of `xarray`, which must be one of {*XQUANTITIES}. + """ return self._input_xtype @input_xtype.setter @@ -300,6 +315,13 @@ def input_xtype(self, _): @property def uuid(self): + """The unique identifier for the DiffractionObject instance. + + Returns + ------- + uuid + The unique identifier of the DiffractionObject instance. + """ return self._uuid @uuid.setter @@ -319,7 +341,8 @@ def get_array_index(self, value, xtype=None): Returns ------- - the index of the value in the array + list + The list containing the index of the closest value in the array. """ xtype = self._input_xtype From 5824250c02fca1e067598615b48418eb24a48504 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 25 Dec 2024 23:15:40 -0500 Subject: [PATCH 301/445] Improve docstring for all_arrays --- src/diffpy/utils/diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a1b1bd13..684c71b7 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -278,12 +278,12 @@ def __rtruediv__(self, other): @property def all_arrays(self): - """The array containing `xarray` values in q, d, tth, and `yarray`. + """The 2D array containing `xarray` and `yarray` values. Returns ------- ndarray - The 2D matrix containing the `xarray` objects values and `yarray`. + The 2D array containing the `xarray` values in q, tth, d, and `yarray`. Examples -------- From 2f293c09999cc1b8617cd39b2c9a0b07f1b2add3 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 25 Dec 2024 23:16:53 -0500 Subject: [PATCH 302/445] Remove extra singl quote around comment --- src/diffpy/utils/diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 684c71b7..286154d7 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -288,9 +288,9 @@ def all_arrays(self): Examples -------- >>> my_do.all_arrays[:, 0] # yarray - >>> my_do.all_arrays[:, 1] # `xarray` in q - >>> my_do.all_arrays[:, 2] # `xarray` in tth - >>> my_do.all_arrays[:, 3] # `xarray` in d + >>> my_do.all_arrays[:, 1] # xarray in q + >>> my_do.all_arrays[:, 2] # xarray in tth + >>> my_do.all_arrays[:, 3] # xarray in d """ return self._all_arrays From 5180eeb379535d6a803f627af819e6b5eb36a5b3 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 11:47:04 -0500 Subject: [PATCH 303/445] initial commit --- news/mu.rst | 23 +++++++++++++++++++++++ requirements/conda.txt | 1 + requirements/pip.txt | 1 + src/diffpy/utils/tools.py | 25 +++++++++++++++++++++++++ tests/test_tools.py | 8 +++++++- 5 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 news/mu.rst diff --git a/news/mu.rst b/news/mu.rst new file mode 100644 index 00000000..bbf9cb1a --- /dev/null +++ b/news/mu.rst @@ -0,0 +1,23 @@ +**Added:** + +* function to compute x-ray attenuation coefficient (mu) using XrayDB + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/requirements/conda.txt b/requirements/conda.txt index 24ce15ab..b26085a8 100644 --- a/requirements/conda.txt +++ b/requirements/conda.txt @@ -1 +1,2 @@ numpy +xraydb diff --git a/requirements/pip.txt b/requirements/pip.txt index 24ce15ab..b26085a8 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1 +1,2 @@ numpy +xraydb diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index a6c30ed3..ca965e22 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -3,6 +3,8 @@ from copy import copy from pathlib import Path +from xraydb import material_mu + def _stringify(obj): """ @@ -131,3 +133,26 @@ def get_package_info(package_names, metadata=None): pkg_info.update({package: importlib.metadata.version(package)}) metadata["package_info"] = pkg_info return metadata + + +def compute_mu_using_xraydb(sample, energy, density=None): + """ + compute mu using the XrayDB database + + Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu + + Parameters + ---------- + sample str + the chemical formula or the name of the material + energy float + the energy in eV + density float or None + material density in gr/cm^3 + + Returns + ------- + the attenuation coefficient mu in mm^{-1} + """ + mu = material_mu(sample, energy, density=density, kind="total") / 10 + return mu diff --git a/tests/test_tools.py b/tests/test_tools.py index cf730ddd..62b1673c 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -5,7 +5,7 @@ import pytest -from diffpy.utils.tools import get_package_info, get_user_info +from diffpy.utils.tools import compute_mu_using_xraydb, get_package_info, get_user_info # def _setup_dirs(monkeypatch, user_filesystem): # home_dir, cwd_dir = user_filesystem.home_dir, user_filesystem.cwd_dir @@ -189,3 +189,9 @@ def test_get_package_info(monkeypatch, inputs, expected): ) actual_metadata = get_package_info(inputs[0], metadata=inputs[1]) assert actual_metadata == expected + + +def test_compute_mu_using_xraydb(): + sample, energy, density = "ZrO2", 17445.362740959618, 1.009 + actual_mu = compute_mu_using_xraydb(sample, energy, density=density) + assert actual_mu == pytest.approx(1.252, rel=1e-4, abs=0.1) From 5ff2c2312de6d4eab46c201327053f86cd64e829 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 16:49:30 +0000 Subject: [PATCH 304/445] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index ca965e22..2440abef 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -136,8 +136,7 @@ def get_package_info(package_names, metadata=None): def compute_mu_using_xraydb(sample, energy, density=None): - """ - compute mu using the XrayDB database + """Compute mu using the XrayDB database. Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu From ceba7f9dead7867476bdba5482caacfe77810de0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 26 Dec 2024 14:49:52 -0500 Subject: [PATCH 305/445] Fix docstring for all_arrays per sbilling pr review --- src/diffpy/utils/diffraction_objects.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 286154d7..83d7f1cf 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -283,10 +283,13 @@ def all_arrays(self): Returns ------- ndarray - The 2D array containing the `xarray` values in q, tth, d, and `yarray`. + The shape (len(data), 4) 2D array with columns containing the `yarray` (intensity) + and the `xarray` values in q, tth, d, and `yarray`. Examples -------- + To access specific arrays individually, use these slices: + >>> my_do.all_arrays[:, 0] # yarray >>> my_do.all_arrays[:, 1] # xarray in q >>> my_do.all_arrays[:, 2] # xarray in tth From 2d1c104b4197b20c94c21d5fef6683f65f876d00 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 19:50:06 +0000 Subject: [PATCH 306/445] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 83d7f1cf..87ed9d13 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -289,7 +289,7 @@ def all_arrays(self): Examples -------- To access specific arrays individually, use these slices: - + >>> my_do.all_arrays[:, 0] # yarray >>> my_do.all_arrays[:, 1] # xarray in q >>> my_do.all_arrays[:, 2] # xarray in tth From 182622167e58bc704f15efefa890e021f2981449 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 14:58:40 -0500 Subject: [PATCH 307/445] add packing fraction, add more tests --- src/diffpy/utils/tools.py | 24 +++++++++++++++--------- tests/test_tools.py | 26 ++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index ca965e22..3674401a 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -135,24 +135,30 @@ def get_package_info(package_names, metadata=None): return metadata -def compute_mu_using_xraydb(sample, energy, density=None): +def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1): """ - compute mu using the XrayDB database + Compute the attenuation coefficient (mu) using the XrayDB database + This function calculates mu based on the sample composition and energy. + If density is not provided, a standard reference density (e.g., 0.987 g/cm^3 for H2O) is used. + User can provide either a measured density or an estimated packing fraction (with a standard density). + It is recommended to specify the density, especially for materials like ZrO2, where it can vary. Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu Parameters ---------- - sample str - the chemical formula or the name of the material - energy float - the energy in eV - density float or None - material density in gr/cm^3 + sample_composition: str + The chemical formula or the name of the material + energy: float + The energy in eV + density: float, optional, Default is None + The mass density of the packed powder/sample in gr/cm^3. If None, a standard density from XrayDB is used. + packing_fraction: float, optional, Default is 1 + The fraction of sample in the capillary (between 0 and 1) Returns ------- the attenuation coefficient mu in mm^{-1} """ - mu = material_mu(sample, energy, density=density, kind="total") / 10 + mu = material_mu(sample_composition, energy, density=density, kind="total") * packing_fraction / 10 return mu diff --git a/tests/test_tools.py b/tests/test_tools.py index 62b1673c..dd70d347 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -191,7 +191,25 @@ def test_get_package_info(monkeypatch, inputs, expected): assert actual_metadata == expected -def test_compute_mu_using_xraydb(): - sample, energy, density = "ZrO2", 17445.362740959618, 1.009 - actual_mu = compute_mu_using_xraydb(sample, energy, density=density) - assert actual_mu == pytest.approx(1.252, rel=1e-4, abs=0.1) +params_mu = [ + # C1: user didn't specify density or packing fraction + ({"sample_composition": "H2O", "energy": 10000, "density": None, "packing_fraction": 1}, 0.5330), + # C2: user specified packing fraction only + ({"sample_composition": "H2O", "energy": 10000, "density": None, "packing_fraction": 0.5}, 0.2665), + # C3: user specified density only + ({"sample_composition": "H2O", "energy": 10000, "density": 0.997, "packing_fraction": 1}, 0.5330), + ({"sample_composition": "H2O", "energy": 10000, "density": 0.4985, "packing_fraction": 1}, 0.2665), + # C4: user specified a standard density and a packing fraction + ({"sample_composition": "H2O", "energy": 10000, "density": 0.997, "packing_fraction": 0.5}, 0.2665), +] + + +@pytest.mark.parametrize("inputs, expected", params_mu) +def test_compute_mu_using_xraydb(inputs, expected): + actual_mu = compute_mu_using_xraydb( + inputs["sample_composition"], + inputs["energy"], + density=inputs["density"], + packing_fraction=inputs["packing_fraction"], + ) + assert actual_mu == pytest.approx(expected, rel=0.01, abs=0.1) From be7c275a66ed254e82f6c772fbee2d21bcea13c2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 20:00:29 +0000 Subject: [PATCH 308/445] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/tools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3674401a..611048f1 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -136,8 +136,7 @@ def get_package_info(package_names, metadata=None): def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1): - """ - Compute the attenuation coefficient (mu) using the XrayDB database + """Compute the attenuation coefficient (mu) using the XrayDB database. This function calculates mu based on the sample composition and energy. If density is not provided, a standard reference density (e.g., 0.987 g/cm^3 for H2O) is used. From 9e4bc0dc1173978544be084e80ee182311587ccc Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 15:27:55 -0500 Subject: [PATCH 309/445] refine docstring and test comments --- src/diffpy/utils/tools.py | 24 +++++++------- tests/test_tools.py | 70 ++++++++++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3674401a..9b78dec2 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -136,29 +136,29 @@ def get_package_info(package_names, metadata=None): def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1): - """ - Compute the attenuation coefficient (mu) using the XrayDB database + """Compute the attenuation coefficient (mu) using the XrayDB database. - This function calculates mu based on the sample composition and energy. + Computes mu based on the sample composition and energy. If density is not provided, a standard reference density (e.g., 0.987 g/cm^3 for H2O) is used. User can provide either a measured density or an estimated packing fraction (with a standard density). It is recommended to specify the density, especially for materials like ZrO2, where it can vary. - Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu + Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu. Parameters ---------- - sample_composition: str - The chemical formula or the name of the material - energy: float - The energy in eV - density: float, optional, Default is None + sample_composition : str + The chemical formula or the name of the material. + energy : float + The energy in eV. + density : float, optional, Default is None The mass density of the packed powder/sample in gr/cm^3. If None, a standard density from XrayDB is used. - packing_fraction: float, optional, Default is 1 - The fraction of sample in the capillary (between 0 and 1) + packing_fraction : float, optional, Default is 1 + The fraction of sample in the capillary (between 0 and 1). Returns ------- - the attenuation coefficient mu in mm^{-1} + mu : float + The attenuation coefficient mu in mm^{-1}. """ mu = material_mu(sample_composition, energy, density=density, kind="total") * packing_fraction / 10 return mu diff --git a/tests/test_tools.py b/tests/test_tools.py index dd70d347..28850985 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -191,25 +191,63 @@ def test_get_package_info(monkeypatch, inputs, expected): assert actual_metadata == expected -params_mu = [ - # C1: user didn't specify density or packing fraction - ({"sample_composition": "H2O", "energy": 10000, "density": None, "packing_fraction": 1}, 0.5330), - # C2: user specified packing fraction only - ({"sample_composition": "H2O", "energy": 10000, "density": None, "packing_fraction": 0.5}, 0.2665), - # C3: user specified density only - ({"sample_composition": "H2O", "energy": 10000, "density": 0.997, "packing_fraction": 1}, 0.5330), - ({"sample_composition": "H2O", "energy": 10000, "density": 0.4985, "packing_fraction": 1}, 0.2665), - # C4: user specified a standard density and a packing fraction - ({"sample_composition": "H2O", "energy": 10000, "density": 0.997, "packing_fraction": 0.5}, 0.2665), -] - - -@pytest.mark.parametrize("inputs, expected", params_mu) -def test_compute_mu_using_xraydb(inputs, expected): +@pytest.mark.parametrize( + "inputs, expected_mu", + [ + # Test whether the function returns the correct mu + ( # C1: No density or packing fraction provided, expect to compute mu based on standard density + { + "sample_composition": "H2O", + "energy": 10000, + "density": None, + "packing_fraction": 1, + }, + 0.5330, + ), + ( # C2: Packing fraction (=0.5) provided only, expect to return half of mu based on standard density + { + "sample_composition": "H2O", + "energy": 10000, + "density": None, + "packing_fraction": 0.5, + }, + 0.2665, + ), + ( # C3: Density provided only, expect to compute mu based on density + # 1. Standard density provided, expect to return the same mu as C1 + { + "sample_composition": "H2O", + "energy": 10000, + "density": 0.997, + "packing_fraction": 1, + }, + 0.5330, + ), + ( # 2. Lower density for H2O (half of standard), expect to return half of mu based on standard density + { + "sample_composition": "H2O", + "energy": 10000, + "density": 0.4985, + "packing_fraction": 1, + }, + 0.2665, + ), + ( # C4: Both standard density and packing fraction are provided, expect to compute the same mu as C2 + { + "sample_composition": "H2O", + "energy": 10000, + "density": 0.997, + "packing_fraction": 0.5, + }, + 0.2665, + ), + ], +) +def test_compute_mu_using_xraydb(inputs, expected_mu): actual_mu = compute_mu_using_xraydb( inputs["sample_composition"], inputs["energy"], density=inputs["density"], packing_fraction=inputs["packing_fraction"], ) - assert actual_mu == pytest.approx(expected, rel=0.01, abs=0.1) + assert actual_mu == pytest.approx(expected_mu, rel=0.01, abs=0.1) From b198d66ae05bce8d6e01a5223e625bc58fafc0fb Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 26 Dec 2024 15:33:56 -0500 Subject: [PATCH 310/445] Remove extra mention of yarray --- src/diffpy/utils/diffraction_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 83d7f1cf..6118f46d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -284,12 +284,12 @@ def all_arrays(self): ------- ndarray The shape (len(data), 4) 2D array with columns containing the `yarray` (intensity) - and the `xarray` values in q, tth, d, and `yarray`. + and the `xarray` values in q, tth, and d. Examples -------- To access specific arrays individually, use these slices: - + >>> my_do.all_arrays[:, 0] # yarray >>> my_do.all_arrays[:, 1] # xarray in q >>> my_do.all_arrays[:, 2] # xarray in tth From 56e7e83b7beec8dde72cf65bff2441dac0c499c5 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 16:24:07 -0500 Subject: [PATCH 311/445] improve comment --- tests/test_diffraction_objects.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9dee757d..f3091561 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -191,7 +191,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether scale_to() scales to the expected values - ( # C1: Same x-array and y-array with 2.1 offset, expect yarray shifted by 2.1 offset + ( # C1: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([2, 3, 4, 5, 6, 7]), @@ -212,7 +212,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - ( # C2: Same length x-arrays with exact x-value match + ( # C2: Same x-arrays, x-value matches at tth=60, expect y-array to divide by 10 { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([10, 20, 25, 30, 60, 100]), @@ -233,7 +233,10 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - ( # C3: Same length x-arrays with approximate x-value match + ( # C3: Different x-arrays with same length, + # x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1) + # for original and target diffraction objects, + # expect y-array to divide by 10 { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), "yarray": np.array([10, 20, 40, 60]), @@ -254,7 +257,10 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - ( # C4: Different x-array lengths with approximate x-value match + ( # C4: Different x-array lengths + # x-value has closest matches at tth=61 (y=50) and tth=62 (y=5), + # for original and target diffraction objects, + # expect y-array to divide by 10 { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), @@ -273,7 +279,6 @@ def test_init_invalid_xtype(): "d": None, "offset": 0, }, - # C5: Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), ], From 0a3081877eaaba0ae1574e01e71f9393cb4a64a5 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 16:26:31 -0500 Subject: [PATCH 312/445] no news added --- news/scaleto-comments.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scaleto-comments.rst diff --git a/news/scaleto-comments.rst b/news/scaleto-comments.rst new file mode 100644 index 00000000..73d0f042 --- /dev/null +++ b/news/scaleto-comments.rst @@ -0,0 +1,23 @@ +**Added:** + +* no news added - make test comments for `test_scale_to` more readable + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From faf0e2fcd29c112c353e55c4dfca549fc13dc8ad Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 26 Dec 2024 21:29:05 -0500 Subject: [PATCH 313/445] return type of method in to return integer instead of list --- news/get-index.rst | 23 +++++++++++++++++++++++ src/diffpy/utils/diffraction_objects.py | 24 ++++++++++++------------ tests/test_diffraction_objects.py | 14 +++++++------- 3 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 news/get-index.rst diff --git a/news/get-index.rst b/news/get-index.rst new file mode 100644 index 00000000..eef8c787 --- /dev/null +++ b/news/get-index.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* return type of `get_array_index` method in `DiffractionObject` to return integer instead of list + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6118f46d..25e3e28c 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -331,29 +331,29 @@ def uuid(self): def uuid(self, _): raise AttributeError(_setter_wmsg("uuid")) - def get_array_index(self, value, xtype=None): + def get_array_index(self, xtype, xvalue): """Return the index of the closest value in the array associated with - the specified xtype. + the specified xtype and the value provided. Parameters ---------- - xtype str - the xtype used to access the array - value float - the target value to search for + xtype : str + The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. + xvalue : float + The value of the xtype to find the closest index for. Returns ------- - list - The list containing the index of the closest value in the array. + int + The index of the closest value in the array associated with the specified xtype and the value provided. """ xtype = self._input_xtype - array = self.on_xtype(xtype)[0] - if len(array) == 0: + xarray = self.on_xtype(xtype)[0] + if len(xarray) == 0: raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.") - i = (np.abs(array - value)).argmin() - return i + index = (np.abs(xarray - xvalue)).argmin() + return index def _set_arrays(self, xarray, yarray, xtype): self._all_arrays = np.empty(shape=(len(xarray), 4)) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 56478e72..f5a88f7a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -364,7 +364,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "xtype": "tth", "value": 30.005, }, - [0], + 0, ), ( # C2: Target value lies in the array, expect the (first) closest index { @@ -377,7 +377,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "xtype": "tth", "value": 45, }, - [0], + 0, ), ( { @@ -390,7 +390,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "xtype": "q", "value": 0.25, }, - [0], + 0, ), # C3: Target value out of the range, expect the closest index ( # 1. Test with xtype of "q" @@ -404,7 +404,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "xtype": "q", "value": 0.1, }, - [0], + 0, ), ( # 2. Test with xtype of "tth" { @@ -417,20 +417,20 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): "xtype": "tth", "value": 63, }, - [1], + 1, ), ], ) def test_get_array_index(do_args, get_array_index_inputs, expected_index): do = DiffractionObject(**do_args) - actual_index = do.get_array_index(get_array_index_inputs["value"], get_array_index_inputs["xtype"]) + actual_index = do.get_array_index(get_array_index_inputs["xtype"], get_array_index_inputs["value"]) assert actual_index == expected_index def test_get_array_index_bad(): do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth") with pytest.raises(ValueError, match=re.escape("The 'tth' array is empty. Please ensure it is initialized.")): - do.get_array_index(value=30) + do.get_array_index(xtype="tth", xvalue=30) def test_dump(tmp_path, mocker): From a2a43a091e34b350d697531c221be59ab7a90f32 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 22:10:13 -0500 Subject: [PATCH 314/445] edit test comments --- tests/test_diffraction_objects.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index f3091561..441ae2dd 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -212,7 +212,10 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - ( # C2: Same x-arrays, x-value matches at tth=60, expect y-array to divide by 10 + ( # C2: Same x-arrays + # x-value has exact matches at tth=60 (y=60) and tth=60 (y=6), + # for original and target diffraction objects, + # expect y-array to multiply by 6/60=1/10 { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([10, 20, 25, 30, 60, 100]), @@ -236,7 +239,7 @@ def test_init_invalid_xtype(): ( # C3: Different x-arrays with same length, # x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1) # for original and target diffraction objects, - # expect y-array to divide by 10 + # expect y-array to multiply by 1/10 { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), "yarray": np.array([10, 20, 40, 60]), @@ -260,7 +263,7 @@ def test_init_invalid_xtype(): ( # C4: Different x-array lengths # x-value has closest matches at tth=61 (y=50) and tth=62 (y=5), # for original and target diffraction objects, - # expect y-array to divide by 10 + # expect y-array to multiply by 5/50=1/10 { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), From 5fc00b2a21878140fa0283f691f8527c682cb6cb Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 22:11:01 -0500 Subject: [PATCH 315/445] edit test comments --- tests/test_diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 441ae2dd..e5c24806 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -215,7 +215,7 @@ def test_init_invalid_xtype(): ( # C2: Same x-arrays # x-value has exact matches at tth=60 (y=60) and tth=60 (y=6), # for original and target diffraction objects, - # expect y-array to multiply by 6/60=1/10 + # expect original y-array to multiply by 6/60=1/10 { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([10, 20, 25, 30, 60, 100]), @@ -239,7 +239,7 @@ def test_init_invalid_xtype(): ( # C3: Different x-arrays with same length, # x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1) # for original and target diffraction objects, - # expect y-array to multiply by 1/10 + # expect original y-array to multiply by 1/10 { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), "yarray": np.array([10, 20, 40, 60]), @@ -263,7 +263,7 @@ def test_init_invalid_xtype(): ( # C4: Different x-array lengths # x-value has closest matches at tth=61 (y=50) and tth=62 (y=5), # for original and target diffraction objects, - # expect y-array to multiply by 5/50=1/10 + # expect original y-array to multiply by 5/50=1/10 { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), From d5fd01f4e740f73d6b2ca9b0ffb3bcc1d26f7329 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 26 Dec 2024 22:11:57 -0500 Subject: [PATCH 316/445] edit test comments --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index e5c24806..861bacbe 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -190,7 +190,7 @@ def test_init_invalid_xtype(): @pytest.mark.parametrize( "org_do_args, target_do_args, scale_inputs, expected", [ - # Test whether scale_to() scales to the expected values + # Test whether the original y-array is scaled as expected ( # C1: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 { "xarray": np.array([10, 15, 25, 30, 60, 140]), From e5c1f3f34b2d19134c0aab27417cc29e7cf432a4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 00:16:57 -0500 Subject: [PATCH 317/445] Add news and add empty array case for init DiffractionObject --- news/valid-empty-do.rst | 23 ++++++++++ tests/test_diffraction_objects.py | 70 +++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 news/valid-empty-do.rst diff --git a/news/valid-empty-do.rst b/news/valid-empty-do.rst new file mode 100644 index 00000000..74b840b0 --- /dev/null +++ b/news/valid-empty-do.rst @@ -0,0 +1,23 @@ +**Added:** + +* unit tests for initializing DiffractionObject object with empty xarray and yarray array + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 56478e72..e1147520 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -468,10 +468,61 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( - "do_init_args, expected_do_dict, divide_by_zero_warning_expected", + "do_init_args, expected_do_dict, divide_by_zero_warning_expected, wavelength_warning_expected", [ # Test __dict__ of DiffractionObject instance initialized with valid arguments - ( # C1: Minimum arguments provided for init, expect all attributes set without None + ( # C1: Instantiate DO with empty arrays, expect it to be a valid DO, but with everything empty + { + "xarray": np.empty(0), + "yarray": np.empty(0), + "xtype": "tth", + }, + { + "_all_arrays": np.array([]), + "_input_xtype": "tth", + "metadata": {}, + "name": "", + "scat_quantity": "", + "qmin": np.float64(np.inf), + "qmax": np.float64(0.0), + "tthmin": np.float64(np.inf), + "tthmax": np.float64(0.0), + "dmin": np.float64(np.inf), + "dmax": np.float64(0.0), + "wavelength": None, + }, + False, + True, + ), + ( # C2: Instantiate just DO with empty array like in C1 but with wavelength, xtype, name, and metadata + # expect a valid DO with empty arrays, but with some non-array attributes + { + "xarray": np.empty(0), + "yarray": np.empty(0), + "xtype": "tth", + "name": "test_name", + "wavelength": 1.54, + "metadata": {"item_1": "1", "item_2": "2"}, + }, + { + "_all_arrays": np.array([]), + "_input_xtype": "tth", + "metadata": {"item_1": "1", "item_2": "2"}, + "name": "test_name", + "scat_quantity": "", + "qmin": np.float64(np.inf), + "qmax": np.float64(0.0), + "tthmin": np.float64(np.inf), + "tthmax": np.float64(0.0), + "dmin": np.float64(np.inf), + "dmax": np.float64(0.0), + "wavelength": 1.54, + }, + False, + False, + ), + ( # C3: Minimum arguments provided for init with non-empty values for xarray and yarray and wavelength + # expect all attributes set without None { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -499,8 +550,9 @@ def test_dump(tmp_path, mocker): "wavelength": 4.0 * np.pi, }, True, + False, ), - ( # C2: Initialize with an optional scat_quantity argument, expect non-empty string for scat_quantity + ( # C4: Same as C3, but with an optional scat_quantity argument, expect non-empty string for scat_quantity { "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -529,13 +581,23 @@ def test_dump(tmp_path, mocker): "wavelength": 4.0 * np.pi, }, False, + False, ), ], ) -def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expected): +def test_init_valid( + do_init_args, + expected_do_dict, + divide_by_zero_warning_expected, + wavelength_warning_expected, + wavelength_warning_msg, +): if divide_by_zero_warning_expected: with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): actual_do_dict = DiffractionObject(**do_init_args).__dict__ + elif wavelength_warning_expected: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + actual_do_dict = DiffractionObject(**do_init_args).__dict__ else: actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( From b865a8e29ab54f0f81f29de554c86d7ffe9f7117 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 00:18:13 -0500 Subject: [PATCH 318/445] Clarify language for news --- news/valid-empty-do.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/valid-empty-do.rst b/news/valid-empty-do.rst index 74b840b0..dfd98716 100644 --- a/news/valid-empty-do.rst +++ b/news/valid-empty-do.rst @@ -1,6 +1,6 @@ **Added:** -* unit tests for initializing DiffractionObject object with empty xarray and yarray array +* unit tests for initializing DiffractionObject with empty array in xarray and yarray **Changed:** From 07938c9aaff79e628fd4846ac9f053ad62a74781 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 27 Dec 2024 10:01:20 -0500 Subject: [PATCH 319/445] offset test case goes to the end --- tests/test_diffraction_objects.py | 48 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 861bacbe..d76f248a 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -191,28 +191,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether the original y-array is scaled as expected - ( # C1: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 - { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([2, 3, 4, 5, 6, 7]), - "xtype": "tth", - "wavelength": 2 * np.pi, - }, - { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([2, 3, 4, 5, 6, 7]), - "xtype": "tth", - "wavelength": 2 * np.pi, - }, - { - "q": None, - "tth": 60, - "d": None, - "offset": 2.1, - }, - {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, - ), - ( # C2: Same x-arrays + ( # C1: Same x-arrays # x-value has exact matches at tth=60 (y=60) and tth=60 (y=6), # for original and target diffraction objects, # expect original y-array to multiply by 6/60=1/10 @@ -236,7 +215,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - ( # C3: Different x-arrays with same length, + ( # C2: Different x-arrays with same length, # x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1) # for original and target diffraction objects, # expect original y-array to multiply by 1/10 @@ -260,7 +239,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - ( # C4: Different x-array lengths + ( # C3: Different x-array lengths # x-value has closest matches at tth=61 (y=50) and tth=62 (y=5), # for original and target diffraction objects, # expect original y-array to multiply by 5/50=1/10 @@ -284,6 +263,27 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), + ( # C4: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": None, + "offset": 2.1, + }, + {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, + ), ], ) def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): From 7db3a4f44f444c4e6d84c2da799b7de94af9567d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:40:34 -0500 Subject: [PATCH 320/445] Refactor __eq__ function for DO --- src/diffpy/utils/diffraction_objects.py | 94 +++++++++++++------------ 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 25e3e28c..316758d0 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -14,9 +14,15 @@ XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] -x_grid_emsg = ( - "objects are not on the same x-grid. You may add them using the self.add method " - "and specifying how to handle the mismatch." +x_grid_length_mismatch_emsg = ( + "The two objects have different x-array lengths. " + "Please ensure the length of the x-value during initialization is identical." +) + +invalid_add_type_emsg = ( + "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " + "Please rerun by adding another DiffractionObject instance or a scalar value. " + "e.g., my_do_1 + my_do_2 or my_do + 10" ) @@ -169,32 +175,44 @@ def __eq__(self, other): return True def __add__(self, other): - summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - summed.on_tth[1] = self.on_tth[1] + other - summed.on_q[1] = self.on_q[1] + other - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to sum two DiffractionObject objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] - summed.on_q[1] = self.on_q[1] + other.on_q[1] - return summed + """Add a scalar value or another DiffractionObject to the xarrays of + the DiffractionObject. - def __radd__(self, other): - summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - summed.on_tth[1] = self.on_tth[1] + other - summed.on_q[1] = self.on_q[1] + other - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to sum two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + Parameters + ---------- + other : DiffractionObject or int or float + The object to add to the current DiffractionObject. If `other` is a scalar value, + it will be added to all xarrays. The length of the xarrays must match if `other` is + an instance of DiffractionObject. + + Returns + ------- + DiffractionObject + The new and deep-copied DiffractionObject instance after adding values to the xarrays. + + Raises + ------ + ValueError + Raised when the length of the xarrays of the two DiffractionObject instances do not match. + TypeError + Raised when the type of `other` is not an instance of DiffractionObject, int, or float. + """ + summed_do = deepcopy(self) + # Add scalar value to all xarrays by broadcasting + if isinstance(other, (int, float)): + summed_do._all_arrays[:, 1] += other + summed_do._all_arrays[:, 2] += other + summed_do._all_arrays[:, 3] += other + # Add xarrays of two DiffractionObject instances + elif isinstance(other, DiffractionObject): + if len(self.on_tth()[0]) != len(other.on_tth()[0]): + raise ValueError(x_grid_length_mismatch_emsg) + summed_do._all_arrays[:, 1] += other.on_q()[0] + summed_do._all_arrays[:, 2] += other.on_tth()[0] + summed_do._all_arrays[:, 3] += other.on_d()[0] else: - summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] - summed.on_q[1] = self.on_q[1] + other.on_q[1] - return summed + raise TypeError(invalid_add_type_emsg) + return summed_do def __sub__(self, other): subtracted = deepcopy(self) @@ -204,7 +222,7 @@ def __sub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] subtracted.on_q[1] = self.on_q[1] - other.on_q[1] @@ -218,7 +236,7 @@ def __rsub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] subtracted.on_q[1] = other.on_q[1] - self.on_q[1] @@ -232,19 +250,7 @@ def __mul__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied - - def __rmul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -258,7 +264,7 @@ def __truediv__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] divided.on_q[1] = self.on_q[1] / other.on_q[1] @@ -270,7 +276,7 @@ def __rtruediv__(self, other): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] divided.on_q[1] = other.on_q[1] / self.on_q[1] From 3d4841b75bd85767678cfdb19320bd8ada213558 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:41:17 -0500 Subject: [PATCH 321/445] Add __eq__ news and test --- news/add-operations-tests.rst | 23 +++++++++++ tests/conftest.py | 17 ++++++++ tests/test_diffraction_objects.py | 65 +++++++++++++++++++++++++++++-- 3 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 news/add-operations-tests.rst diff --git a/news/add-operations-tests.rst b/news/add-operations-tests.rst new file mode 100644 index 00000000..e59edbd5 --- /dev/null +++ b/news/add-operations-tests.rst @@ -0,0 +1,23 @@ +**Added:** + +* unit tests for __add__ operation for DiffractionObject + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/conftest.py b/tests/conftest.py index 7f8de460..5eaaa902 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -63,3 +63,20 @@ def invalid_q_or_d_or_wavelength_error_msg(): "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) + + +@pytest.fixture +def invalid_add_type_error_msg(): + return ( + "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " + "Please rerun by adding another DiffractionObject instance or a scalar value. " + "e.g., my_do_1 + my_do_2 or my_do + 10" + ) + + +@pytest.fixture +def x_grid_size_mismatch_error_msg(): + return ( + "The two objects have different x-array lengths. " + "Please ensure the length of the x-value during initialization is identical." + ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 71271931..9675ad7f 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -155,7 +155,7 @@ def test_diffraction_objects_equality( @pytest.mark.parametrize( - "xtype, expected_xarray", + "xtype, expected_all_arrays", [ # Test whether on_xtype returns the correct xarray values. # C1: tth to tth, expect no change in xarray value @@ -169,10 +169,10 @@ def test_diffraction_objects_equality( ("d", np.array([12.13818, 6.28319])), ], ) -def test_on_xtype(xtype, expected_xarray, do_minimal_tth): +def test_on_xtype(xtype, expected_all_arrays, do_minimal_tth): do = do_minimal_tth actual_xrray, actual_yarray = do.on_xtype(xtype) - assert np.allclose(actual_xrray, expected_xarray) + assert np.allclose(actual_xrray, expected_all_arrays) assert np.allclose(actual_yarray, np.array([1, 2])) @@ -702,3 +702,62 @@ def test_copy_object(do_minimal): do_copy = do.copy() assert do == do_copy assert id(do) != id(do_copy) + + +@pytest.mark.parametrize( + "starting_all_arrays, scalar_value, expected_all_arrays", + [ + # Test scalar addition to xarray values (q, tth, d) and expect no change to yarray values + ( # C1: Add integer of 5, expect xarray to increase by by 5 + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 5, + np.array([[1.0, 5.51763809, 35.0, 17.13818192], [2.0, 6.0, 65.0, 11.28318531]]), + ), + ( # C2: Add float of 5.1, expect xarray to be added by 5.1 + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 5.1, + np.array([[1.0, 5.61763809, 35.1, 17.23818192], [2.0, 6.1, 65.1, 11.38318531]]), + ), + ], +) +def test_addition_operator_by_scalar(starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): + do = do_minimal_tth + assert np.allclose(do.all_arrays, starting_all_arrays) + do_sum = do + scalar_value + assert np.allclose(do_sum.all_arrays, expected_all_arrays) + + +@pytest.mark.parametrize( + "LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum", + [ + # Test addition of two DO objects, expect combined xarray values (q, tth, d) and no change to yarray + ( # C1: Add two DO objects with identical xarray values, expect sum of xarray values + (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), + (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), + np.array([[1.0, 1.03527618, 60.0, 24.27636384], [2.0, 2.0, 120.0, 12.56637061]]), + ), + ], +) +def test_addition_operator_by_another_do(LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum, do_minimal_tth): + assert np.allclose(do_minimal_tth.all_arrays, LHS_all_arrays) + do_LHS = do_minimal_tth + do_RHS = do_minimal_tth + do_sum = do_LHS + do_RHS + assert np.allclose(do_LHS.all_arrays, LHS_all_arrays) + assert np.allclose(do_RHS.all_arrays, RHS_all_arrays) + assert np.allclose(do_sum.all_arrays, expected_all_arrays_sum) + + +def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): + # Add a string to a DO object, expect TypeError, only scalar (int, float) allowed for addition + do_LHS = do_minimal_tth + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + do_LHS + "string_value" + + +def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): + # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays + do_LHS = do_minimal + do_RHS = do_minimal_tth + with pytest.raises(ValueError, match=re.escape(x_grid_size_mismatch_error_msg)): + do_LHS + do_RHS From 5d0ebcc62552af1df5953d2fa75fe0f7efdfaa34 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:44:34 -0500 Subject: [PATCH 322/445] Add example to __eq__ --- src/diffpy/utils/diffraction_objects.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 316758d0..f2a0ce68 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -196,7 +196,16 @@ def __add__(self, other): Raised when the length of the xarrays of the two DiffractionObject instances do not match. TypeError Raised when the type of `other` is not an instance of DiffractionObject, int, or float. + + Examples + -------- + Add a scalar value to the xarrays of the DiffractionObject instance: + >>> new_do = my_do + 10.1 + + Add the xarrays of two DiffractionObject instances: + >>> new_do = my_do_1 + my_do_2 """ + summed_do = deepcopy(self) # Add scalar value to all xarrays by broadcasting if isinstance(other, (int, float)): From 9741a8ed56acc859770cb12e19d7d4d712cd6bb5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:46:08 -0500 Subject: [PATCH 323/445] Revert not needed change to naming in a test func --- tests/test_diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9675ad7f..0555dc37 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -155,7 +155,7 @@ def test_diffraction_objects_equality( @pytest.mark.parametrize( - "xtype, expected_all_arrays", + "xtype, expected_xarray", [ # Test whether on_xtype returns the correct xarray values. # C1: tth to tth, expect no change in xarray value @@ -169,10 +169,10 @@ def test_diffraction_objects_equality( ("d", np.array([12.13818, 6.28319])), ], ) -def test_on_xtype(xtype, expected_all_arrays, do_minimal_tth): +def test_on_xtype(xtype, expected_xarray, do_minimal_tth): do = do_minimal_tth actual_xrray, actual_yarray = do.on_xtype(xtype) - assert np.allclose(actual_xrray, expected_all_arrays) + assert np.allclose(actual_xrray, expected_xarray) assert np.allclose(actual_yarray, np.array([1, 2])) From 3f577d7e558cd6f346c01ea3c71c1063f0d91be5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:46:40 -0500 Subject: [PATCH 324/445] Apply pre-commit --- src/diffpy/utils/diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index f2a0ce68..097f1d9d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -196,16 +196,16 @@ def __add__(self, other): Raised when the length of the xarrays of the two DiffractionObject instances do not match. TypeError Raised when the type of `other` is not an instance of DiffractionObject, int, or float. - + Examples -------- Add a scalar value to the xarrays of the DiffractionObject instance: >>> new_do = my_do + 10.1 - + Add the xarrays of two DiffractionObject instances: >>> new_do = my_do_1 + my_do_2 """ - + summed_do = deepcopy(self) # Add scalar value to all xarrays by broadcasting if isinstance(other, (int, float)): From 0bdbbb48a8c9fff3e9a2c9a715baa04dd08e9164 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 27 Dec 2024 21:18:59 -0500 Subject: [PATCH 325/445] enforce requirement to specify either density or packing fraction --- src/diffpy/utils/tools.py | 31 +++++++++++---- tests/test_tools.py | 82 ++++++++++++++++++++------------------- 2 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 7144a9bb..d8b59f10 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,5 +1,6 @@ import importlib.metadata import json +import warnings from copy import copy from pathlib import Path @@ -210,12 +211,12 @@ def get_package_info(package_names, metadata=None): return metadata -def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1): +def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None, packing_fraction=None): """Compute the attenuation coefficient (mu) using the XrayDB database. Computes mu based on the sample composition and energy. - User can provide a measured density or an estimated packing fraction. - Specifying the density is recommended, though not required for some pure or standard materials. + User should provide a sample mass density or a packing fraction. + If neither density nor packing fraction is specified, or if both are specified, a ValueError will be raised. Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu. Parameters @@ -223,10 +224,10 @@ def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fr sample_composition : str The chemical formula or the name of the material. energy : float - The energy in keV. - density : float, optional, Default is None + The energy of the incident x-rays in keV. + sample_mass_density : float, optional, Default is None The mass density of the packed powder/sample in gr/cm^3. - packing_fraction : float, optional, Default is 1 + packing_fraction : float, optional, Default is None The fraction of sample in the capillary (between 0 and 1). Returns @@ -234,5 +235,21 @@ def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fr mu : float The attenuation coefficient mu in mm^{-1}. """ - mu = material_mu(sample_composition, energy * 1000, density=density, kind="total") * packing_fraction / 10 + if (sample_mass_density is None and packing_fraction is None) or ( + sample_mass_density is not None and packing_fraction is not None + ): + raise ValueError( + "You must specify either sample_mass_density or packing_fraction, but not both. " + "Please rerun specifying only one." + ) + if sample_mass_density is not None: + mu = material_mu(sample_composition, energy * 1000, density=sample_mass_density, kind="total") / 10 + else: + warnings.warn( + "Warning: Density is set to None if a packing fraction is specified, " + "which may cause errors for some materials. " + "We recommend specifying sample mass density for now. " + "Auto-density calculation is coming soon." + ) + mu = material_mu(sample_composition, energy * 1000, density=None, kind="total") * packing_fraction / 10 return mu diff --git a/tests/test_tools.py b/tests/test_tools.py index 06202cdf..a3363320 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -171,59 +171,61 @@ def test_get_package_info(monkeypatch, inputs, expected): "inputs, expected_mu", [ # Test whether the function returns the correct mu - ( # C1: No density or packing fraction (only for known material), expect to get mu from database + ( # C1: Composition, energy, and mass density provided, expect to get mu based on mass density + # 1. Fully dense mass density + {"sample_composition": "quartz", "energy": 10, "sample_mass_density": 2.65}, + 5.0368, + ), + ( # 2. Measured mass density { - "sample_composition": "H2O", - "energy": 10, + "sample_composition": "ZrO2", + "energy": 17.445, + "sample_mass_density": 1.009, }, - 0.5330, + 1.2522, ), - ( # C2: Packing fraction (=0.5) provided only (only for known material) + ( # C2: Composition, energy, and packing fraction provided, expect to get mu based on packing fraction + # Reuse pattern from C1.1 here { - "sample_composition": "H2O", + "sample_composition": "quartz", "energy": 10, "packing_fraction": 0.5, }, - 0.2665, + 2.5184, ), - ( # C3: Density provided only, expect to compute mu based on it - # 1. Known material + ], +) +def test_compute_mu_using_xraydb(inputs, expected_mu): + actual_mu = compute_mu_using_xraydb(**inputs) + assert actual_mu == pytest.approx(expected_mu, rel=1e-6, abs=1e-4) + + +@pytest.mark.parametrize( + "inputs", + [ + # Test when the function raises ValueError + # C1: Both mass density and packing fraction are provided + ( { - "sample_composition": "H2O", + "sample_composition": "quartz", "energy": 10, - "density": 0.987, - }, - 0.5330, + "sample_mass_density": 2.65, + "packing_fraction": 1, + } ), - ( # 2. Unknown material - { - "sample_composition": "ZrO2", - "energy": 17, - "density": 1.009, - }, - 1.252, - ), - ( # C4: Both density and packing fraction are provided, expect to compute mu based on both - # 1. Known material + # C2: None of mass density or packing fraction are provided + ( { - "sample_composition": "H2O", + "sample_composition": "quartz", "energy": 10, - "density": 0.997, - "packing_fraction": 0.5, - }, - 0.2665, - ), - ( # 2. Unknown material - { - "sample_composition": "ZrO2", - "energy": 17, - "density": 1.009, - "packing_fraction": 0.5, - }, - 0.626, + } ), ], ) -def test_compute_mu_using_xraydb(inputs, expected_mu): - actual_mu = compute_mu_using_xraydb(**inputs) - assert actual_mu == pytest.approx(expected_mu, rel=0.01, abs=0.1) +def test_compute_mu_using_xraydb_bad(inputs): + with pytest.raises( + ValueError, + match="You must specify either sample_mass_density or packing_fraction, but not both. " + "Please rerun specifying only one.", + ): + compute_mu_using_xraydb(**inputs) From d56158344269410a37f36ea0e27c18778168eaec Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 23:42:14 -0500 Subject: [PATCH 326/445] Add radd and rmul back and add tests --- src/diffpy/utils/diffraction_objects.py | 14 ++++++++++++++ tests/test_diffraction_objects.py | 18 +++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 097f1d9d..e741d94a 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -223,6 +223,8 @@ def __add__(self, other): raise TypeError(invalid_add_type_emsg) return summed_do + __radd__ = __add__ + def __sub__(self, other): subtracted = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): @@ -265,6 +267,18 @@ def __mul__(self, other): multiplied.on_q[1] = self.on_q[1] * other.on_q[1] return multiplied + def __rmul__(self, other): + multiplied = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + multiplied.on_tth[1] = other * self.on_tth[1] + multiplied.on_q[1] = other * self.on_q[1] + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_length_mismatch_emsg) + else: + multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] + multiplied.on_q[1] = self.on_q[1] * other.on_q[1] + return multiplied + def __truediv__(self, other): divided = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 0555dc37..30dffb25 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -705,7 +705,7 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( - "starting_all_arrays, scalar_value, expected_all_arrays", + "starting_all_arrays, scalar_to_add, expected_all_arrays", [ # Test scalar addition to xarray values (q, tth, d) and expect no change to yarray values ( # C1: Add integer of 5, expect xarray to increase by by 5 @@ -720,11 +720,13 @@ def test_copy_object(do_minimal): ), ], ) -def test_addition_operator_by_scalar(starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): +def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - do_sum = do + scalar_value - assert np.allclose(do_sum.all_arrays, expected_all_arrays) + do_sum_RHS = do + scalar_to_add + do_sum_LHS = scalar_to_add + do + assert np.allclose(do_sum_RHS.all_arrays, expected_all_arrays) + assert np.allclose(do_sum_LHS.all_arrays, expected_all_arrays) @pytest.mark.parametrize( @@ -750,10 +752,12 @@ def test_addition_operator_by_another_do(LHS_all_arrays, RHS_all_arrays, expecte def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): # Add a string to a DO object, expect TypeError, only scalar (int, float) allowed for addition - do_LHS = do_minimal_tth + do = do_minimal_tth with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - do_LHS + "string_value" - + do + "string_value" + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + "string_value" + do + def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays From ac5a2f3b608b53128a6944bc2919ca902d6d3849 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Dec 2024 04:42:24 +0000 Subject: [PATCH 327/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 30dffb25..0f4cc08e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -757,7 +757,7 @@ def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_m do + "string_value" with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): "string_value" + do - + def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays From e91661df5764bae3a0bb0a6d81e29daab8f8b793 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 28 Dec 2024 23:02:00 -0500 Subject: [PATCH 328/445] add news --- news/scaleto-max.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scaleto-max.rst diff --git a/news/scaleto-max.rst b/news/scaleto-max.rst new file mode 100644 index 00000000..0037f533 --- /dev/null +++ b/news/scaleto-max.rst @@ -0,0 +1,23 @@ +**Added:** + +* new feature in `scale_to()` to run without specifying an x-value + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 4a87d00a4ed1f093b177b546bc7dcd8febd92196 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 28 Dec 2024 23:02:33 -0500 Subject: [PATCH 329/445] add example --- doc/source/examples/diffraction_objects_example.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 8621f50f..aa4de157 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -117,6 +117,13 @@ For convenience, you can also apply an offset to the scaled new diffraction obje scaled_and_offset_measured = measured.scale_to(calculated, q=5.5, offset=0.5) +You can call `scale_to()` without specifying a value for `q`, `tth`, or `d`. +In this case, the scaling will be done based on the maximal x-array value of both diffraction objects: + +.. code-block:: python + + scaled_measured = measured.scale_to(calculated) + DiffractionObject convenience functions --------------------------------------- From 2c8335cd1dda1762e60f5c4a6501c4c0364b49f5 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 28 Dec 2024 23:07:19 -0500 Subject: [PATCH 330/445] add feature so that user can run scale_to without xvalue --- src/diffpy/utils/diffraction_objects.py | 15 ++++++--- tests/test_diffraction_objects.py | 41 +++++++++++++------------ 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 25e3e28c..413d9eac 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -402,14 +402,16 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. - If multiple values of `q`, `tth`, or `d` are provided, or none are provided, an error will be raised. + If none of `q`, `tth`, or `d` are provided, + the scaling will be based on the maximal x-array value from both objects. + If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. Parameters ---------- target_diff_object: DiffractionObject the diffraction object you want to scale the current one onto - q, tth, d : float, optional, must specify exactly one of them + q, tth, d : float, optional, default is q with the maximal x-array value of the current object The value of the x-array where you want the curves to line up vertically. Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. @@ -422,11 +424,16 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): """ scaled = self.copy() count = sum([q is not None, tth is not None, d is not None]) - if count != 1: + if count > 1: raise ValueError( - "You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." + "You must specify none or exactly one of 'q', 'tth', or 'd'. " + "Please provide either none or one value." ) + if count == 0: + scaled._all_arrays[:, 0] *= max(target_diff_object.on_q()[1]) / max(self.on_q()[1]) + return scaled + xtype = "q" if q is not None else "tth" if tth is not None else "d" data = self.on_xtype(xtype) target = target_diff_object.on_xtype(xtype) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index c6355882..789a5e77 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -284,23 +284,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - ], -) -def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): - original_do = DiffractionObject(**org_do_args) - target_do = DiffractionObject(**target_do_args) - scaled_do = original_do.scale_to( - target_do, q=scale_inputs["q"], tth=scale_inputs["tth"], d=scale_inputs["d"], offset=scale_inputs["offset"] - ) - # Check the intensity data is the same as expected - assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) - - -@pytest.mark.parametrize( - "org_do_args, target_do_args, scale_inputs", - [ - # Test expected errors produced from scale_to() with invalid inputs - ( # C1: none of q, tth, d, provided, expect ValueError + ( # C5: none of q, tth, d, provided, expect to scale on the maximal x-arrays { "xarray": np.array([0.1, 0.2, 0.3]), "yarray": np.array([1, 2, 3]), @@ -319,8 +303,25 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "d": None, "offset": 0, }, + {"xtype": "q", "yarray": np.array([10, 20, 30])}, ), - ( # C2: tth and d both provided, expect ValueErrort + ], +) +def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): + original_do = DiffractionObject(**org_do_args) + target_do = DiffractionObject(**target_do_args) + scaled_do = original_do.scale_to( + target_do, q=scale_inputs["q"], tth=scale_inputs["tth"], d=scale_inputs["d"], offset=scale_inputs["offset"] + ) + # Check the intensity data is the same as expected + assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) + + +@pytest.mark.parametrize( + "org_do_args, target_do_args, scale_inputs", + [ + # Test expected errors produced from scale_to() with invalid inputs + ( # C2: tth and d both provided, expect ValueError { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), @@ -346,7 +347,9 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): original_do = DiffractionObject(**org_do_args) target_do = DiffractionObject(**target_do_args) with pytest.raises( - ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." + ValueError, + match="You must specify none or exactly one of 'q', 'tth', or 'd'. " + "Please provide either none or one value.", ): original_do.scale_to( target_do, From 00f36ad2ff38985ce74c26563cc13887abd7e735 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 28 Dec 2024 23:35:37 -0500 Subject: [PATCH 331/445] add notimplementederror --- src/diffpy/utils/tools.py | 29 +++++++++++++++++++---------- tests/test_tools.py | 9 --------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index d8b59f10..e1e7ad0a 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -211,6 +211,14 @@ def get_package_info(package_names, metadata=None): return metadata +def get_density_from_cloud(sample_composition, mp_token=""): + """Function to get material density from the MP database. + + It is not implemented yet. + """ + raise NotImplementedError + + def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None, packing_fraction=None): """Compute the attenuation coefficient (mu) using the XrayDB database. @@ -242,14 +250,15 @@ def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None "You must specify either sample_mass_density or packing_fraction, but not both. " "Please rerun specifying only one." ) - if sample_mass_density is not None: - mu = material_mu(sample_composition, energy * 1000, density=sample_mass_density, kind="total") / 10 - else: - warnings.warn( - "Warning: Density is set to None if a packing fraction is specified, " - "which may cause errors for some materials. " - "We recommend specifying sample mass density for now. " - "Auto-density calculation is coming soon." - ) - mu = material_mu(sample_composition, energy * 1000, density=None, kind="total") * packing_fraction / 10 + if packing_fraction is None: + packing_fraction = 1 + try: + sample_mass_density = get_density_from_cloud(sample_composition) * packing_fraction + except NotImplementedError: + warnings.warn( + "Density computation is not implemented right now. " + "Please rerun specifying a sample mass density." + ) + energy_eV = energy * 1000 + mu = material_mu(sample_composition, energy_eV, density=sample_mass_density, kind="total") / 10 return mu diff --git a/tests/test_tools.py b/tests/test_tools.py index a3363320..d02996ca 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -184,15 +184,6 @@ def test_get_package_info(monkeypatch, inputs, expected): }, 1.2522, ), - ( # C2: Composition, energy, and packing fraction provided, expect to get mu based on packing fraction - # Reuse pattern from C1.1 here - { - "sample_composition": "quartz", - "energy": 10, - "packing_fraction": 0.5, - }, - 2.5184, - ), ], ) def test_compute_mu_using_xraydb(inputs, expected_mu): From 11c41669b5c5cc7fc1d8340ac0011dfdce6f0782 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 28 Dec 2024 23:57:16 -0500 Subject: [PATCH 332/445] refactor: add yarrays together instead of xarrays for __add__ --- src/diffpy/utils/diffraction_objects.py | 61 +++++++++++++------------ 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index e741d94a..25e09381 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -14,15 +14,15 @@ XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] -x_grid_length_mismatch_emsg = ( - "The two objects have different x-array lengths. " - "Please ensure the length of the x-value during initialization is identical." +y_grid_length_mismatch_emsg = ( + "The two objects have different y-array lengths. " + "Please ensure the length of the y-value during initialization is identical." ) invalid_add_type_emsg = ( "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " "Please rerun by adding another DiffractionObject instance or a scalar value. " - "e.g., my_do_1 + my_do_2 or my_do + 10" + "e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" ) @@ -175,56 +175,57 @@ def __eq__(self, other): return True def __add__(self, other): - """Add a scalar value or another DiffractionObject to the xarrays of - the DiffractionObject. + """Add a scalar value or another DiffractionObject to the yarray of the + DiffractionObject. Parameters ---------- other : DiffractionObject or int or float The object to add to the current DiffractionObject. If `other` is a scalar value, - it will be added to all xarrays. The length of the xarrays must match if `other` is + it will be added to all yarray. The length of the yarray must match if `other` is an instance of DiffractionObject. Returns ------- DiffractionObject - The new and deep-copied DiffractionObject instance after adding values to the xarrays. + The new and deep-copied DiffractionObject instance after adding values to the yarray. Raises ------ ValueError - Raised when the length of the xarrays of the two DiffractionObject instances do not match. + Raised when the length of the yarray of the two DiffractionObject instances do not match. TypeError Raised when the type of `other` is not an instance of DiffractionObject, int, or float. Examples -------- - Add a scalar value to the xarrays of the DiffractionObject instance: + Add a scalar value to the yarray of the DiffractionObject instance: >>> new_do = my_do + 10.1 + >>> new_do = 10.1 + my_do - Add the xarrays of two DiffractionObject instances: + Add the yarray of two DiffractionObject instances: >>> new_do = my_do_1 + my_do_2 """ + self._check_operation_compatibility(other) summed_do = deepcopy(self) - # Add scalar value to all xarrays by broadcasting if isinstance(other, (int, float)): - summed_do._all_arrays[:, 1] += other - summed_do._all_arrays[:, 2] += other - summed_do._all_arrays[:, 3] += other - # Add xarrays of two DiffractionObject instances - elif isinstance(other, DiffractionObject): - if len(self.on_tth()[0]) != len(other.on_tth()[0]): - raise ValueError(x_grid_length_mismatch_emsg) - summed_do._all_arrays[:, 1] += other.on_q()[0] - summed_do._all_arrays[:, 2] += other.on_tth()[0] - summed_do._all_arrays[:, 3] += other.on_d()[0] - else: - raise TypeError(invalid_add_type_emsg) + summed_do._all_arrays[:, 0] += other + if isinstance(other, DiffractionObject): + summed_do._all_arrays[:, 0] += other.all_arrays[:, 0] return summed_do __radd__ = __add__ + def _check_operation_compatibility(self, other): + if not isinstance(other, (DiffractionObject, int, float)): + raise TypeError(invalid_add_type_emsg) + if isinstance(other, DiffractionObject): + self_yarray = self.all_arrays[:, 0] + other_yarray = other.all_arrays[:, 0] + if len(self_yarray) != len(other_yarray): + raise ValueError(y_grid_length_mismatch_emsg) + def __sub__(self, other): subtracted = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): @@ -233,7 +234,7 @@ def __sub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] subtracted.on_q[1] = self.on_q[1] - other.on_q[1] @@ -247,7 +248,7 @@ def __rsub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] subtracted.on_q[1] = other.on_q[1] - self.on_q[1] @@ -261,7 +262,7 @@ def __mul__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -273,7 +274,7 @@ def __rmul__(self, other): multiplied.on_tth[1] = other * self.on_tth[1] multiplied.on_q[1] = other * self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -287,7 +288,7 @@ def __truediv__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] divided.on_q[1] = self.on_q[1] / other.on_q[1] @@ -299,7 +300,7 @@ def __rtruediv__(self, other): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] divided.on_q[1] = other.on_q[1] / self.on_q[1] From 846c72ae76f29426ae009ed257c17226484a6a8d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 28 Dec 2024 23:57:55 -0500 Subject: [PATCH 333/445] test: combine do with another do or scalar value tests --- tests/conftest.py | 14 +++++-- tests/test_diffraction_objects.py | 64 ++++++++++++++++++------------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5eaaa902..9e5f1e60 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,6 +47,12 @@ def do_minimal_tth(): return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") +@pytest.fixture +def do_minimal_d(): + # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values + return DiffractionObject(wavelength=1.54, xarray=np.array([1, 2]), yarray=np.array([1, 2]), xtype="d") + + @pytest.fixture def wavelength_warning_msg(): return ( @@ -70,13 +76,13 @@ def invalid_add_type_error_msg(): return ( "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " "Please rerun by adding another DiffractionObject instance or a scalar value. " - "e.g., my_do_1 + my_do_2 or my_do + 10" + "e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" ) @pytest.fixture -def x_grid_size_mismatch_error_msg(): +def y_grid_size_mismatch_error_msg(): return ( - "The two objects have different x-array lengths. " - "Please ensure the length of the x-value during initialization is identical." + "The two objects have different y-array lengths. " + "Please ensure the length of the y-value during initialization is identical." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ab7eaf26..649de01b 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -715,47 +715,57 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( "starting_all_arrays, scalar_to_add, expected_all_arrays", [ - # Test scalar addition to xarray values (q, tth, d) and expect no change to yarray values - ( # C1: Add integer of 5, expect xarray to increase by by 5 + # Test scalar addition to yarray values (intensity) and expect no change to xarrays (q, tth, d) + ( # C1: Add integer of 5, expect yarray to increase by by 5 np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5, - np.array([[1.0, 5.51763809, 35.0, 17.13818192], [2.0, 6.0, 65.0, 11.28318531]]), + np.array([[6.0, 0.51763809, 30.0, 12.13818192], [7.0, 1.0, 60.0, 6.28318531]]), ), - ( # C2: Add float of 5.1, expect xarray to be added by 5.1 + ( # C2: Add float of 5.1, expect yarray to be added by 5.1 np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5.1, - np.array([[1.0, 5.61763809, 35.1, 17.23818192], [2.0, 6.1, 65.1, 11.38318531]]), + np.array([[6.1, 0.51763809, 30.0, 12.13818192], [7.1, 1.0, 60.0, 6.28318531]]), ), ], ) def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - do_sum_RHS = do + scalar_to_add - do_sum_LHS = scalar_to_add + do - assert np.allclose(do_sum_RHS.all_arrays, expected_all_arrays) - assert np.allclose(do_sum_LHS.all_arrays, expected_all_arrays) + do_scalar_right_sum = do + scalar_to_add + assert np.allclose(do_scalar_right_sum.all_arrays, expected_all_arrays) + do_scalar_left_sum = scalar_to_add + do + assert np.allclose(do_scalar_left_sum.all_arrays, expected_all_arrays) @pytest.mark.parametrize( - "LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum", + "do_1_all_arrays, " + "do_2_all_arrays, " + "expected_do_1_all_arrays_with_y_summed, " + "expected_do_2_all_arrays_with_y_summed", [ # Test addition of two DO objects, expect combined xarray values (q, tth, d) and no change to yarray - ( # C1: Add two DO objects with identical xarray values, expect sum of xarray values + ( # C1: Add two DO objects, expect sum of yarray values (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), - (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), - np.array([[1.0, 1.03527618, 60.0, 24.27636384], [2.0, 2.0, 120.0, 12.56637061]]), + (np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]),), + (np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]),), + (np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]),), ), ], ) -def test_addition_operator_by_another_do(LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum, do_minimal_tth): - assert np.allclose(do_minimal_tth.all_arrays, LHS_all_arrays) - do_LHS = do_minimal_tth - do_RHS = do_minimal_tth - do_sum = do_LHS + do_RHS - assert np.allclose(do_LHS.all_arrays, LHS_all_arrays) - assert np.allclose(do_RHS.all_arrays, RHS_all_arrays) - assert np.allclose(do_sum.all_arrays, expected_all_arrays_sum) +def test_addition_operator_by_another_do( + do_1_all_arrays, + do_2_all_arrays, + expected_do_1_all_arrays_with_y_summed, + expected_do_2_all_arrays_with_y_summed, + do_minimal_tth, + do_minimal_d, +): + do_1 = do_minimal_tth + assert np.allclose(do_1.all_arrays, do_1_all_arrays) + do_2 = do_minimal_d + assert np.allclose(do_2.all_arrays, do_2_all_arrays) + assert np.allclose((do_1 + do_2).all_arrays, expected_do_1_all_arrays_with_y_summed) + assert np.allclose((do_2 + do_1).all_arrays, expected_do_2_all_arrays_with_y_summed) def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @@ -767,9 +777,11 @@ def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_m "string_value" + do -def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): +def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays - do_LHS = do_minimal - do_RHS = do_minimal_tth - with pytest.raises(ValueError, match=re.escape(x_grid_size_mismatch_error_msg)): - do_LHS + do_RHS + do_1 = do_minimal + do_2 = do_minimal_tth + assert len(do_1.all_arrays[:, 0]) == 0 + assert len(do_2.all_arrays[:, 0]) == 2 + with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): + do_1 + do_2 From da70bd6629ab7589bd628feca625f106c5b515a0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 01:00:04 -0500 Subject: [PATCH 334/445] test: changed xarray to yarray in one of the test cases --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 649de01b..ccab71fe 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -743,7 +743,7 @@ def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expecte "expected_do_1_all_arrays_with_y_summed, " "expected_do_2_all_arrays_with_y_summed", [ - # Test addition of two DO objects, expect combined xarray values (q, tth, d) and no change to yarray + # Test addition of two DO objects, expect combined yarray values and no change to xarrays ((q, tth, d) ( # C1: Add two DO objects, expect sum of yarray values (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), (np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]),), From 255c603a54ccc0d680a8a851308355114663ca6c Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 15:07:29 -0500 Subject: [PATCH 335/445] refactor: rearrange error msg and docstring, update condition in function --- src/diffpy/utils/tools.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index e1e7ad0a..9c963e14 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -1,6 +1,5 @@ import importlib.metadata import json -import warnings from copy import copy from pathlib import Path @@ -212,11 +211,15 @@ def get_package_info(package_names, metadata=None): def get_density_from_cloud(sample_composition, mp_token=""): - """Function to get material density from the MP database. + """Function to get material density from the MP or COD database. It is not implemented yet. """ - raise NotImplementedError + raise NotImplementedError( + "So sorry, density computation from composition is not implemented right now. " + "We hope to have this implemented in the next release. " + "Please rerun specifying a sample mass density." + ) def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None, packing_fraction=None): @@ -250,15 +253,8 @@ def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None "You must specify either sample_mass_density or packing_fraction, but not both. " "Please rerun specifying only one." ) - if packing_fraction is None: - packing_fraction = 1 - try: - sample_mass_density = get_density_from_cloud(sample_composition) * packing_fraction - except NotImplementedError: - warnings.warn( - "Density computation is not implemented right now. " - "Please rerun specifying a sample mass density." - ) + if packing_fraction is not None: + sample_mass_density = get_density_from_cloud(sample_composition) * packing_fraction energy_eV = energy * 1000 mu = material_mu(sample_composition, energy_eV, density=sample_mass_density, kind="total") / 10 return mu From 630011a6210663b3428e81784a921efab5d3624f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 16:28:47 -0500 Subject: [PATCH 336/445] refactor: reorder docstring and tests, change offset default to None, update tests for flexible inputs --- src/diffpy/utils/diffraction_objects.py | 13 ++-- tests/test_diffraction_objects.py | 87 ++++++++----------------- 2 files changed, 34 insertions(+), 66 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 413d9eac..36386ce1 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -396,14 +396,14 @@ def on_tth(self): def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] - def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): + def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): """Returns a new diffraction object which is the current object but rescaled in y to the target. + By default, if none of `q`, `tth`, or `d` are provided, + the scaling is based on the maximal x-array value from both objects. The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. - If none of `q`, `tth`, or `d` are provided, - the scaling will be based on the maximal x-array value from both objects. If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. Parameters @@ -411,17 +411,20 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): target_diff_object: DiffractionObject the diffraction object you want to scale the current one onto - q, tth, d : float, optional, default is q with the maximal x-array value of the current object + q, tth, d : float, optional, default is q with the maximal x-array value from each object The value of the x-array where you want the curves to line up vertically. Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. - offset : float, optional, default is 0 + offset : float, optional, default is None an offset to add to the scaled y-values Returns ------- the rescaled DiffractionObject as a new object """ + if offset is None: + offset = 0 + scaled = self.copy() count = sum([q is not None, tth is not None, d is not None]) if count > 1: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 789a5e77..6499bdd7 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -191,7 +191,23 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether the original y-array is scaled as expected - ( # C1: Same x-arrays + ( # C1: none of q, tth, d, provided, expect to scale on the maximal x-arrays + { + "xarray": np.array([0.1, 0.2, 0.3]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([0.05, 0.1, 0.2, 0.3]), + "yarray": np.array([5, 10, 20, 30]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + {}, + {"xtype": "q", "yarray": np.array([10, 20, 30])}, + ), + ( # C2: Same x-arrays # x-value has exact matches at tth=60 (y=60) and tth=60 (y=6), # for original and target diffraction objects, # expect original y-array to multiply by 6/60=1/10 @@ -207,15 +223,10 @@ def test_init_invalid_xtype(): "xtype": "tth", "wavelength": 2 * np.pi, }, - { - "q": None, - "tth": 60, - "d": None, - "offset": 0, - }, + {"tth": 60}, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - ( # C2: Different x-arrays with same length, + ( # C3: Different x-arrays with same length, # x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1) # for original and target diffraction objects, # expect original y-array to multiply by 1/10 @@ -231,15 +242,10 @@ def test_init_invalid_xtype(): "xtype": "q", "wavelength": 2 * np.pi, }, - { - "q": 0.1, - "tth": None, - "d": None, - "offset": 0, - }, + {"q": 0.1}, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - ( # C3: Different x-array lengths + ( # C4: Different x-array lengths # x-value has closest matches at tth=61 (y=50) and tth=62 (y=5), # for original and target diffraction objects, # expect original y-array to multiply by 5/50=1/10 @@ -255,15 +261,10 @@ def test_init_invalid_xtype(): "xtype": "tth", "wavelength": 2 * np.pi, }, - { - "q": None, - "tth": 60, - "d": None, - "offset": 0, - }, + {"tth": 60}, {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), - ( # C4: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 + ( # C5: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 { "xarray": np.array([10, 15, 25, 30, 60, 140]), "yarray": np.array([2, 3, 4, 5, 6, 7]), @@ -276,43 +277,15 @@ def test_init_invalid_xtype(): "xtype": "tth", "wavelength": 2 * np.pi, }, - { - "q": None, - "tth": 60, - "d": None, - "offset": 2.1, - }, + {"tth": 60, "offset": 2.1}, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - ( # C5: none of q, tth, d, provided, expect to scale on the maximal x-arrays - { - "xarray": np.array([0.1, 0.2, 0.3]), - "yarray": np.array([1, 2, 3]), - "xtype": "q", - "wavelength": 2 * np.pi, - }, - { - "xarray": np.array([0.05, 0.1, 0.2, 0.3]), - "yarray": np.array([5, 10, 20, 30]), - "xtype": "q", - "wavelength": 2 * np.pi, - }, - { - "q": None, - "tth": None, - "d": None, - "offset": 0, - }, - {"xtype": "q", "yarray": np.array([10, 20, 30])}, - ), ], ) def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): original_do = DiffractionObject(**org_do_args) target_do = DiffractionObject(**target_do_args) - scaled_do = original_do.scale_to( - target_do, q=scale_inputs["q"], tth=scale_inputs["tth"], d=scale_inputs["d"], offset=scale_inputs["offset"] - ) + scaled_do = original_do.scale_to(target_do, **scale_inputs) # Check the intensity data is the same as expected assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) @@ -335,10 +308,8 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): "wavelength": 2 * np.pi, }, { - "q": None, "tth": 60, "d": 10, - "offset": 0, }, ), ], @@ -351,13 +322,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): match="You must specify none or exactly one of 'q', 'tth', or 'd'. " "Please provide either none or one value.", ): - original_do.scale_to( - target_do, - q=scale_inputs["q"], - tth=scale_inputs["tth"], - d=scale_inputs["d"], - offset=scale_inputs["offset"], - ) + original_do.scale_to(target_do, **scale_inputs) @pytest.mark.parametrize( From 66c67c6a169ce38f1dd3256f24bb03e4c94972b2 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 16:50:45 -0500 Subject: [PATCH 337/445] docs: clarify docstring and news --- news/scaleto-max.rst | 2 +- src/diffpy/utils/diffraction_objects.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/news/scaleto-max.rst b/news/scaleto-max.rst index 0037f533..d70e9d7f 100644 --- a/news/scaleto-max.rst +++ b/news/scaleto-max.rst @@ -1,6 +1,6 @@ **Added:** -* new feature in `scale_to()` to run without specifying an x-value +* new feature in `scale_to()`: default scaling is based on the max q-value in each diffraction object. **Changed:** diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 36386ce1..80202cd3 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -400,9 +400,8 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): """Returns a new diffraction object which is the current object but rescaled in y to the target. - By default, if none of `q`, `tth`, or `d` are provided, - the scaling is based on the maximal x-array value from both objects. - The y-value in the target at the closest specified x-value will be used as the factor to scale to. + By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max q-value from each object. + Otherwise, y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. @@ -411,7 +410,7 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): target_diff_object: DiffractionObject the diffraction object you want to scale the current one onto - q, tth, d : float, optional, default is q with the maximal x-array value from each object + q, tth, d : float, optional, default is the max q-value from each object The value of the x-array where you want the curves to line up vertically. Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. From 0a155aaf231c3dc237e7f6ddeefde23e64d5a4c9 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 17:01:33 -0500 Subject: [PATCH 338/445] docs: edit example file --- .../examples/diffraction_objects_example.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index aa4de157..f956081c 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -106,23 +106,26 @@ we would replace the code above with The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new variable and make use of, +The default behavior is to align the objects based on the maximal q-value of each diffraction object, +so they will align at the intensity at these indices. + .. code-block:: python - scaled_measured = measured.scale_to(calculated, q=5.5) + scaled_measured = measured.scale_to(calculated) -For convenience, you can also apply an offset to the scaled new diffraction object with the optional -``offset`` argument, for example, +If this doesn't give the desirable results, you can specify an ``xtype=value`` to scale +based on the closest x-value in both objects. For example: .. code-block:: python - scaled_and_offset_measured = measured.scale_to(calculated, q=5.5, offset=0.5) + scaled_measured = measured.scale_to(calculated, q=5.5) -You can call `scale_to()` without specifying a value for `q`, `tth`, or `d`. -In this case, the scaling will be done based on the maximal x-array value of both diffraction objects: +For convenience, you can also apply an offset to the scaled new diffraction object with the optional +``offset`` argument, for example, .. code-block:: python - scaled_measured = measured.scale_to(calculated) + scaled_and_offset_measured = measured.scale_to(calculated, q=5.5, offset=0.5) DiffractionObject convenience functions --------------------------------------- From 46bc3d18d8ce8b2d190b1cf3fc1a6d8e65dd18db Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 17:10:44 -0500 Subject: [PATCH 339/445] docs: add news --- news/muD_calculator.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/muD_calculator.rst diff --git a/news/muD_calculator.rst b/news/muD_calculator.rst new file mode 100644 index 00000000..fc21cef3 --- /dev/null +++ b/news/muD_calculator.rst @@ -0,0 +1,23 @@ +**Added:** + +* function to compute muD from a given z-scan file + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From bbb6151ebb2db0a1c902a43e43044d2359508fa8 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 17:11:58 -0500 Subject: [PATCH 340/445] feat: move muD calculator from labpdfproc to here --- src/diffpy/utils/tools.py | 122 ++++++++++++++++++++++++++++++++------ tests/test_tools.py | 25 +++++++- 2 files changed, 128 insertions(+), 19 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index b7a4c47d..040e62ea 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -3,25 +3,11 @@ from copy import copy from pathlib import Path +import numpy as np +from scipy.optimize import dual_annealing +from scipy.signal import convolve -def clean_dict(obj): - """Remove keys from the dictionary where the corresponding value is None. - - Parameters - ---------- - obj: dict - The dictionary to clean. If None, initialize as an empty dictionary. - - Returns - ------- - dict: - The cleaned dictionary with keys removed where the value is None. - """ - obj = obj if obj is not None else {} - for key, value in copy(obj).items(): - if not value: - del obj[key] - return obj +from diffpy.utils.parsers.loaddata import loadData def _stringify(obj): @@ -206,3 +192,103 @@ def get_package_info(package_names, metadata=None): pkg_info.update({package: importlib.metadata.version(package)}) metadata["package_info"] = pkg_info return metadata + + +def _top_hat(z, half_slit_width): + """Create a top-hat function, return 1.0 for values within the specified + slit width and 0 otherwise.""" + return np.where((z >= -half_slit_width) & (z <= half_slit_width), 1.0, 0.0) + + +def _model_function(z, diameter, z0, I0, mud, slope): + """ + Compute the model function with the following steps: + 1. Let dz = z-z0, so that dz is centered at 0 + 2. Compute length l that is the effective length for computing intensity I = I0 * e^{-mu * l}: + - For dz within the capillary diameter, l is the chord length of the circle at position dz + - For dz outside this range, l = 0 + 3. Apply a linear adjustment to I0 by taking I0 as I0 - slope * z + """ + min_radius = -diameter / 2 + max_radius = diameter / 2 + dz = z - z0 + length = np.piecewise( + dz, + [dz < min_radius, (min_radius <= dz) & (dz <= max_radius), dz > max_radius], + [0, lambda dz: 2 * np.sqrt((diameter / 2) ** 2 - dz**2), 0], + ) + return (I0 - slope * z) * np.exp(-mud / diameter * length) + + +def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): + """Extend z values and I values for padding (so that we don't have tails in + convolution), then perform convolution (note that the convolved I values + are the same as modeled I values if slit width is close to 0)""" + n_points = len(z) + z_left_pad = np.linspace(z.min() - n_points * (z[1] - z[0]), z.min(), n_points) + z_right_pad = np.linspace(z.max(), z.max() + n_points * (z[1] - z[0]), n_points) + z_extended = np.concatenate([z_left_pad, z, z_right_pad]) + I_extended = _model_function(z_extended, diameter, z0, I0, mud, slope) + kernel = _top_hat(z_extended - z_extended.mean(), half_slit_width) + I_convolved = I_extended # this takes care of the case where slit width is close to 0 + if kernel.sum() != 0: + kernel /= kernel.sum() + I_convolved = convolve(I_extended, kernel, mode="same") + padding_length = len(z_left_pad) + return I_convolved[padding_length:-padding_length] + + +def _objective_function(params, z, observed_data): + """Compute the objective function for fitting a model to the + observed/experimental data by minimizing the sum of squared residuals + between the observed data and the convolved model data.""" + diameter, half_slit_width, z0, I0, mud, slope = params + convolved_model_data = _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope) + residuals = observed_data - convolved_model_data + return np.sum(residuals**2) + + +def _compute_single_mud(z_data, I_data): + """Perform dual annealing optimization and extract the parameters.""" + bounds = [ + (1e-5, z_data.max() - z_data.min()), # diameter: [small positive value, upper bound] + (0, (z_data.max() - z_data.min()) / 2), # half slit width: [0, upper bound] + (z_data.min(), z_data.max()), # z0: [min z, max z] + (1e-5, I_data.max()), # I0: [small positive value, max observed intensity] + (1e-5, 20), # muD: [small positive value, upper bound] + (-100000, 100000), # slope: [lower bound, upper bound] + ] + result = dual_annealing(_objective_function, bounds, args=(z_data, I_data)) + diameter, half_slit_width, z0, I0, mud, slope = result.x + convolved_fitted_signal = _extend_z_and_convolve(z_data, diameter, half_slit_width, z0, I0, mud, slope) + residuals = I_data - convolved_fitted_signal + rmse = np.sqrt(np.mean(residuals**2)) + return mud, rmse + + +def compute_mud(filepath): + """Compute the best-fit mu*D value from a z-scan file, removing the sample + holder effect. + + This function loads z-scan data and fits it to a model + that convolves a top-hat function with I = I0 * e^{-mu * l}. + The fitting procedure is run multiple times, and we return the best-fit parameters based on the lowest rmse. + + The full mathematical details are described in the paper: + An ad hoc Absorption Correction for Reliable Pair-Distribution Functions from Low Energy x-ray Sources, + Yucong Chen, Till Schertenleib, Andrew Yang, Pascal Schouwink, Wendy L. Queen and Simon J. L. Billinge, + in preparation. + + Parameters + ---------- + filepath : str + The path to the z-scan file. + + Returns + ------- + mu*D : float + The best-fit mu*D value. + """ + z_data, I_data = loadData(filepath, unpack=True) + best_mud, _ = min((_compute_single_mud(z_data, I_data) for _ in range(20)), key=lambda pair: pair[1]) + return best_mud diff --git a/tests/test_tools.py b/tests/test_tools.py index 3808537d..fa8b3c32 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -3,9 +3,16 @@ import os from pathlib import Path +import numpy as np import pytest -from diffpy.utils.tools import check_and_build_global_config, get_package_info, get_user_info +from diffpy.utils.tools import ( + _extend_z_and_convolve, + check_and_build_global_config, + compute_mud, + get_package_info, + get_user_info, +) @pytest.mark.parametrize( @@ -160,3 +167,19 @@ def test_get_package_info(monkeypatch, inputs, expected): ) actual_metadata = get_package_info(inputs[0], metadata=inputs[1]) assert actual_metadata == expected + + +def test_compute_mud(tmp_path): + diameter, slit_width, z0, I0, mud, slope = 1, 0.1, 0, 1e5, 3, 0 + z_data = np.linspace(-1, 1, 50) + convolved_I_data = _extend_z_and_convolve(z_data, diameter, slit_width, z0, I0, mud, slope) + + directory = Path(tmp_path) + file = directory / "testfile" + with open(file, "w") as f: + for x, I in zip(z_data, convolved_I_data): + f.write(f"{x}\t{I}\n") + + expected_mud = 3 + actual_mud = compute_mud(file) + assert actual_mud == pytest.approx(expected_mud, rel=1e-4, abs=1e-3) From 7f94407ba76f05a3909b8fa4065e8cf296d45cfe Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 17:13:26 -0500 Subject: [PATCH 341/445] build: add scipy package to requirements for pip and conda --- requirements/conda.txt | 1 + requirements/pip.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements/conda.txt b/requirements/conda.txt index 24ce15ab..6bad1038 100644 --- a/requirements/conda.txt +++ b/requirements/conda.txt @@ -1 +1,2 @@ numpy +scipy diff --git a/requirements/pip.txt b/requirements/pip.txt index 24ce15ab..6bad1038 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1 +1,2 @@ numpy +scipy From 630f00e7cd1898eab370f1a79006a65acc188936 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:11:53 -0500 Subject: [PATCH 342/445] feat: implement __sub__ feature for DiffractionObject --- src/diffpy/utils/diffraction_objects.py | 76 ++++++-------------- tests/test_diffraction_objects.py | 96 +++++++++++++++++-------- 2 files changed, 88 insertions(+), 84 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 25e09381..58393ef4 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -217,42 +217,16 @@ def __add__(self, other): __radd__ = __add__ - def _check_operation_compatibility(self, other): - if not isinstance(other, (DiffractionObject, int, float)): - raise TypeError(invalid_add_type_emsg) - if isinstance(other, DiffractionObject): - self_yarray = self.all_arrays[:, 0] - other_yarray = other.all_arrays[:, 0] - if len(self_yarray) != len(other_yarray): - raise ValueError(y_grid_length_mismatch_emsg) - def __sub__(self, other): - subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - subtracted.on_tth[1] = self.on_tth[1] - other - subtracted.on_q[1] = self.on_q[1] - other - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to subtract two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] - subtracted.on_q[1] = self.on_q[1] - other.on_q[1] - return subtracted + self._check_operation_compatibility(other) + subtracted_do = deepcopy(self) + if isinstance(other, (int, float)): + subtracted_do._all_arrays[:, 0] -= other + if isinstance(other, DiffractionObject): + subtracted_do._all_arrays[:, 0] -= other.all_arrays[:, 0] + return subtracted_do - def __rsub__(self, other): - subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - subtracted.on_tth[1] = other - self.on_tth[1] - subtracted.on_q[1] = other - self.on_q[1] - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to subtract two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] - subtracted.on_q[1] = other.on_q[1] - self.on_q[1] - return subtracted + __rsub__ = __sub__ def __mul__(self, other): multiplied = deepcopy(self) @@ -268,19 +242,10 @@ def __mul__(self, other): multiplied.on_q[1] = self.on_q[1] * other.on_q[1] return multiplied - def __rmul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied + __rmul__ = __mul__ def __truediv__(self, other): + divided = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): divided.on_tth[1] = other / self.on_tth[1] @@ -294,17 +259,16 @@ def __truediv__(self, other): divided.on_q[1] = self.on_q[1] / other.on_q[1] return divided - def __rtruediv__(self, other): - divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - divided.on_tth[1] = other / self.on_tth[1] - divided.on_q[1] = other / self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] - divided.on_q[1] = other.on_q[1] / self.on_q[1] - return divided + __rmul__ = __mul__ + + def _check_operation_compatibility(self, other): + if not isinstance(other, (DiffractionObject, int, float)): + raise TypeError(invalid_add_type_emsg) + if isinstance(other, DiffractionObject): + self_yarray = self.all_arrays[:, 0] + other_yarray = other.all_arrays[:, 0] + if self_yarray.shape != other_yarray.shape: + raise ValueError(y_grid_length_mismatch_emsg) @property def all_arrays(self): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ccab71fe..979a7efe 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -713,59 +713,93 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( - "starting_all_arrays, scalar_to_add, expected_all_arrays", + "operation, starting_all_arrays, scalar_value, expected_all_arrays", [ - # Test scalar addition to yarray values (intensity) and expect no change to xarrays (q, tth, d) - ( # C1: Add integer of 5, expect yarray to increase by by 5 + # C1: Test scalar addition to yarray values (intensity), expect no change to xarrays (q, tth, d) + ( # 1. Add integer 5 + "add", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5, np.array([[6.0, 0.51763809, 30.0, 12.13818192], [7.0, 1.0, 60.0, 6.28318531]]), ), - ( # C2: Add float of 5.1, expect yarray to be added by 5.1 + ( # 2. Add float 5.1 + "add", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5.1, np.array([[6.1, 0.51763809, 30.0, 12.13818192], [7.1, 1.0, 60.0, 6.28318531]]), ), + # C2. Test scalar subtraction to yarray values (intensity), expect no change to xarrays (q, tth, d) + ( # 1. Subtract integer 1 + "sub", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 1, + np.array([[0.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + ), + ( # 2. Subtract float 0.5 + "sub", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 0.5, + np.array([[0.5, 0.51763809, 30.0, 12.13818192], [1.5, 1.0, 60.0, 6.28318531]]), + ), ], ) -def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expected_all_arrays, do_minimal_tth): +def test_scalar_operations(operation, starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - do_scalar_right_sum = do + scalar_to_add - assert np.allclose(do_scalar_right_sum.all_arrays, expected_all_arrays) - do_scalar_left_sum = scalar_to_add + do - assert np.allclose(do_scalar_left_sum.all_arrays, expected_all_arrays) + + if operation == "add": + result_right = do + scalar_value + result_left = scalar_value + do + elif operation == "sub": + result_right = do - scalar_value + result_left = scalar_value - do + + assert np.allclose(result_right.all_arrays, expected_all_arrays) + assert np.allclose(result_left.all_arrays, expected_all_arrays) @pytest.mark.parametrize( - "do_1_all_arrays, " - "do_2_all_arrays, " - "expected_do_1_all_arrays_with_y_summed, " - "expected_do_2_all_arrays_with_y_summed", + "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", [ - # Test addition of two DO objects, expect combined yarray values and no change to xarrays ((q, tth, d) - ( # C1: Add two DO objects, expect sum of yarray values - (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), - (np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]),), - (np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]),), - (np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]),), + # Test addition of two DO objects, expect combined yarray values + ( + "add", + np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), + ), + # Test subtraction of two DO objects, expect differences in yarray values + ( + "sub", + np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), + np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), ], ) -def test_addition_operator_by_another_do( - do_1_all_arrays, - do_2_all_arrays, - expected_do_1_all_arrays_with_y_summed, - expected_do_2_all_arrays_with_y_summed, +def test_binary_operator_on_do( + operation, + expected_do_1_all_arrays_with_y_modified, + expected_do_2_all_arrays_with_y_modified, do_minimal_tth, do_minimal_d, ): do_1 = do_minimal_tth - assert np.allclose(do_1.all_arrays, do_1_all_arrays) do_2 = do_minimal_d - assert np.allclose(do_2.all_arrays, do_2_all_arrays) - assert np.allclose((do_1 + do_2).all_arrays, expected_do_1_all_arrays_with_y_summed) - assert np.allclose((do_2 + do_1).all_arrays, expected_do_2_all_arrays_with_y_summed) + assert np.allclose( + do_1.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) + ) + assert np.allclose( + do_2.all_arrays, np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]) + ) + + if operation == "add": + do_1_y_modified = do_1 + do_2 + do_2_y_modified = do_2 + do_1 + elif operation == "sub": + do_1_y_modified = do_1 - do_2 + do_2_y_modified = do_2 - do_1 + + assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) + assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @@ -775,6 +809,10 @@ def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_m do + "string_value" with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): "string_value" + do + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + do - "string_value" + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + "string_value" - do def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): @@ -785,3 +823,5 @@ def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_g assert len(do_2.all_arrays[:, 0]) == 2 with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): do_1 + do_2 + with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): + do_1 - do_2 From ddeeb266a7b9e60e0e5b40e1059b43cab3d0d954 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:17:43 -0500 Subject: [PATCH 343/445] feat: implement __mul__ for DiffracitonObject wih test funcs --- src/diffpy/utils/diffraction_objects.py | 19 +++++++------------ tests/test_diffraction_objects.py | 25 ++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 58393ef4..1a88a147 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -229,18 +229,13 @@ def __sub__(self, other): __rsub__ = __sub__ def __mul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to multiply two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied + self._check_operation_compatibility(other) + multiplied_do = deepcopy(self) + if isinstance(other, (int, float)): + multiplied_do._all_arrays[:, 0] *= other + if isinstance(other, DiffractionObject): + multiplied_do._all_arrays[:, 0] *= other.all_arrays[:, 0] + return multiplied_do __rmul__ = __mul__ diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 979a7efe..a21ed57d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -741,18 +741,33 @@ def test_copy_object(do_minimal): 0.5, np.array([[0.5, 0.51763809, 30.0, 12.13818192], [1.5, 1.0, 60.0, 6.28318531]]), ), + # C2. Test scalar multiplication to yarray values (intensity), expect no change to xarrays (q, tth, d) + ( # 1. Multipliy by integer 2 + "mul", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 2, + np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + ), + ( # 2. Multipliy by float 0.5 + "mul", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 2.5, + np.array([[2.5, 0.51763809, 30.0, 12.13818192], [5.0, 1.0, 60.0, 6.28318531]]), + ), ], ) def test_scalar_operations(operation, starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - if operation == "add": result_right = do + scalar_value result_left = scalar_value + do elif operation == "sub": result_right = do - scalar_value result_left = scalar_value - do + elif operation == "mul": + result_right = do * scalar_value + result_left = scalar_value * do assert np.allclose(result_right.all_arrays, expected_all_arrays) assert np.allclose(result_left.all_arrays, expected_all_arrays) @@ -773,6 +788,11 @@ def test_scalar_operations(operation, starting_all_arrays, scalar_value, expecte np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), + ( + "mul", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), + ), ], ) def test_binary_operator_on_do( @@ -797,6 +817,9 @@ def test_binary_operator_on_do( elif operation == "sub": do_1_y_modified = do_1 - do_2 do_2_y_modified = do_2 - do_1 + elif operation == "mul": + do_1_y_modified = do_1 * do_2 + do_2_y_modified = do_2 * do_1 assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) From eb2941b4e0613aabbb7d182d80b47602796ab952 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:42:53 -0500 Subject: [PATCH 344/445] feat: add __div__ function to DiffractionObject --- src/diffpy/utils/diffraction_objects.py | 22 ++-- tests/test_diffraction_objects.py | 139 +++++++++++++++--------- 2 files changed, 97 insertions(+), 64 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 1a88a147..477b1f2c 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -240,21 +240,15 @@ def __mul__(self, other): __rmul__ = __mul__ def __truediv__(self, other): + self._check_operation_compatibility(other) + divided_do = deepcopy(self) + if isinstance(other, (int, float)): + divided_do._all_arrays[:, 0] /= other + if isinstance(other, DiffractionObject): + divided_do._all_arrays[:, 0] /= other.all_arrays[:, 0] + return divided_do - divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - divided.on_tth[1] = other / self.on_tth[1] - divided.on_q[1] = other / self.on_q[1] - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to multiply two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(y_grid_length_mismatch_emsg) - else: - divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] - divided.on_q[1] = self.on_q[1] / other.on_q[1] - return divided - - __rmul__ = __mul__ + __rtruediv__ = __truediv__ def _check_operation_compatibility(self, other): if not isinstance(other, (DiffractionObject, int, float)): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index a21ed57d..6773aced 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -713,64 +713,84 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( - "operation, starting_all_arrays, scalar_value, expected_all_arrays", + "operation, starting_yarray, scalar_value, expected_yarray", [ - # C1: Test scalar addition to yarray values (intensity), expect no change to xarrays (q, tth, d) - ( # 1. Add integer 5 + # C1: Test scalar addition to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Add 5 "add", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 5, - np.array([[6.0, 0.51763809, 30.0, 12.13818192], [7.0, 1.0, 60.0, 6.28318531]]), + np.array([6.0, 7.0]), ), - ( # 2. Add float 5.1 + ( # 2. Add 5.1 "add", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 5.1, - np.array([[6.1, 0.51763809, 30.0, 12.13818192], [7.1, 1.0, 60.0, 6.28318531]]), + np.array([6.1, 7.1]), ), - # C2. Test scalar subtraction to yarray values (intensity), expect no change to xarrays (q, tth, d) - ( # 1. Subtract integer 1 + # C2: Test scalar subtraction to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Subtract 1 "sub", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 1, - np.array([[0.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + np.array([0.0, 1.0]), ), - ( # 2. Subtract float 0.5 + ( # 2. Subtract 0.5 "sub", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 0.5, - np.array([[0.5, 0.51763809, 30.0, 12.13818192], [1.5, 1.0, 60.0, 6.28318531]]), + np.array([0.5, 1.5]), ), - # C2. Test scalar multiplication to yarray values (intensity), expect no change to xarrays (q, tth, d) - ( # 1. Multipliy by integer 2 + # C3: Test scalar multiplication to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Multiply by 2 "mul", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 2, - np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array([2.0, 4.0]), ), - ( # 2. Multipliy by float 0.5 + ( # 2. Multiply by 2.5 "mul", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 2.5, - np.array([[2.5, 0.51763809, 30.0, 12.13818192], [5.0, 1.0, 60.0, 6.28318531]]), + np.array([2.5, 5.0]), + ), + # C4: Test scalar division to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Divide by 2 + "div", + np.array([1.0, 2.0]), + 2, + np.array([0.5, 1.0]), + ), + ( # 2. Divide by 2.5 + "div", + np.array([1.0, 2.0]), + 2.5, + np.array([0.4, 0.8]), ), ], ) -def test_scalar_operations(operation, starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): +def test_scalar_operations(operation, starting_yarray, scalar_value, expected_yarray, do_minimal_tth): do = do_minimal_tth - assert np.allclose(do.all_arrays, starting_all_arrays) + expected_xarray_constant = np.array([[0.51763809, 30.0, 12.13818192], [1.0, 60.0, 6.28318531]]) + assert np.allclose(do.all_arrays[:, [1, 2, 3]], expected_xarray_constant) + assert np.allclose(do.all_arrays[:, 0], starting_yarray) if operation == "add": - result_right = do + scalar_value - result_left = scalar_value + do + do_right_op = do + scalar_value + do_left_op = scalar_value + do elif operation == "sub": - result_right = do - scalar_value - result_left = scalar_value - do + do_right_op = do - scalar_value + do_left_op = scalar_value - do elif operation == "mul": - result_right = do * scalar_value - result_left = scalar_value * do - - assert np.allclose(result_right.all_arrays, expected_all_arrays) - assert np.allclose(result_left.all_arrays, expected_all_arrays) + do_right_op = do * scalar_value + do_left_op = scalar_value * do + elif operation == "div": + do_right_op = do / scalar_value + do_left_op = scalar_value / do + assert np.allclose(do_right_op.all_arrays[:, 0], expected_yarray) + assert np.allclose(do_left_op.all_arrays[:, 0], expected_yarray) + # Ensure x-values are unchanged + assert np.allclose(do_right_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant) + assert np.allclose(do_left_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant) @pytest.mark.parametrize( @@ -793,6 +813,11 @@ def test_scalar_operations(operation, starting_all_arrays, scalar_value, expecte np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), ), + ( + "div", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + np.array([[1.0, 6.28318531, 100.70777771, 1], [1.0, 3.14159265, 45.28748053, 2.0]]), + ), ], ) def test_binary_operator_on_do( @@ -820,31 +845,45 @@ def test_binary_operator_on_do( elif operation == "mul": do_1_y_modified = do_1 * do_2 do_2_y_modified = do_2 * do_1 + elif operation == "div": + do_1_y_modified = do_1 / do_2 + do_2_y_modified = do_2 / do_1 assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) -def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): - # Add a string to a DO object, expect TypeError, only scalar (int, float) allowed for addition +def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): do = do_minimal_tth - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - do + "string_value" - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - "string_value" + do - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - do - "string_value" - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - "string_value" - do - - -def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): - # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays + invalid_value = "string_value" + + operations = [ + (lambda x, y: x + y), # Test addition + (lambda x, y: x - y), # Test subtraction + (lambda x, y: x * y), # Test multiplication + (lambda x, y: x / y), # Test division + ] + + # Test each operation with both orderings of operands + for operation in operations: + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + operation(do, invalid_value) + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + operation(invalid_value, do) + + +@pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"]) +def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): do_1 = do_minimal do_2 = do_minimal_tth assert len(do_1.all_arrays[:, 0]) == 0 assert len(do_2.all_arrays[:, 0]) == 2 with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): - do_1 + do_2 - with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): - do_1 - do_2 + if operation == "add": + do_1 + do_2 + elif operation == "sub": + do_1 - do_2 + elif operation == "mul": + do_1 * do_2 + elif operation == "div": + do_1 / do_2 From 48e4c5883a4d50782c1b0bdb6f22c6eb67ae04ee Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:45:20 -0500 Subject: [PATCH 345/445] chore: add news --- news/op-mul-sub-div.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/op-mul-sub-div.rst diff --git a/news/op-mul-sub-div.rst b/news/op-mul-sub-div.rst new file mode 100644 index 00000000..9fe75ccc --- /dev/null +++ b/news/op-mul-sub-div.rst @@ -0,0 +1,23 @@ +**Added:** + +* addition, multiplication, subtraction, and division operators between two DiffractionObject instances or a scalar value with another DiffractionObject for modifying yarray (intensity) values. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From e8653efc5613e319e30478aa7acd0c13b7de9cec Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:49:40 -0500 Subject: [PATCH 346/445] test: improve comments for tests --- tests/test_diffraction_objects.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6773aced..6c390e16 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -796,24 +796,23 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya @pytest.mark.parametrize( "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", [ - # Test addition of two DO objects, expect combined yarray values - ( + ( # Test addition of two DO objects, expect combined yarray values "add", np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), ), - # Test subtraction of two DO objects, expect differences in yarray values - ( + ( # Test subtraction of two DO objects, expect differences in yarray values "sub", np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), - ( + ( # Test multiplication of two DO objects, expect multiplication in yarray values + "mul", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), ), - ( + ( # Test division of two DO objects, expect division in yarray values "div", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [1.0, 3.14159265, 45.28748053, 2.0]]), @@ -856,15 +855,13 @@ def test_binary_operator_on_do( def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): do = do_minimal_tth invalid_value = "string_value" - operations = [ (lambda x, y: x + y), # Test addition (lambda x, y: x - y), # Test subtraction (lambda x, y: x * y), # Test multiplication (lambda x, y: x / y), # Test division ] - - # Test each operation with both orderings of operands + # Add a string to a DiffractionObject, expect TypeError for operation in operations: with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): operation(do, invalid_value) @@ -878,6 +875,7 @@ def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y do_2 = do_minimal_tth assert len(do_1.all_arrays[:, 0]) == 0 assert len(do_2.all_arrays[:, 0]) == 2 + # Add two DO objets with different yarray lengths, expect ValueError with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): if operation == "add": do_1 + do_2 From 116280c1a8dfef0197b90f06cf3a6f3ce9719f52 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Dec 2024 23:50:46 +0000 Subject: [PATCH 347/445] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6c390e16..067fce5d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -807,7 +807,6 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), ( # Test multiplication of two DO objects, expect multiplication in yarray values - "mul", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), From 6656dabc93ffd54ebcc770fab17e8124da9bc5c7 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:53:22 -0500 Subject: [PATCH 348/445] test: add higher-level test comments --- tests/test_diffraction_objects.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 067fce5d..17f7ca19 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -715,6 +715,7 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( "operation, starting_yarray, scalar_value, expected_yarray", [ + # Test scalar addition, subtraction, multiplication, and division to y-values by adding a scalar value # C1: Test scalar addition to y-values (intensity), expect no change to x-values (q, tth, d) ( # 1. Add 5 "add", @@ -796,6 +797,7 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya @pytest.mark.parametrize( "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", [ + # Test addition, subtraction, multiplication, and division of two DO objects ( # Test addition of two DO objects, expect combined yarray values "add", np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), @@ -852,6 +854,7 @@ def test_binary_operator_on_do( def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): + # Add a string to a DiffractionObject, expect TypeError do = do_minimal_tth invalid_value = "string_value" operations = [ @@ -860,7 +863,6 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): (lambda x, y: x * y), # Test multiplication (lambda x, y: x / y), # Test division ] - # Add a string to a DiffractionObject, expect TypeError for operation in operations: with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): operation(do, invalid_value) @@ -870,11 +872,11 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"]) def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): + # Add two DO objects with different yarray lengths, expect ValueError do_1 = do_minimal do_2 = do_minimal_tth assert len(do_1.all_arrays[:, 0]) == 0 assert len(do_2.all_arrays[:, 0]) == 2 - # Add two DO objets with different yarray lengths, expect ValueError with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): if operation == "add": do_1 + do_2 From b64e224685282c466029d02699ed724bb26b71d2 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 29 Dec 2024 21:03:29 -0500 Subject: [PATCH 349/445] extend news --- news/muD_calculator.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/news/muD_calculator.rst b/news/muD_calculator.rst index fc21cef3..7534c4ff 100644 --- a/news/muD_calculator.rst +++ b/news/muD_calculator.rst @@ -1,6 +1,7 @@ **Added:** -* function to compute muD from a given z-scan file +* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile + from a line-scan through the sample **Changed:** From a23268644f2275bc1c4a7b3408f3c27bc8867497 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 02:03:37 +0000 Subject: [PATCH 350/445] [pre-commit.ci] auto fixes from pre-commit hooks --- news/muD_calculator.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/muD_calculator.rst b/news/muD_calculator.rst index 7534c4ff..a6731760 100644 --- a/news/muD_calculator.rst +++ b/news/muD_calculator.rst @@ -1,6 +1,6 @@ **Added:** -* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile +* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile from a line-scan through the sample **Changed:** From 13f227dfb03bfc7b7e20b4fbc3d31891d615e689 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 21:04:49 -0500 Subject: [PATCH 351/445] docs: improve docstring --- src/diffpy/utils/diffraction_objects.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 80202cd3..3ce01346 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -408,22 +408,22 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): Parameters ---------- target_diff_object: DiffractionObject - the diffraction object you want to scale the current one onto + The diffraction object you want to scale the current one onto. - q, tth, d : float, optional, default is the max q-value from each object + q, tth, d : float, optional, default is None The value of the x-array where you want the curves to line up vertically. Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. offset : float, optional, default is None - an offset to add to the scaled y-values + The offset to add to the scaled y-values. Returns ------- - the rescaled DiffractionObject as a new object + scaled : DiffractionObject + The rescaled DiffractionObject as a new object. """ if offset is None: offset = 0 - scaled = self.copy() count = sum([q is not None, tth is not None, d is not None]) if count > 1: From 5492ad8718b46854c8fc0530e9f1d28be4dba89e Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 21:08:09 -0500 Subject: [PATCH 352/445] test: remove test for the correct mu, improve test comment for invalid inputs --- tests/test_tools.py | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index d02996ca..d3f7cf16 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -167,35 +167,11 @@ def test_get_package_info(monkeypatch, inputs, expected): assert actual_metadata == expected -@pytest.mark.parametrize( - "inputs, expected_mu", - [ - # Test whether the function returns the correct mu - ( # C1: Composition, energy, and mass density provided, expect to get mu based on mass density - # 1. Fully dense mass density - {"sample_composition": "quartz", "energy": 10, "sample_mass_density": 2.65}, - 5.0368, - ), - ( # 2. Measured mass density - { - "sample_composition": "ZrO2", - "energy": 17.445, - "sample_mass_density": 1.009, - }, - 1.2522, - ), - ], -) -def test_compute_mu_using_xraydb(inputs, expected_mu): - actual_mu = compute_mu_using_xraydb(**inputs) - assert actual_mu == pytest.approx(expected_mu, rel=1e-6, abs=1e-4) - - @pytest.mark.parametrize( "inputs", [ - # Test when the function raises ValueError - # C1: Both mass density and packing fraction are provided + # Test when the function has invalid inputs + # C1: Both mass density and packing fraction are provided, expect ValueError exception ( { "sample_composition": "quartz", @@ -204,7 +180,7 @@ def test_compute_mu_using_xraydb(inputs, expected_mu): "packing_fraction": 1, } ), - # C2: None of mass density or packing fraction are provided + # C2: None of mass density or packing fraction are provided, expect ValueError exception ( { "sample_composition": "quartz", From 21711fb8a3d15030e5b74d63ccbfe09830395acf Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 29 Dec 2024 21:16:25 -0500 Subject: [PATCH 353/445] fix: shorten all_arrays shape check --- src/diffpy/utils/diffraction_objects.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 477b1f2c..bf53a5ac 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -254,9 +254,7 @@ def _check_operation_compatibility(self, other): if not isinstance(other, (DiffractionObject, int, float)): raise TypeError(invalid_add_type_emsg) if isinstance(other, DiffractionObject): - self_yarray = self.all_arrays[:, 0] - other_yarray = other.all_arrays[:, 0] - if self_yarray.shape != other_yarray.shape: + if self.all_arrays.shape != other.all_arrays.shape: raise ValueError(y_grid_length_mismatch_emsg) @property From a4a217d482af895970ea4934745b95fc45bd6497 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 21:29:17 -0500 Subject: [PATCH 354/445] test: reformat tests --- tests/test_tools.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index d3f7cf16..91bf63bb 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -171,19 +171,17 @@ def test_get_package_info(monkeypatch, inputs, expected): "inputs", [ # Test when the function has invalid inputs - # C1: Both mass density and packing fraction are provided, expect ValueError exception - ( + ( # C1: Both mass density and packing fraction are provided, expect ValueError exception { - "sample_composition": "quartz", + "sample_composition": "SiO2", "energy": 10, "sample_mass_density": 2.65, "packing_fraction": 1, } ), - # C2: None of mass density or packing fraction are provided, expect ValueError exception - ( + ( # C2: None of mass density or packing fraction are provided, expect ValueError exception { - "sample_composition": "quartz", + "sample_composition": "SiO2", "energy": 10, } ), From cdf4b49e45ce7f32023633fce6dcf397385e3280 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 29 Dec 2024 21:33:19 -0500 Subject: [PATCH 355/445] doc: small tweak to description of scale_to --- doc/source/examples/diffraction_objects_example.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index f956081c..4494fcfc 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -104,10 +104,9 @@ we would replace the code above with plt.show() The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new -variable and make use of, +variable and make use of. -The default behavior is to align the objects based on the maximal q-value of each diffraction object, -so they will align at the intensity at these indices. +The default behavior is to align the objects based on the maximal value of each diffraction object. .. code-block:: python From dd52ad9b401eb9475fccfb57ee09297a02bafa58 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 21:48:37 -0500 Subject: [PATCH 356/445] docs: improve docstring --- src/diffpy/utils/tools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index c562a2dd..036cc696 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -218,13 +218,14 @@ def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None Parameters ---------- sample_composition : str - The chemical formula or the name of the material. + The chemical formula of the material. energy : float The energy of the incident x-rays in keV. sample_mass_density : float, optional, Default is None - The mass density of the packed powder/sample in gr/cm^3. + The mass density of the packed powder/sample in g/cm*3. packing_fraction : float, optional, Default is None The fraction of sample in the capillary (between 0 and 1). + Specify either sample_mass_density or packing_fraction but not both. Returns ------- From 27da4ded721f3d0978745ca9f7bcc4d567b88aca Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 22:19:06 -0500 Subject: [PATCH 357/445] feat: add offset to default behavior and test case --- src/diffpy/utils/diffraction_objects.py | 4 +++- tests/test_diffraction_objects.py | 31 +++++++++++++++++++------ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 3ce01346..a404b155 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -433,7 +433,9 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): ) if count == 0: - scaled._all_arrays[:, 0] *= max(target_diff_object.on_q()[1]) / max(self.on_q()[1]) + q_target_max = max(target_diff_object.on_q()[1]) + q_self_max = max(self.on_q()[1]) + scaled._all_arrays[:, 0] = scaled._all_arrays[:, 0] * q_target_max / q_self_max + offset return scaled xtype = "q" if q is not None else "tth" if tth is not None else "d" diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6499bdd7..455dc41c 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -264,21 +264,38 @@ def test_init_invalid_xtype(): {"tth": 60}, {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), - ( # C5: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1 + ( # C5.1: Reuse test case from C1, none of q, tth, d, provided, but include an offset, + # expect scaled y-array in C1 to shift up by 2 { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xarray": np.array([0.1, 0.2, 0.3]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([0.05, 0.1, 0.2, 0.3]), + "yarray": np.array([5, 10, 20, 30]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + {"offset": 2}, + {"xtype": "q", "yarray": np.array([12, 22, 32])}, + ), + ( # C5.2: Reuse test case from C4, but include an offset, expect scaled y-array in C4 to shift up by 2 + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), "xtype": "tth", "wavelength": 2 * np.pi, }, { - "xarray": np.array([10, 15, 25, 30, 60, 140]), - "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), "xtype": "tth", "wavelength": 2 * np.pi, }, - {"tth": 60, "offset": 2.1}, - {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, + {"tth": 60, "offset": 2}, + {"xtype": "tth", "yarray": np.array([3, 4, 5, 6, 7, 8, 12])}, ), ], ) From 0691e545f1680b45461e357277de5556d079a8da Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 29 Dec 2024 22:23:19 -0500 Subject: [PATCH 358/445] docs: improve docstring and test comment --- src/diffpy/utils/diffraction_objects.py | 2 +- tests/test_diffraction_objects.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a404b155..1b32e1d7 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -400,7 +400,7 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): """Returns a new diffraction object which is the current object but rescaled in y to the target. - By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max q-value from each object. + By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max intensity from each object. Otherwise, y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 455dc41c..9f0d9a0d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -191,7 +191,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether the original y-array is scaled as expected - ( # C1: none of q, tth, d, provided, expect to scale on the maximal x-arrays + ( # C1: none of q, tth, d, provided, expect to scale on the maximal intensity from each object { "xarray": np.array([0.1, 0.2, 0.3]), "yarray": np.array([1, 2, 3]), From 6325cf6d7e9218bafc9e5379018ff47d7993c39d Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 30 Dec 2024 11:54:22 -0500 Subject: [PATCH 359/445] docs: add examples for operations --- .../examples/diffraction_objects_example.rst | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 4494fcfc..0e4e5b55 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -129,14 +129,14 @@ For convenience, you can also apply an offset to the scaled new diffraction obje DiffractionObject convenience functions --------------------------------------- -1) create a copy of a diffraction object using the ``copy`` method +1. create a copy of a diffraction object using the ``copy`` method when you want to preserve the original data while working with a modified version. .. code-block:: python copy_of_calculated = calculated.copy() -2) test the equality of two diffraction objects. For example, +2. test the equality of two diffraction objects. For example, .. code-block:: python @@ -145,15 +145,38 @@ DiffractionObject convenience functions will return ``True``. -3) make arithmetic operations on the intensities of diffraction objects. e.g., +3. make arithmetic operations on the intensities of diffraction objects. +For example, you can do scalar operations on a single diffraction object, +which will modify the intensity values (``yarrays``) without affecting other properties: .. code-block:: python - doubled_object = 2 * diff_object1 # Double the intensities - sum_object = diff_object1 + diff_object2 # Sum the intensities - subtract_scaled = diff_object1 - 5 * diff_object2 # subtract 5 * obj2 from obj 1 + increased_intensity = diff_object1 + 5 # Increases the intensities by 5 + decreased_intensity = diff_object1 - 1 # Decreases the intensities by 1 + doubled_object = 2 * diff_object1 # Double the intensities + reduced_intensity = diff_object1 / 2 # Halves the intensities -4) get the value of the DiffractionObject at a given point in one of the xarrays +You can also do binary operations between two diffraction objects, as long as their yarrays have the same length. +The operation will apply to the intensity values, while other properties +(such as ``xarrays``, ``xtype``, and ``metadata``) will be inherited +from the left-hand side diffraction object (``diff_object1``). +For example: + +.. code-block:: python + + sum_object = diff_object1 + diff_object2 # Sum the intensities + subtract_scaled = diff_object1 - 5 * diff_object2 # Subtract 5 * obj2 from obj 1 + multiplied_object = diff_object1 * diff_object2 # Multiply the intensities + divided_object = diff_object1 / diff_object2 # Divide the intensities + +You cannot perform operations between diffraction objects and incompatible types. +For example, attempting to add a diffraction object and a string will raise an error: + +.. code-block:: python + + diff_object1 + "string_value" # This will raise an error + +4. get the value of the DiffractionObject at a given point in one of the xarrays .. code-block:: python @@ -170,7 +193,7 @@ you can find its closest index for ``q=0.25`` by typing either of the following: index = do.get_array_index(0.25) # no xtype passed, defaults to do._input_xtype, or in this example, q index = do.get_array_index(0.25, xtype="q") # explicitly choose an xtype to specify a value -5) The ``dump`` function saves the diffraction data and relevant information to an xy format file with headers +5. The ``dump`` function saves the diffraction data and relevant information to an xy format file with headers (widely used chi format used, for example, by Fit2D and diffpy. These files can be read by ``LoadData()`` in ``diffpy.utils.parsers``). From 0865819658482dba27a85d845181f92062ca1c10 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 30 Dec 2024 11:57:14 -0500 Subject: [PATCH 360/445] docs: no news added --- news/docs-do.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/docs-do.rst diff --git a/news/docs-do.rst b/news/docs-do.rst new file mode 100644 index 00000000..21faf520 --- /dev/null +++ b/news/docs-do.rst @@ -0,0 +1,23 @@ +**Added:** + +* no news added: adding documentation for diffraction object operation examples + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From cfb7362b787380b58b0b11cb743f8728229958be Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 30 Dec 2024 15:08:20 -0500 Subject: [PATCH 361/445] docs: fix error --- doc/source/examples/diffraction_objects_example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/examples/diffraction_objects_example.rst b/doc/source/examples/diffraction_objects_example.rst index 0e4e5b55..1122bf21 100644 --- a/doc/source/examples/diffraction_objects_example.rst +++ b/doc/source/examples/diffraction_objects_example.rst @@ -156,7 +156,7 @@ which will modify the intensity values (``yarrays``) without affecting other pro doubled_object = 2 * diff_object1 # Double the intensities reduced_intensity = diff_object1 / 2 # Halves the intensities -You can also do binary operations between two diffraction objects, as long as their yarrays have the same length. +You can also do binary operations between two diffraction objects, as long as they are on the same ``q/tth/d-array``. The operation will apply to the intensity values, while other properties (such as ``xarrays``, ``xtype``, and ``metadata``) will be inherited from the left-hand side diffraction object (``diff_object1``). From 98c65dd62435ab3e53839ffe7126f8bf8bba5c5c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 18:53:16 -0500 Subject: [PATCH 362/445] test: ensure we compare the values of xarrays before applying operations between 2 DO --- news/no-news.rst | 23 +++++++++++++ src/diffpy/utils/diffraction_objects.py | 11 ++++--- tests/conftest.py | 7 ++-- tests/test_diffraction_objects.py | 43 +++++++++++++++++-------- 4 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 news/no-news.rst diff --git a/news/no-news.rst b/news/no-news.rst new file mode 100644 index 00000000..31779a23 --- /dev/null +++ b/news/no-news.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: just making sure the previous add, mul, div, and sub tests are working as expected. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 9e56e0d3..5c5cd7e2 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -14,9 +14,10 @@ XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] -y_grid_length_mismatch_emsg = ( - "The two objects have different y-array lengths. " - "Please ensure the length of the y-value during initialization is identical." +x_values_not_equal_emsg = ( + "The two objects have different values in x arrays (my_do.all_arrays[:, [1, 2, 3]]). " + "Please ensure the x values of the two objects are identical by re-instantiating " + "the DiffractionObject with the correct x value inputs." ) invalid_add_type_emsg = ( @@ -255,7 +256,9 @@ def _check_operation_compatibility(self, other): raise TypeError(invalid_add_type_emsg) if isinstance(other, DiffractionObject): if self.all_arrays.shape != other.all_arrays.shape: - raise ValueError(y_grid_length_mismatch_emsg) + raise ValueError(x_values_not_equal_emsg) + if not np.allclose(self.all_arrays[:, [1, 2, 3]], other.all_arrays[:, [1, 2, 3]]): + raise ValueError(x_values_not_equal_emsg) @property def all_arrays(self): diff --git a/tests/conftest.py b/tests/conftest.py index 9e5f1e60..296ae14f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -81,8 +81,9 @@ def invalid_add_type_error_msg(): @pytest.fixture -def y_grid_size_mismatch_error_msg(): +def x_values_not_equal_error_msg(): return ( - "The two objects have different y-array lengths. " - "Please ensure the length of the y-value during initialization is identical." + "The two objects have different values in x arrays (my_do.all_arrays[:, [1, 2, 3]]). " + "Please ensure the x values of the two objects are identical by re-instantiating " + "the DiffractionObject with the correct x value inputs." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ff81ff28..13b3719b 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -780,28 +780,28 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya @pytest.mark.parametrize( - "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", + "operation, expected_do_1_all_arrays_with_y_modified, expected_do_2_all_arrays_with_y_modified", [ # Test addition, subtraction, multiplication, and division of two DO objects ( # Test addition of two DO objects, expect combined yarray values "add", np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), - np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), + np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), ), ( # Test subtraction of two DO objects, expect differences in yarray values "sub", np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), - np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), + np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), ), ( # Test multiplication of two DO objects, expect multiplication in yarray values "mul", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), - np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), ), ( # Test division of two DO objects, expect division in yarray values "div", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), - np.array([[1.0, 6.28318531, 100.70777771, 1], [1.0, 3.14159265, 45.28748053, 2.0]]), + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), ), ], ) @@ -810,15 +810,14 @@ def test_binary_operator_on_do( expected_do_1_all_arrays_with_y_modified, expected_do_2_all_arrays_with_y_modified, do_minimal_tth, - do_minimal_d, ): do_1 = do_minimal_tth - do_2 = do_minimal_d + do_2 = do_minimal_tth assert np.allclose( do_1.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) ) assert np.allclose( - do_2.all_arrays, np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]) + do_2.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) ) if operation == "add": @@ -856,13 +855,31 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"]) -def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): - # Add two DO objects with different yarray lengths, expect ValueError +def test_operator_invalid_xarray_values_not_equal( + operation, do_minimal_tth, do_minimal_d, x_values_not_equal_error_msg +): + # Add two DO objects with different xarray values but equal in shape, expect ValueError + do_1 = do_minimal_tth + do_2 = do_minimal_d + with pytest.raises(ValueError, match=re.escape(x_values_not_equal_error_msg)): + if operation == "add": + do_1 + do_2 + elif operation == "sub": + do_1 - do_2 + elif operation == "mul": + do_1 * do_2 + elif operation == "div": + do_1 / do_2 + + +@pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"]) +def test_operator_invalid_xarray_shape_not_equal( + operation, do_minimal, do_minimal_tth, x_values_not_equal_error_msg +): + # Add two DO objects with different xarrays shape, expect ValueError do_1 = do_minimal do_2 = do_minimal_tth - assert len(do_1.all_arrays[:, 0]) == 0 - assert len(do_2.all_arrays[:, 0]) == 2 - with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): + with pytest.raises(ValueError, match=re.escape(x_values_not_equal_error_msg)): if operation == "add": do_1 + do_2 elif operation == "sub": From 13af9e2a2470778d5f57eec634f6df0cc1c527d0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:03:43 -0500 Subject: [PATCH 363/445] docs: improve docstring based on group standards in diffraction_objects.py transforms.py --- doc/source/api/diffpy.utils.parsers.rst | 16 ++-- doc/source/api/diffpy.utils.rst | 26 ++++-- news/docstrings-refactor.rst | 23 +++++ src/diffpy/utils/diffraction_objects.py | 119 +++++++++++++++++------- src/diffpy/utils/tools.py | 39 ++++---- src/diffpy/utils/transforms.py | 56 +++++------ 6 files changed, 180 insertions(+), 99 deletions(-) create mode 100644 news/docstrings-refactor.rst diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index 3456f24e..29ec4782 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -11,14 +11,6 @@ diffpy.utils.parsers package Submodules ---------- -diffpy.utils.parsers.serialization module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.parsers.serialization - :members: - :undoc-members: - :show-inheritance: - diffpy.utils.parsers.loaddata module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,3 +26,11 @@ diffpy.utils.parsers.custom_exceptions module :members: :undoc-members: :show-inheritance: + +diffpy.utils.parsers.serialization module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.parsers.serialization + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 2513e821..8073d8e6 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -16,30 +16,28 @@ Subpackages diffpy.utils.parsers diffpy.utils.wx - diffpy.utils.scattering_objects Submodules ---------- -diffpy.utils.tools module -^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.transforms module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.tools +.. automodule:: diffpy.utils.transforms :members: :undoc-members: :show-inheritance: -diffpy.utils.resampler module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.tools module +^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.resampler +.. automodule:: diffpy.utils.tools :members: :undoc-members: :show-inheritance: - diffpy.utils.user_config module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.user_config :members: @@ -47,9 +45,17 @@ diffpy.utils.user_config module :show-inheritance: diffpy.utils.diffraction_objects module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.diffraction_objects :members: :undoc-members: :show-inheritance: + +diffpy.utils.resampler module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.resampler + :members: + :undoc-members: + :show-inheritance: diff --git a/news/docstrings-refactor.rst b/news/docstrings-refactor.rst new file mode 100644 index 00000000..0dca3d1d --- /dev/null +++ b/news/docstrings-refactor.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: this branch was used to refactor the docstrings in the codebase without adding new features + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 9e56e0d3..ce43a831 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -92,18 +92,18 @@ def __init__( 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 {}. + wavelength : float, optional, default is None. + The wavelength of the incoming beam, specified in angstroms (Å) + scat_quantity : str, optional, default is an empty string "". + The type of scattering experiment (e.g., "x-ray", "neutron"). + name : str, optional, default is an empty string "". + The name or label for the scattering data. + metadata : dict, optional, default is an empty dictionary {} + The additional metadata associated with the diffraction object. Examples -------- - Create a DiffractionObject for X-ray scattering data + Create a DiffractionObject for X-ray scattering data: >>> import numpy as np >>> from diffpy.utils.diffraction_objects import DiffractionObject ... @@ -180,30 +180,32 @@ def __add__(self, other): Parameters ---------- - other : DiffractionObject or int or float - The object to add to the current DiffractionObject. If `other` is a scalar value, - it will be added to all yarray. The length of the yarray must match if `other` is - an instance of DiffractionObject. + other : DiffractionObject, int, or float + The item to be added. If `other` is a scalar value, this value will be added to each element of the + yarray of this DiffractionObject instance. If `other` is another DiffractionObject, the yarrays of the + two DiffractionObjects will be combined element-wise. The result is a new DiffractionObject instance, + representing the addition and using the xarray from the left-hand side DiffractionObject. Returns ------- DiffractionObject - The new and deep-copied DiffractionObject instance after adding values to the yarray. + THe new DiffractionObject instance with modified yarray values. This instance is a deep copy of the + original with the additions applied. Raises ------ ValueError - Raised when the length of the yarray of the two DiffractionObject instances do not match. + Raised when the xarrays of two DiffractionObject instances are not equal. TypeError - Raised when the type of `other` is not an instance of DiffractionObject, int, or float. + Raised when `other` is not an instance of DiffractionObject, int, or float. Examples -------- - Add a scalar value to the yarray of the DiffractionObject instance: + Add a scalar value to the yarray of a DiffractionObject instance: >>> new_do = my_do + 10.1 >>> new_do = 10.1 + my_do - Add the yarray of two DiffractionObject instances: + Combine the yarrays of two DiffractionObject instances: >>> new_do = my_do_1 + my_do_2 """ @@ -218,6 +220,21 @@ def __add__(self, other): __radd__ = __add__ def __sub__(self, other): + """Subtract scalar value or another DiffractionObject to the yarray of + the DiffractionObject. + + This method behaves similarly to the `__add__` method, but performs subtraction instead of addition. + For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + + Examples + -------- + Subtract a scalar value from the yarray of a DiffractionObject instance: + >>> new_do = my_do - 10.1 + + Subtract the yarrays of two DiffractionObject instances: + >>> new_do = my_do_1 - my_do_2 + """ + self._check_operation_compatibility(other) subtracted_do = deepcopy(self) if isinstance(other, (int, float)): @@ -229,6 +246,21 @@ def __sub__(self, other): __rsub__ = __sub__ def __mul__(self, other): + """Multiply a scalar value or another DiffractionObject with the yarray + of this DiffractionObject. + + This method behaves similarly to the `__add__` method, but performs multiplication instead of addition. + For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + + Examples + -------- + Multiply a scalar value with the yarray of a DiffractionObject instance: + >>> new_do = my_do * 3.5 + + Multiply the yarrays of two DiffractionObject instances: + >>> new_do = my_do_1 * my_do_2 + """ + self._check_operation_compatibility(other) multiplied_do = deepcopy(self) if isinstance(other, (int, float)): @@ -240,6 +272,20 @@ def __mul__(self, other): __rmul__ = __mul__ def __truediv__(self, other): + """Divide the yarray of this DiffractionObject by a scalar value or + another DiffractionObject. + + This method behaves similarly to the `__add__` method, but performs division instead of addition. + For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + + Examples + -------- + Divide the yarray of a DiffractionObject instance by a scalar value: + >>> new_do = my_do / 2.0 + + Divide the yarrays of two DiffractionObject instances: + >>> new_do = my_do_1 / my_do_2 + """ self._check_operation_compatibility(other) divided_do = deepcopy(self) if isinstance(other, (int, float)): @@ -288,7 +334,7 @@ def input_xtype(self): Returns ------- - str + input_xtype : str The type of `xarray`, which must be one of {*XQUANTITIES}. """ return self._input_xtype @@ -303,7 +349,7 @@ def uuid(self): Returns ------- - uuid + uuid : UUID The unique identifier of the DiffractionObject instance. """ return self._uuid @@ -325,7 +371,7 @@ def get_array_index(self, xtype, xvalue): Returns ------- - int + index : int The index of the closest value in the array associated with the specified xtype and the value provided. """ @@ -378,7 +424,7 @@ def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): - """Returns a new diffraction object which is the current object but + """Return a new diffraction object which is the current object but rescaled in y to the target. By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max intensity from each object. @@ -400,12 +446,12 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): Returns ------- - scaled : DiffractionObject + scaled_do : DiffractionObject The rescaled DiffractionObject as a new object. """ if offset is None: offset = 0 - scaled = self.copy() + scaled_do = self.copy() count = sum([q is not None, tth is not None, d is not None]) if count > 1: raise ValueError( @@ -416,8 +462,8 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): if count == 0: q_target_max = max(target_diff_object.on_q()[1]) q_self_max = max(self.on_q()[1]) - scaled._all_arrays[:, 0] = scaled._all_arrays[:, 0] * q_target_max / q_self_max + offset - return scaled + scaled_do._all_arrays[:, 0] = scaled_do._all_arrays[:, 0] * q_target_max / q_self_max + offset + return scaled_do xtype = "q" if q is not None else "tth" if tth is not None else "d" data = self.on_xtype(xtype) @@ -427,21 +473,26 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): xindex_data = (np.abs(data[0] - xvalue)).argmin() xindex_target = (np.abs(target[0] - xvalue)).argmin() - scaled._all_arrays[:, 0] = data[1] * target[1][xindex_target] / data[1][xindex_data] + offset - return scaled + scaled_do._all_arrays[:, 0] = data[1] * target[1][xindex_target] / data[1][xindex_data] + offset + return scaled_do def on_xtype(self, xtype): - """Return a list of two 1D np array with x and y data, raise an error - if the specified xtype is invalid. + """Return a tuple of two 1D numpy arrays containing x and y data. Parameters ---------- - xtype str - the type of quantity for the independent variable from {*XQUANTITIES, } + xtype : str + The type of quantity for the independent variable chosen from {*XQUANTITIES, } + + Raises + ------ + ValueError + Raised when the specified xtype is not among {*XQUANTITIES, } Returns ------- - a list of two 1D np array with x and y data + (xarray, yarray) : tuple of ndarray + A tuple containing two 1D numpy arrays with x and y data for the specified xtype. """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth() @@ -482,6 +533,6 @@ def copy(self): Returns ------- DiffractionObject - A new instance of DiffractionObject, which is a deep copy of the current instance. + The new instance of DiffractionObject, which is a deep copy of the current instance. """ return deepcopy(self) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 036cc696..61868072 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -11,20 +11,20 @@ from diffpy.utils.parsers.loaddata import loadData -def _stringify(obj): +def _stringify(string_value): """Convert None to an empty string. Parameters ---------- - obj: str - The object to convert. If None, return an empty string. + string_value : str or None + The value to be converted. If None, an empty string is returned. Returns ------- - str or None: - The converted string if obj is not None, otherwise an empty string. + str + The original string if string_value is not None, otherwise an empty string. """ - return obj if obj is not None else "" + return string_value if string_value is not None else "" def _load_config(file_path): @@ -32,12 +32,12 @@ def _load_config(file_path): Parameters ---------- - file_path: Path + file_path : Path The path to the configuration file. Returns ------- - dict: + config : dict The configuration dictionary or {} if the config file does not exist. """ config_file = Path(file_path).resolve() @@ -50,7 +50,7 @@ def _load_config(file_path): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): - """Get name, email and orcid of the owner/user from various sources and + """Get name, email, and orcid of the owner/user from various sources and return it as a metadata dictionary. The function looks for the information in json format configuration files with the name 'diffpyconfig.json'. @@ -71,16 +71,16 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): Parameters ---------- - owner_name: string, optional, default is the value stored in the global or local config file. + owner_name : str, optional, default is the value stored in the global or local config file. The name of the user who will show as owner in the metadata that is stored with the data - owner_email: string, optional, default is the value stored in the global or local config file. + owner_email : str, optional, default is the value stored in the global or local config file. The email of the user/owner - owner_orcid: string, optional, default is the value stored in the global or local config file. + owner_orcid : str, optional, default is the value stored in the global or local config file. The ORCID id of the user/owner Returns ------- - dict: + user_info : dict The dictionary containing username, email and orcid of the user/owner, and any other information stored in the global or local config files. """ @@ -97,7 +97,7 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): def check_and_build_global_config(skip_config_creation=False): - """Checks for a global diffpu config file in user's home directory and + """Check for a global diffpu config file in user's home directory and creates one if it is missing. The file it looks for is called diffpyconfig.json. This can contain anything in json format, but @@ -116,12 +116,13 @@ def check_and_build_global_config(skip_config_creation=False): Parameters ---------- - skip_config_creation: bool, optional, Default is False - The bool that will override the creation workflow even if no config file exists. + skip_config_creation : bool, optional, default is False. + The boolean that will override the creation workflow even if no config file exists. Returns ------- - bool: True if the file exists and False otherwise. + config_exists : bool + The boolean indicating whether the config file exists. """ config_exists = False config_path = Path().home() / "diffpyconfig.json" @@ -168,7 +169,7 @@ def check_and_build_global_config(skip_config_creation=False): def get_package_info(package_names, metadata=None): - """Fetches package version and updates it into (given) metadata. + """Fetch package version and updates it into (given) metadata. Package info stored in metadata as {'package_info': {'package_name': 'version_number'}}. @@ -180,7 +181,7 @@ def get_package_info(package_names, metadata=None): Returns ------- - dict: + metadata : dict The updated metadata dict with package info inserted. """ if metadata is None: diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index f2956879..f3c169a6 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -28,7 +28,7 @@ def _validate_inputs(q, wavelength): raise ValueError(invalid_q_or_d_or_wavelength_emsg) -def q_to_tth(q, wavelength): +def q_to_tth(q, wavelength) -> np.ndarray: r"""Helper function to convert q to two-theta. If wavelength is missing, returns x-values that are integer indexes @@ -47,8 +47,8 @@ def q_to_tth(q, wavelength): Parameters ---------- - q : 1D array - The array of :math:`q` values numpy.array([qs]). + q : ndarray + The 1D array of :math:`q` values numpy.array([qs]). The units of q must be reciprocal of the units of wavelength. wavelength : float @@ -56,8 +56,8 @@ def q_to_tth(q, wavelength): Returns ------- - tth : 1D array - The array of :math:`2\theta` values in degrees numpy.array([tths]). + tth : ndarray + The 1D array of :math:`2\theta` values in degrees numpy.array([tths]). """ _validate_inputs(q, wavelength) q.astype(float) @@ -89,17 +89,17 @@ def tth_to_q(tth, wavelength): Parameters ---------- - tth : 1D array - The array of :math:`2\theta` values np.array([tths]). + tth : ndarray + The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. wavelength : float - Wavelength of the incoming x-rays/neutrons/electrons + The wavelength of the incoming x-rays/neutrons/electrons. Returns ------- - q : 1D array - The array of :math:`q` values np.array([qs]). + q : ndarray + The 1D array of :math:`q` values np.array([qs]). The units for the q-values are the inverse of the units of the provided wavelength. """ tth.astype(float) @@ -122,14 +122,14 @@ def q_to_d(q): Parameters ---------- - q : 1D array - The array of :math:`q` values np.array([qs]). + q : ndarray + The 1D array of :math:`q` values np.array([qs]). The units of q must be reciprocal of the units of wavelength. Returns ------- - d : 1D array - The array of :math:`d` values np.array([ds]). + d : ndarray + The 1D array of :math:`d` values np.array([ds]). """ if 0 in q: print(inf_output_imsg) @@ -145,17 +145,17 @@ def tth_to_d(tth, wavelength): Parameters ---------- - tth : 1D array - The array of :math:`2\theta` values np.array([tths]). + tth : nsarray + The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. wavelength : float - Wavelength of the incoming x-rays/neutrons/electrons + The wavelength of the incoming x-rays/neutrons/electrons. Returns ------- - d : 1D array - The array of :math:`d` values np.array([ds]). + d : nsarray + The 1D array of :math:`d` values np.array([ds]). """ q = tth_to_q(tth, wavelength) d = copy(tth) @@ -174,13 +174,13 @@ def d_to_q(d): Parameters ---------- - d : 1D array - The array of :math:`d` values np.array([ds]). + d : nsarray + The 1D array of :math:`d` values np.array([ds]). Returns ------- - q : 1D array - The array of :math:`q` values np.array([qs]). + q : nsarray + The 1D array of :math:`q` values np.array([qs]). The units of q must be reciprocal of the units of wavelength. """ if 0 in d: @@ -197,16 +197,16 @@ def d_to_tth(d, wavelength): Parameters ---------- - d : 1D array - The array of :math:`d` values np.array([ds]). + d : nsarray + The 1D array of :math:`d` values np.array([ds]). wavelength : float - Wavelength of the incoming x-rays/neutrons/electrons + The wavelength of the incoming x-rays/neutrons/electrons. Returns ------- - tth : 1D array - The array of :math:`2\theta` values np.array([tths]). + tth : nsarray + The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. """ q = d_to_q(d) From ee5f3a3b0fb206b1633f013fa074664accf8af96 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:09:29 -0500 Subject: [PATCH 364/445] chore: fix from a tuple to The tuple in docstring --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ce43a831..cc9a92a6 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -492,7 +492,7 @@ def on_xtype(self, xtype): Returns ------- (xarray, yarray) : tuple of ndarray - A tuple containing two 1D numpy arrays with x and y data for the specified xtype. + The tuple containing two 1D numpy arrays with x and y data for the specified xtype. """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth() From 77c5957ff336b2efaf874d80d4066db7476e0c40 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:27:10 -0500 Subject: [PATCH 365/445] docs: generate docs with auto api --- doc/source/api/diffpy.utils.parsers.rst | 17 +++++++------ doc/source/api/diffpy.utils.rst | 33 ++++++++++++++++++------- doc/source/api/diffpy.utils.wx.rst | 1 + 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index 3456f24e..b08a1840 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -11,14 +11,6 @@ diffpy.utils.parsers package Submodules ---------- -diffpy.utils.parsers.serialization module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.parsers.serialization - :members: - :undoc-members: - :show-inheritance: - diffpy.utils.parsers.loaddata module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,3 +26,12 @@ diffpy.utils.parsers.custom_exceptions module :members: :undoc-members: :show-inheritance: + +diffpy.utils.parsers.serialization module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.parsers.serialization + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 2513e821..128d914d 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -16,30 +16,36 @@ Subpackages diffpy.utils.parsers diffpy.utils.wx - diffpy.utils.scattering_objects Submodules ---------- -diffpy.utils.tools module -^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.transforms module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.tools +.. automodule:: diffpy.utils.transforms :members: :undoc-members: :show-inheritance: -diffpy.utils.resampler module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +diffpy.utils.validators module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.utils.resampler +.. automodule:: diffpy.utils.validators :members: :undoc-members: :show-inheritance: +diffpy.utils.tools module +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.tools + :members: + :undoc-members: + :show-inheritance: diffpy.utils.user_config module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.user_config :members: @@ -47,9 +53,18 @@ diffpy.utils.user_config module :show-inheritance: diffpy.utils.diffraction_objects module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: diffpy.utils.diffraction_objects :members: :undoc-members: :show-inheritance: + +diffpy.utils.resampler module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.utils.resampler + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index 76c89035..60f60599 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -18,3 +18,4 @@ diffpy.utils.wx.gridutils module :members: :undoc-members: :show-inheritance: + From c65eace677d6e5aff1bf816d8bd69d5a5e7663f5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:27:32 -0500 Subject: [PATCH 366/445] refactor: rename isfloat to is_number, in a separate validators.py folder --- src/diffpy/utils/parsers/loaddata.py | 17 ++--------- src/diffpy/utils/validators.py | 45 ++++++++++++++++++++++++++++ tests/test_validators.py | 22 ++++++++++++++ 3 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 src/diffpy/utils/validators.py create mode 100644 tests/test_validators.py diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 39c4b163..8369e516 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -17,6 +17,8 @@ import numpy +from diffpy.utils import validators + def loadData(filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs): """Find and load data from a text file. @@ -139,7 +141,7 @@ def countcolumnsvalues(line): name = hpair[0] value = hpair[1] # check if data value should be stored as float - if isfloat(hpair[1]): + if validators.is_number(hpair[1]): value = float(hpair[1]) hdata.update({name: value}) # continue search for the start of datablock @@ -331,16 +333,3 @@ def _findDataBlocks(self): self.headers.append(header) self.datasets.append(data) return - - -# End of class TextDataLoader - - -def isfloat(s): - """True if s is convertible to float.""" - try: - float(s) - return True - except ValueError: - pass - return False diff --git a/src/diffpy/utils/validators.py b/src/diffpy/utils/validators.py new file mode 100644 index 00000000..0f8cd204 --- /dev/null +++ b/src/diffpy/utils/validators.py @@ -0,0 +1,45 @@ +def is_number(string): + """ + Check if the provided string can be converted to a float. + + Parameters + ---------- + string : str + The string to evaluate for numeric conversion. + + Returns + ------- + bool + The boolean whether `string` can be successfully converted to float. + + Examples + -------- + >>> is_number("3.14") + True + + >>> is_number("-1.23") + True + + >>> is_number("007") + True + + >>> is_number("five") + False + + >>> is_number("3.14.15") + False + + >>> is_number("NaN") + True + + >>> is_number("Infinity") + True + + >>> is_number("Inf") + True + """ + try: + float(string) + return True + except ValueError: + return False \ No newline at end of file diff --git a/tests/test_validators.py b/tests/test_validators.py new file mode 100644 index 00000000..0d732e77 --- /dev/null +++ b/tests/test_validators.py @@ -0,0 +1,22 @@ +from diffpy.utils.validators import is_number +import pytest + +@pytest.mark.parametrize("input,expected", [ + ("3.14", True), # Standard float + ("2", True), # Integer + ("-100", True), # Negative integer + ("-3.14", True), # Negative float + ("0", True), # Zero + ("4.5e-1", True), # Scientific notation + ("abc", False), # Non-numeric string + ("", False), # Empty string + ("3.14.15", False), # Multiple dots + ("2+3", False), # Arithmetic expression + ("NaN", True), # Not a Number (special float value) + ("Infinity", True), # Positive infinity + ("-Infinity", True), # Negative infinity + ("Inf", True), # Positive infinity + ("-Inf", True), # Negative infinity +]) +def test_is_number(input, expected): + assert is_number(input) == expected \ No newline at end of file From 23c32d6ec7a9c5e6431f15793a5eebaa5d21ec39 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:27:38 -0500 Subject: [PATCH 367/445] chore: add news file --- news/is-float.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/is-float.rst diff --git a/news/is-float.rst b/news/is-float.rst new file mode 100644 index 00000000..918600e5 --- /dev/null +++ b/news/is-float.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* Rename the `isfloat` function to `is_number`, and move it to the `diffpy/utils/utilsvalidators.py` directory + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 7d6dbc1cd1573200ee4766d9c9f2d84e707fcd25 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 03:30:08 +0000 Subject: [PATCH 368/445] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/api/diffpy.utils.parsers.rst | 1 - doc/source/api/diffpy.utils.rst | 1 - doc/source/api/diffpy.utils.wx.rst | 1 - src/diffpy/utils/validators.py | 9 +++--- tests/test_validators.py | 43 ++++++++++++++----------- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/doc/source/api/diffpy.utils.parsers.rst b/doc/source/api/diffpy.utils.parsers.rst index b08a1840..29ec4782 100644 --- a/doc/source/api/diffpy.utils.parsers.rst +++ b/doc/source/api/diffpy.utils.parsers.rst @@ -34,4 +34,3 @@ diffpy.utils.parsers.serialization module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.rst b/doc/source/api/diffpy.utils.rst index 128d914d..e4de1d4f 100644 --- a/doc/source/api/diffpy.utils.rst +++ b/doc/source/api/diffpy.utils.rst @@ -67,4 +67,3 @@ diffpy.utils.resampler module :members: :undoc-members: :show-inheritance: - diff --git a/doc/source/api/diffpy.utils.wx.rst b/doc/source/api/diffpy.utils.wx.rst index 60f60599..76c89035 100644 --- a/doc/source/api/diffpy.utils.wx.rst +++ b/doc/source/api/diffpy.utils.wx.rst @@ -18,4 +18,3 @@ diffpy.utils.wx.gridutils module :members: :undoc-members: :show-inheritance: - diff --git a/src/diffpy/utils/validators.py b/src/diffpy/utils/validators.py index 0f8cd204..dba25b70 100644 --- a/src/diffpy/utils/validators.py +++ b/src/diffpy/utils/validators.py @@ -1,6 +1,5 @@ def is_number(string): - """ - Check if the provided string can be converted to a float. + """Check if the provided string can be converted to a float. Parameters ---------- @@ -31,10 +30,10 @@ def is_number(string): >>> is_number("NaN") True - + >>> is_number("Infinity") True - + >>> is_number("Inf") True """ @@ -42,4 +41,4 @@ def is_number(string): float(string) return True except ValueError: - return False \ No newline at end of file + return False diff --git a/tests/test_validators.py b/tests/test_validators.py index 0d732e77..e340d065 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,22 +1,27 @@ -from diffpy.utils.validators import is_number import pytest -@pytest.mark.parametrize("input,expected", [ - ("3.14", True), # Standard float - ("2", True), # Integer - ("-100", True), # Negative integer - ("-3.14", True), # Negative float - ("0", True), # Zero - ("4.5e-1", True), # Scientific notation - ("abc", False), # Non-numeric string - ("", False), # Empty string - ("3.14.15", False), # Multiple dots - ("2+3", False), # Arithmetic expression - ("NaN", True), # Not a Number (special float value) - ("Infinity", True), # Positive infinity - ("-Infinity", True), # Negative infinity - ("Inf", True), # Positive infinity - ("-Inf", True), # Negative infinity -]) +from diffpy.utils.validators import is_number + + +@pytest.mark.parametrize( + "input,expected", + [ + ("3.14", True), # Standard float + ("2", True), # Integer + ("-100", True), # Negative integer + ("-3.14", True), # Negative float + ("0", True), # Zero + ("4.5e-1", True), # Scientific notation + ("abc", False), # Non-numeric string + ("", False), # Empty string + ("3.14.15", False), # Multiple dots + ("2+3", False), # Arithmetic expression + ("NaN", True), # Not a Number (special float value) + ("Infinity", True), # Positive infinity + ("-Infinity", True), # Negative infinity + ("Inf", True), # Positive infinity + ("-Inf", True), # Negative infinity + ], +) def test_is_number(input, expected): - assert is_number(input) == expected \ No newline at end of file + assert is_number(input) == expected From f29ad9195c576447d7ba96a8ee92fd5db0b46803 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:42:19 -0500 Subject: [PATCH 369/445] chore: removed np.nsarray return type, not needed --- src/diffpy/utils/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index f3c169a6..21c069b1 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -28,7 +28,7 @@ def _validate_inputs(q, wavelength): raise ValueError(invalid_q_or_d_or_wavelength_emsg) -def q_to_tth(q, wavelength) -> np.ndarray: +def q_to_tth(q, wavelength): r"""Helper function to convert q to two-theta. If wavelength is missing, returns x-values that are integer indexes From 84dcb4e6256169202f9bb439079f502976147d6b Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 30 Dec 2024 22:46:54 -0500 Subject: [PATCH 370/445] docs: improve description on how int is also a float --- src/diffpy/utils/validators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/diffpy/utils/validators.py b/src/diffpy/utils/validators.py index dba25b70..0f281073 100644 --- a/src/diffpy/utils/validators.py +++ b/src/diffpy/utils/validators.py @@ -1,5 +1,8 @@ def is_number(string): """Check if the provided string can be converted to a float. + + Since integers can be converted to floats, this function will return True for integers as well. + Hence, we can use this function to check if a string is a number. Parameters ---------- From f8b1c41be6fe43cbfcf2d58b50d2c892d6662460 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 03:47:03 +0000 Subject: [PATCH 371/445] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/utils/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/validators.py b/src/diffpy/utils/validators.py index 0f281073..91a461bf 100644 --- a/src/diffpy/utils/validators.py +++ b/src/diffpy/utils/validators.py @@ -1,6 +1,6 @@ def is_number(string): """Check if the provided string can be converted to a float. - + Since integers can be converted to floats, this function will return True for integers as well. Hence, we can use this function to check if a string is a number. From 5dfff333783e6a2f2a0a30b70834372851bac87e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 31 Dec 2024 09:28:20 -0500 Subject: [PATCH 372/445] chore: added news for docstring --- news/docstrings-refactor.rst | 2 +- src/diffpy/utils/diffraction_objects.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/news/docstrings-refactor.rst b/news/docstrings-refactor.rst index 0dca3d1d..203b036e 100644 --- a/news/docstrings-refactor.rst +++ b/news/docstrings-refactor.rst @@ -1,6 +1,6 @@ **Added:** -* No news added: this branch was used to refactor the docstrings in the codebase without adding new features +* Improved API documentation in `DiffractionObject` methods and properties using the NumPy docstring format and PEP 256 **Changed:** diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 2f4dfa8b..f95f1758 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -190,7 +190,7 @@ def __add__(self, other): Returns ------- DiffractionObject - THe new DiffractionObject instance with modified yarray values. This instance is a deep copy of the + The new DiffractionObject instance with modified yarray values. This instance is a deep copy of the original with the additions applied. Raises From a7850fb51a3955cc0f3448841e02502f6f055347 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 10:56:06 -0500 Subject: [PATCH 373/445] fix: remove user_config.py --- src/diffpy/utils/user_config.py | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 src/diffpy/utils/user_config.py diff --git a/src/diffpy/utils/user_config.py b/src/diffpy/utils/user_config.py deleted file mode 100644 index ca2cc004..00000000 --- a/src/diffpy/utils/user_config.py +++ /dev/null @@ -1,30 +0,0 @@ -import json -from pathlib import Path - -CONFIG_FILE = "diffpyconfig.json" -CWD_CONFIG_PATH = Path.cwd() / CONFIG_FILE -HOME_CONFIG_PATH = Path.home() / CONFIG_FILE - - -def find_conf_file(): - if CWD_CONFIG_PATH.exists() and CWD_CONFIG_PATH.is_file(): - return CWD_CONFIG_PATH - elif HOME_CONFIG_PATH.exists() and HOME_CONFIG_PATH.is_file(): - return HOME_CONFIG_PATH - return None - - -def read_conf_file(): - conf_file = find_conf_file() - if conf_file: - with open(conf_file, "r") as f: - config = json.load(f) - if not config.get("username") or not config.get("email"): - raise ValueError("Please provide a configuration file with username and email.") - return config.get("username"), config.get("email") - return None, None - - -def write_conf_file(username, email): - with open(HOME_CONFIG_PATH, "w") as f: - json.dump({"username": username, "email": email}, f) From f870c2f8cfb624b16f3fa1396655e7bee273de1d Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 10:59:05 -0500 Subject: [PATCH 374/445] add news --- news/remove-userconfig.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/remove-userconfig.rst diff --git a/news/remove-userconfig.rst b/news/remove-userconfig.rst new file mode 100644 index 00000000..0d8e047b --- /dev/null +++ b/news/remove-userconfig.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* `user_config.py` function. + +**Fixed:** + +* + +**Security:** + +* From 59c8ecb973b623921ee71252fc232cf552839ba6 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 11:24:41 -0500 Subject: [PATCH 375/445] docs: improve news --- news/remove-userconfig.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/remove-userconfig.rst b/news/remove-userconfig.rst index 0d8e047b..967d9559 100644 --- a/news/remove-userconfig.rst +++ b/news/remove-userconfig.rst @@ -12,7 +12,7 @@ **Removed:** -* `user_config.py` function. +* `user_config.py`. Replaced by `_load_config` and `check_and_build_global_config` in `tools.py`. **Fixed:** From 5693321abad7e99b9d435774b334e1040ba8555d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 31 Dec 2024 11:49:50 -0500 Subject: [PATCH 376/445] fix: Add sbillinge as the designated authorized user for release --- .../workflows/build-wheel-release-upload.yml | 2 ++ news/set-github-admin.rst | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 news/set-github-admin.rst diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 841d1a76..db92d9d7 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -11,6 +11,8 @@ jobs: uses: Billingegroup/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 with: project: diffpy.utils + github_admin_username: sbillinge + secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} PAT_TOKEN: ${{ secrets.PAT_TOKEN }} diff --git a/news/set-github-admin.rst b/news/set-github-admin.rst new file mode 100644 index 00000000..3f04cf1a --- /dev/null +++ b/news/set-github-admin.rst @@ -0,0 +1,23 @@ +**Added:** + +* sbillinge username as the authorized admin for GitHub release workflow in `build-wheel-release-upload.yml` + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 6757a57f3900617c3db3796a32a78dbf98e4affb Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 16:16:26 -0500 Subject: [PATCH 377/445] docs: add docstrings and news --- news/DO-docstring.rst | 23 ++++++++++++++++++ src/diffpy/utils/diffraction_objects.py | 31 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 news/DO-docstring.rst diff --git a/news/DO-docstring.rst b/news/DO-docstring.rst new file mode 100644 index 00000000..e3107199 --- /dev/null +++ b/news/DO-docstring.rst @@ -0,0 +1,23 @@ +**Added:** + +* docstrings for `on_q`, `on_tth`, `on_d`, and `dump` in `diffraction_objects.py`. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index f95f1758..dc7bcea3 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -418,12 +418,33 @@ def _get_original_array(self): return self.on_d(), "d" def on_q(self): + """Return the tuple of two 1D numpy arrays containing q and y data. + + Returns + ------- + (xarray, yarray) : tuple of ndarray + The tuple containing two 1D numpy arrays with q and y data + """ return [self.all_arrays[:, 1], self.all_arrays[:, 0]] def on_tth(self): + """Return the tuple of two 1D numpy arrays containing tth and y data. + + Returns + ------- + (xarray, yarray) : tuple of ndarray + The tuple containing two 1D numpy arrays with tth and y data + """ return [self.all_arrays[:, 2], self.all_arrays[:, 0]] def on_d(self): + """Return the tuple of two 1D numpy arrays containing d and y data. + + Returns + ------- + (xarray, yarray) : tuple of ndarray + The tuple containing two 1D numpy arrays with d and y data + """ return [self.all_arrays[:, 3], self.all_arrays[:, 0]] def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): @@ -507,6 +528,16 @@ def on_xtype(self, xtype): raise ValueError(_xtype_wmsg(xtype)) def dump(self, filepath, xtype=None): + """Dump the xarray and yarray of the diffraction object to a two-column + file, with the associated information included in the header. + + Parameters + ---------- + filepath : str + The filepath where the diffraction object will be dumped + xtype : str, optional, default is q + The type of quantity for the independent variable chosen from {*XQUANTITIES, } + """ if xtype is None: xtype = "q" if xtype in QQUANTITIES: From da009de589d160e221d046104fc0e89434299773 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 16:40:17 -0500 Subject: [PATCH 378/445] docs: refine docstring and add example for dump --- src/diffpy/utils/diffraction_objects.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index dc7bcea3..a5784fde 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -422,7 +422,7 @@ def on_q(self): Returns ------- - (xarray, yarray) : tuple of ndarray + (q-array, y-array) : tuple of ndarray The tuple containing two 1D numpy arrays with q and y data """ return [self.all_arrays[:, 1], self.all_arrays[:, 0]] @@ -432,7 +432,7 @@ def on_tth(self): Returns ------- - (xarray, yarray) : tuple of ndarray + (tth-array, y-array) : tuple of ndarray The tuple containing two 1D numpy arrays with tth and y data """ return [self.all_arrays[:, 2], self.all_arrays[:, 0]] @@ -442,7 +442,7 @@ def on_d(self): Returns ------- - (xarray, yarray) : tuple of ndarray + (d-array, y-array) : tuple of ndarray The tuple containing two 1D numpy arrays with d and y data """ return [self.all_arrays[:, 3], self.all_arrays[:, 0]] @@ -537,6 +537,14 @@ def dump(self, filepath, xtype=None): The filepath where the diffraction object will be dumped xtype : str, optional, default is q The type of quantity for the independent variable chosen from {*XQUANTITIES, } + + Examples + -------- + To save a diffraction object to a file named "diffraction_data.chi" with + the independent variable 'q': + + >>> file = "diffraction_data.chi" + >>> do.dump(file, xtype="q") """ if xtype is None: xtype = "q" From dd5995b7be4f3ddce8cb1c7e45e52d30c72ce120 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 31 Dec 2024 16:54:28 -0500 Subject: [PATCH 379/445] docs: add more examples --- src/diffpy/utils/diffraction_objects.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a5784fde..99b6b7a1 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -540,11 +540,21 @@ def dump(self, filepath, xtype=None): Examples -------- - To save a diffraction object to a file named "diffraction_data.chi" with - the independent variable 'q': + To save a diffraction object to a file named "diffraction_data.chi" in the current directory + with the independent variable 'q': >>> file = "diffraction_data.chi" >>> do.dump(file, xtype="q") + + To save the diffraction data to a file in a subfolder `output`: + + >>> file = "./output/diffraction_data.chi" + >>> do.dump(file, xtype="q") + + To save the diffraction data with a different independent variable, such as 'tth': + + >>> file = "diffraction_data_tth.chi" + >>> do.dump(file, xtype="tth") """ if xtype is None: xtype = "q" From c23df0a92f8193024b0e1b97b326ffe2fc72d938 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 31 Dec 2024 18:25:55 -0500 Subject: [PATCH 380/445] docs: deploy docs on github pre-release with tag push --- .github/workflows/publish-docs-on-release.yml | 2 +- news/build-doc-automatic.rst | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 news/build-doc-automatic.rst diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml index b09c688a..e222ac53 100644 --- a/.github/workflows/publish-docs-on-release.yml +++ b/.github/workflows/publish-docs-on-release.yml @@ -2,7 +2,7 @@ name: Deploy Documentation on Release on: release: - types: [published] + types: [published, prereleased] workflow_dispatch: jobs: diff --git a/news/build-doc-automatic.rst b/news/build-doc-automatic.rst new file mode 100644 index 00000000..309fc888 --- /dev/null +++ b/news/build-doc-automatic.rst @@ -0,0 +1,23 @@ +**Added:** + +* deploy github pages documentation on pre-release + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 8bafe8da57e92d2738c83eea85f3044b59049ecc Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 1 Jan 2025 15:17:05 -0500 Subject: [PATCH 381/445] simon doing some docs tweaking --- doc/source/examples/examples.rst | 6 +- doc/source/examples/parsers_example.rst | 82 +++++++++++++++------- doc/source/examples/resample_example.rst | 63 ++++++++++++----- doc/source/examples/transforms_example.rst | 4 ++ doc/source/index.rst | 18 +++-- doc/source/license.rst | 4 +- 6 files changed, 122 insertions(+), 55 deletions(-) diff --git a/doc/source/examples/examples.rst b/doc/source/examples/examples.rst index 5e285ea1..5a30d3b4 100644 --- a/doc/source/examples/examples.rst +++ b/doc/source/examples/examples.rst @@ -7,8 +7,8 @@ Examples Landing page for diffpy.utils examples. .. toctree:: - parsers_example + diffraction_objects_example + transforms_example resample_example + parsers_example tools_example - transforms_example - diffraction_objects_example diff --git a/doc/source/examples/parsers_example.rst b/doc/source/examples/parsers_example.rst index f9320885..92135f3b 100644 --- a/doc/source/examples/parsers_example.rst +++ b/doc/source/examples/parsers_example.rst @@ -9,49 +9,65 @@ This example will demonstrate how diffpy.utils lets us easily process and serial Using the parsers module, we can load file data into simple and easy-to-work-with Python objects. 1) To begin, unzip :download:`parser_data<./example_data/parser_data.zip>` and take a look at ``data.txt``. - Our goal will be to extract and serialize the data table as well as the parameters listed in the header of this file. + This is a fairly standard format for 1D powder diffraction data. + Our goal will be to extract the data, and the parameters listed in the header, from this file and + load it into our program. 2) To get the data table, we will use the ``loadData`` function. The default behavior of this - function is to find and extract a data table from a file.:: + function is to find and extract a data table from a file. + +.. code-block:: python from diffpy.utils.parsers.loaddata import loadData data_table = loadData('') - While this will work with most datasets, on our ``data.txt`` file, we got a ``ValueError``. The reason for this is - due to the comments ``$ Phase Transition Near This Temperature Range`` and ``--> Note Significant Jump in Rw <--`` - embedded within the dataset. To fix this, try using the ``comments`` parameter. :: +While this will work with most datasets, on our ``data.txt`` file, we got a ``ValueError``. The reason for this is +due to the comments ``$ Phase Transition Near This Temperature Range`` and ``--> Note Significant Jump in Rw <--`` +embedded within the dataset. To fix this, try using the ``comments`` parameter. + +.. code-block:: python data_table = loadData('', comments=['$', '-->']) - This parameter tells ``loadData`` that any lines beginning with ``$`` and ``-->`` are just comments and - more entries in our data table may follow. +This parameter tells ``loadData`` that any lines beginning with ``$`` and ``-->`` are just comments and +more entries in our data table may follow. - Here are a few other parameters to test out: +Here are a few other parameters to test out: * ``delimiter=','``: Look for a comma-separated data table. Useful for csv file types. - However, since ``data.txt`` is whitespace separated, running :: + However, since ``data.txt`` is whitespace separated, running + +.. code-block:: python loadData('', comments=['$', '-->'], delimiter=',') - returns an empty list. +returns an empty list. * ``minrows=50``: Only look for data tables with at least 50 rows. Since our data table has much less than that many - rows, running :: + rows, running + +.. code-block:: python loadData('', comments=['$', '-->'], minrows=50) - returns an empty list. +returns an empty list. * ``usecols=[0, 3]``: Only return the 0th and 3rd columns (zero-indexed) of the data table. For ``data.txt``, this - corresponds to the temperature and rw columns. :: + corresponds to the temperature and rw columns. + +.. code-block:: python loadData('', comments=['$', '-->'], usecols=[0, 3]) 3) Next, to get the header information, we can again use ``loadData``, - but this time with the ``headers`` parameter enabled. :: + but this time with the ``headers`` parameter enabled. + +.. code-block:: python hdata = loadData('', comments=['$', '-->'], headers=True) 4) Rather than working with separate ``data_table`` and ``hdata`` objects, it may be easier to combine them into a single - dictionary. We can do so using the ``serialize_data`` function. :: + dictionary. We can do so using the ``serialize_data`` function. + +.. code-block:: python from diffpy.utils.parsers.loaddata import serialize_data file_data = serialize_data('', hdata, data_table, dt_colnames=data_table_column_names) data_dict = file_data['data.txt'] - Now we can extract specific data table columns from the dictionary. :: +Now we can extract specific data table columns from the dictionary. +.. code-block:: python data_table_temperature_column = data_dict['temperature'] data_table_rw_column = data_dict['rw'] -5) When we are done working with the data, we can store it on disc for later use. This can also be done using the - ``serialize_data`` function with an additional ``serial_file`` parameter.:: +5) When we are done working with the data, we can store it on disk for later use. This can also be done using the + ``serialize_data`` function with an additional ``serial_file`` parameter. + +.. code-block:: python parsed_file_data = serialize_data('', hdata, data_table, serial_file='') The returned value, ``parsed_file_data``, is the dictionary we just added to ``serialfile.json``. - To extract the data from the serial file, we use ``deserialize_data``. :: + To extract the data from the serial file, we use ``deserialize_data``. + +.. code-block:: python from diffpy.utils.parsers.serialization import deserialize_data parsed_file_data = deserialize_data('') -6) Finally, ``serialize_data`` allows us to store data from multiple text file in a single serial file. For one last bit - of practice, we will extract and add the data from ``moredata.txt`` into the same ``serialdata.json`` file.:: +6) Finally, ``serialize_data`` allows us to store data from multiple text files in a single serial file. For one last bit + of practice, we will extract and add the data from ``moredata.txt`` into the same ``serialdata.json`` file. + +.. code-block:: python data_table = loadData('') hdata = loadData('', headers=True) serialize_data('', hdata, data_table, serial_file='') - The serial file ``serialfile.json`` should now contain two entries: ``data.txt`` and ``moredata.txt``. - The data from each file can be accessed using :: +The serial file ``serialfile.json`` should now contain two entries: ``data.txt`` and ``moredata.txt``. +The data from each file can be accessed using + +.. code-block:: python serial_data = deserialize_data('') data_txt_data = serial_data['data.txt'] # Access data.txt data moredata_txt_data = serial_data['moredata.txt'] # Access moredata.txt data For more information, check out the :ref:`documentation` of the ``parsers`` module. +b diff --git a/doc/source/examples/resample_example.rst b/doc/source/examples/resample_example.rst index 5e54fca1..4884f5c9 100644 --- a/doc/source/examples/resample_example.rst +++ b/doc/source/examples/resample_example.rst @@ -13,14 +13,29 @@ given enough datapoints. 1) To start, unzip :download:`parser_data<./example_data/parser_data.zip>`. Then, load the data table from ``Nickel.gr`` and ``NiTarget.gr``. These datasets are based on data from `Atomic Pair Distribution Function Analysis: A Primer `_. - :: - from diffpy.utils.parsers.loaddata import loadData - nickel_datatable = loadData('') - nitarget_datatable = loadData('') +.. code-block:: python - Each data table has two columns: first is the grid and second is the function value. - To extract the columns, we can utilize the serialize function ... :: + from diffpy.utils.parsers.loaddata import loadData + nickel_datatable = loadData('') + nitarget_datatable = loadData('') + +Each data table has two columns: first is the grid and second is the function value. +To extract the columns, we can utilize the serialize function ... + +.. code-block:: python + + from diffpy.utils.parsers.serialization import serialize_data + nickel_data = serialize_data('Nickel.gr', {}, nickel_datatable, dt_colnames=['grid', 'func']) + nickel_grid = nickel_data['Nickel.gr']['grid'] + nickel_func = nickel_data['Nickel.gr']['func'] + target_data = serialize_data('NiTarget.gr', {}, nitarget_datatable, dt_colnames=['grid', 'function']) + target_grid = nickel_data['Nickel.gr']['grid'] + target_func = nickel_data['Nickel.gr']['func'] + +To extract the columns, we can utilize the serialize function ... + +.. code-block:: python from diffpy.utils.parsers.serialization import serialize_data nickel_data = serialize_data('Nickel.gr', {}, nickel_datatable, dt_colnames=['grid', 'func']) @@ -30,42 +45,52 @@ given enough datapoints. target_grid = nickel_data['Nickel.gr']['grid'] target_func = nickel_data['Nickel.gr']['func'] - ... or you can use any other column extracting method you prefer. +... or you can use any other column extracting method you prefer. -2) If we plot the two on top of each other :: +2) If we plot the two on top of each other + +.. code-block:: python import matplotlib.pyplot as plt plt.plot(target_grid, target_func, linewidth=3) plt.plot(nickel_grid, nickel_func, linewidth=1) - they look pretty similar, but to truly see the difference, we should plot the difference between the two. - We may want to run something like ... :: +they look pretty similar, but to truly see the difference, we should plot the difference between the two. +We may want to run something like ... + +.. code-block:: python import numpy as np difference = np.subtract(target_func, nickel_func) - ... but this will only produce the right result if the ``target_func`` and ``nickel_func`` are on the same grid. - Checking the lengths of ``target_grid`` and ``nickel_grid`` shows that these grids are clearly distinct. +... but this will only produce the right result if the ``target_func`` and ``nickel_func`` are on the same grid. +Checking the lengths of ``target_grid`` and ``nickel_grid`` shows that these grids are clearly distinct. 3) However, we can resample the two functions to be on the same grid. Since both functions have grids spanning - ``[0, 60]``, let us define a new grid ... :: + ``[0, 60]``, let us define a new grid ... + +.. code-block:: python grid = np.linspace(0, 60, 6001) - ... and use the diffpy.utils ``wsinterp`` function to resample on this grid.:: +... and use the diffpy.utils ``wsinterp`` function to resample on this grid. + +.. code-block:: python from diffpy.utils.resampler import wsinterp nickel_resample = wsinterp(grid, nickel_grid, nickel_func) target_resample = wsinterp(grid, target_grid, target_func) - We can now plot the difference to see that these two functions are quite similar.:: +We can now plot the difference to see that these two functions are quite similar. + +.. code-block:: python plt.plot(grid, target_resample) plt.plot(grid, nickel_resample) plt.plot(grid, target_resample - nickel_resample) - This is the desired result as the data in ``Nickel.gr`` is every tenth data point in ``NiTarget.gr``. - This also shows us that ``wsinterp`` can help us reconstruct a function from incomplete data. +This is the desired result as the data in ``Nickel.gr`` is every tenth data point in ``NiTarget.gr``. +This also shows us that ``wsinterp`` can help us reconstruct a function from incomplete data. 4) In order for our function reconstruction to be perfect up to a truncation error, we require that (a) the function is a Fourier transform of a band-limited dataset and (b) the original grid has enough equally-spaced datapoints based on @@ -79,7 +104,9 @@ given enough datapoints. Thus, our original grid requires :math:`25.0 * 60.0 / \pi < 478`. Since our grid has :math:`601` datapoints, our reconstruction was perfect as shown from the comparison between ``Nickel.gr`` and ``NiTarget.gr``. - This computation is implemented in the function ``nsinterp``.:: + This computation is implemented in the function ``nsinterp``. + +.. code-block:: python from diffpy.utils.resampler import nsinterp qmin = 0 diff --git a/doc/source/examples/transforms_example.rst b/doc/source/examples/transforms_example.rst index df7666b9..2fc40c4a 100644 --- a/doc/source/examples/transforms_example.rst +++ b/doc/source/examples/transforms_example.rst @@ -16,6 +16,7 @@ This example will demonstrate how to use the functions in the # Example: convert q to 2theta from diffpy.utils.transforms import q_to_tth + wavelength = 0.71 q = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) tth = q_to_tth(q, wavelength) @@ -32,6 +33,7 @@ This example will demonstrate how to use the functions in the # Example: convert 2theta to q from diffpy.utils.transforms import tth_to_q + wavelength = 0.71 tth = np.array([0, 30, 60, 90, 120, 180]) q = tth_to_q(tth, wavelength) @@ -49,11 +51,13 @@ This example will demonstrate how to use the functions in the # Example: convert d to q from diffpy.utils.transforms import d_to_q + d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) q = d_to_q(d) # Example: convert d to 2theta from diffpy.utils.transforms import d_to_tth + wavelength = 0.71 d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) tth = d_to_tth(d, wavelength) diff --git a/doc/source/index.rst b/doc/source/index.rst index f9f9fe80..7623146a 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -4,14 +4,18 @@ .. |title| replace:: diffpy.utils documentation -diffpy.utils - Shared utilities for diffpy packages. +diffpy.utils - General utilities for analyzing diffraction data | Software version |release|. | Last updated |today|. -The diffpy.utils package provides general functions for extracting data from variously formatted text files as well as -some PDF-specific functionality. These include wx GUI utilities used by the PDFgui program and an interpolation function -based on the Whittaker-Shannon formula for resampling a bandlimited PDF or other profile function. +The diffpy.utils package provides a number of functions and classes designed to help +researchers analyze their diffraction data. It also includes some functionality for +carrying out PDF analysis. Examples are parsers for reading common format diffraction +data files, ``DiffractionObjects`` that allow you to do algebra on diffraction patterns, +tools for better capture and propagation of metadata, +diffraction-friendly interpolation routines, as well as some other tools used across +diffpy libraries. Click :ref:`here` for a full list of utilities offered by diffpy.utils. @@ -20,6 +24,7 @@ Examples ======== Illustrations of when and how one would use various diffpy.utils functions. +* :ref:`Manipulate and do algebra on diffraction data` * :ref:`File Data Extraction` * :ref:`Resampling and Data Reconstruction` * :ref:`Load and Manage User and Package Information` @@ -30,8 +35,9 @@ Authors diffpy.utils is developed by members of the Billinge Group at Columbia University and at Brookhaven National Laboratory including -Pavol Juhás, Christopher L. Farrow, the Billinge Group -and its community contributors. +Pavol Juhás, Christopher L. Farrow, Simon J. L. Billinge, Andrew Yang, +with contributions from many Billinge Group members and +members of the diffpy community. For a detailed list of contributors see https://github.com/diffpy/diffpy.utils/graphs/contributors. diff --git a/doc/source/license.rst b/doc/source/license.rst index 38f27eec..b80d4eee 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -14,10 +14,10 @@ OPEN SOURCE LICENSE AGREEMENT Lawrence Berkeley National Laboratory | Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") | Copyright (c) 2006-2007, Board of Trustees of Michigan State University -| Copyright (c) 2008-2012, The Trustees of Columbia University in - the City of New York | Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National Laboratory +| Copyright (c) 2008-2025, The Trustees of Columbia University in + the City of New York The "DiffPy-CMI" is distributed subject to the following license conditions: From 0977b7fba5d6ec544c1bd22e9f9385a79aed0395 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 1 Jan 2025 15:29:06 -0500 Subject: [PATCH 382/445] citation added to index of docs --- doc/source/index.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/source/index.rst b/doc/source/index.rst index 7623146a..a406daa3 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -49,6 +49,14 @@ Installation See the `README `_ file included with the distribution. +======== +Citation +======== + +If you use this program for a scientific research that leads to publication, we ask that you acknowledge use of the program by citing the following paper in your publication: + + P. Juhás, C. L. Farrow, X. Yang, K. R. Knox and S. J. L. Billinge, Complex modeling: a strategy and software program for combining multiple information sources to solve ill posed structure and nanostructure inverse problems, Acta Crystallogr. A 71, 562-568 (2015). + ================= Table of contents ================= From 4d0b0f2be0442fb96c63da170321bf7356bec09b Mon Sep 17 00:00:00 2001 From: sbillinge Date: Wed, 1 Jan 2025 20:43:53 +0000 Subject: [PATCH 383/445] update changelog --- CHANGELOG.rst | 79 +++++++++++++++++++++++++++++ news/DO-docstring.rst | 23 --------- news/add-operations-tests.rst | 23 --------- news/array_index.rst | 23 --------- news/build-doc-automatic.rst | 23 --------- news/capture-user.rst | 23 --------- news/class-docstring.rst | 23 --------- news/codespell.rst | 23 --------- news/config-UX.rst | 23 --------- news/configupdate.rst | 23 --------- news/constructor.rst | 25 --------- news/d-q.rst | 23 --------- news/d-tth.rst | 23 --------- news/deepcopy.rst | 23 --------- news/deprecate.rst | 23 --------- news/doc-update.rst | 23 --------- news/doccln.rst | 23 --------- news/docformatter.rst | 23 --------- news/docs-do.rst | 23 --------- news/docstrings-refactor.rst | 23 --------- news/dump.rst | 23 --------- news/get-index.rst | 23 --------- news/is-float.rst | 23 --------- news/mu.rst | 23 --------- news/muD_calculator.rst | 24 --------- news/mv-input-scattering-quan.rst | 23 --------- news/no-empty-object.rst | 23 --------- news/no-news.rst | 23 --------- news/nsinterp.rst | 23 --------- news/op-mul-sub-div.rst | 23 --------- news/prettier-pre-commit.rst | 23 --------- news/private_f.rst | 24 --------- news/pytest-comment.rst | 23 --------- news/pytest-handle-warning.rst | 23 --------- news/pytest-reformat-transform.rst | 23 --------- news/pytest-test-refactor.rst | 23 --------- news/pytest-warning-divide-zero.rst | 23 --------- news/pytest-warning.rst | 23 --------- news/pytest-warnings-others.rst | 23 --------- news/pytest-wavelength-warnings.rst | 23 --------- news/remove-userconfig.rst | 23 --------- news/resample-dep.rst | 23 --------- news/resampler-relocation.rst | 23 --------- news/rm-range-methods.rst | 23 --------- news/scaleto-comments.rst | 23 --------- news/scaleto-max.rst | 23 --------- news/scaleto.rst | 23 --------- news/scattering-obj-valid.rst | 23 --------- news/scattering_obt.rst | 23 --------- news/set-github-admin.rst | 23 --------- news/setter-property.rst | 23 --------- news/test-func-format-compact.rst | 23 --------- news/test-refactor.rst | 23 --------- news/tests-restruct.rst | 23 --------- news/tools-doc-update.rst | 23 --------- news/tth-q.rst | 23 --------- news/user_info_doc.rst | 23 --------- news/userinfo.rst | 24 --------- news/uuid-rename.rst | 23 --------- news/uuid.rst | 23 --------- news/valid-empty-do.rst | 23 --------- news/xtype.rst | 23 --------- 62 files changed, 79 insertions(+), 1408 deletions(-) delete mode 100644 news/DO-docstring.rst delete mode 100644 news/add-operations-tests.rst delete mode 100644 news/array_index.rst delete mode 100644 news/build-doc-automatic.rst delete mode 100644 news/capture-user.rst delete mode 100644 news/class-docstring.rst delete mode 100644 news/codespell.rst delete mode 100644 news/config-UX.rst delete mode 100644 news/configupdate.rst delete mode 100644 news/constructor.rst delete mode 100644 news/d-q.rst delete mode 100644 news/d-tth.rst delete mode 100644 news/deepcopy.rst delete mode 100644 news/deprecate.rst delete mode 100644 news/doc-update.rst delete mode 100644 news/doccln.rst delete mode 100644 news/docformatter.rst delete mode 100644 news/docs-do.rst delete mode 100644 news/docstrings-refactor.rst delete mode 100644 news/dump.rst delete mode 100644 news/get-index.rst delete mode 100644 news/is-float.rst delete mode 100644 news/mu.rst delete mode 100644 news/muD_calculator.rst delete mode 100644 news/mv-input-scattering-quan.rst delete mode 100644 news/no-empty-object.rst delete mode 100644 news/no-news.rst delete mode 100644 news/nsinterp.rst delete mode 100644 news/op-mul-sub-div.rst delete mode 100644 news/prettier-pre-commit.rst delete mode 100644 news/private_f.rst delete mode 100644 news/pytest-comment.rst delete mode 100644 news/pytest-handle-warning.rst delete mode 100644 news/pytest-reformat-transform.rst delete mode 100644 news/pytest-test-refactor.rst delete mode 100644 news/pytest-warning-divide-zero.rst delete mode 100644 news/pytest-warning.rst delete mode 100644 news/pytest-warnings-others.rst delete mode 100644 news/pytest-wavelength-warnings.rst delete mode 100644 news/remove-userconfig.rst delete mode 100644 news/resample-dep.rst delete mode 100644 news/resampler-relocation.rst delete mode 100644 news/rm-range-methods.rst delete mode 100644 news/scaleto-comments.rst delete mode 100644 news/scaleto-max.rst delete mode 100644 news/scaleto.rst delete mode 100644 news/scattering-obj-valid.rst delete mode 100644 news/scattering_obt.rst delete mode 100644 news/set-github-admin.rst delete mode 100644 news/setter-property.rst delete mode 100644 news/test-func-format-compact.rst delete mode 100644 news/test-refactor.rst delete mode 100644 news/tests-restruct.rst delete mode 100644 news/tools-doc-update.rst delete mode 100644 news/tth-q.rst delete mode 100644 news/user_info_doc.rst delete mode 100644 news/userinfo.rst delete mode 100644 news/uuid-rename.rst delete mode 100644 news/uuid.rst delete mode 100644 news/valid-empty-do.rst delete mode 100644 news/xtype.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3032bdd3..16a17067 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,85 @@ Release Notes .. current developments +3.6.0 +===== + +**Added:** + +* unit tests for initializing DiffractionObject with empty array in xarray and yarray +* function to return the index of the closest value to the specified value in an array. +* functions to convert between d and q +* catch division by zero warning messages in tests +* functionality to raise useful warning and error messages during angular conversion between two theta and q +* Improved API documentation in `DiffractionObject` methods and properties using the NumPy docstring format and PEP 256 +* validate xtype belongs to XQUANTITIES during DiffractionObject init +* Group's Pytest practices for using @pytest.mark.parametrize in test_diffraction_objects.py +* unit tests for __add__ operation for DiffractionObject +* Better wording on the capture user info functionality +* Spelling check via Codespell in pre-commit +* prettier pre-commit hook for automatic linting of .yml, .json, and .md files +* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile +from a line-scan through the sample +* sbillinge username as the authorized admin for GitHub release workflow in `build-wheel-release-upload.yml` +* function to compute x-ray attenuation coefficient (mu) using XrayDB +* class docstring for `DiffractionObject` +* docforamtter in pre-commit for automatic formatting of docstrings to PEP 257 +* Function nsinterp for automatic interpolation onto the Nyquist-Shannon grid. +* functionality to return the 2D array based on the specified xtype +* functionality in dump to allow writing data on dspace +* addition, multiplication, subtraction, and division operators between two DiffractionObject instances or a scalar value with another DiffractionObject for modifying yarray (intensity) values. +* functionality to rescale diffraction objects, placing one on top of another at a specified point +* new feature in `scale_to()`: default scaling is based on the max q-value in each diffraction object. +* functions to convert between d and tth +* unit test for expected warning when no wavelength is provided for DiffractionObject init +* copy() method for DiffractionObject instance +* docstrings for `on_q`, `on_tth`, `on_d`, and `dump` in `diffraction_objects.py`. +* prevent direct modification of `all_arrays` using `@property` +* Information on how to update the default user information +* Example docs for basic DiffractionObject usage +* deploy github pages documentation on pre-release +* Gettable `id` property to `DiffractionObject` + +**Changed:** + +* Refactor get_user_info to separate the tasks of getting the info from config files +and creating config files when they are missing. +* test comment format with compact style without extra line for each comment +* Rename `input_scattering_quantity` to `input_data` in `DiffractionObject` init +* refactor `q_to_tth()` and `tth_to_q()` into `diffpy.utils.transforms` to make them available outside +DiffractionObject +* Moved resampler out of parsers, new path is diffpy.utils.resampler +* Rename the `isfloat` function to `is_number`, and move it to the `diffpy/utils/utilsvalidators.py` directory +* arrays and attributes now can be inserted when a DiffractionObject is instantiated +* data are now stored as a (len(x),4) numpy array with intensity in column 0, the q, then tth, then d +* `DiffractionObject.on_q`, `...on_tth` and `...on_d` are now methods and called as `DiffractionObject.on_q()` etc.` +* \tests directory tree to match \src +* DiffractionObject's "id" property renamed to "uuid" +* `DiffractionObject` requires 3 input parameters of `xarray`, `yarray`, `xtype`, to be instantiated. It can be instantiated with empty arrays. +* Paths in our documentation reflect changes made in code. +* Enumerated list for the different ways to set user information + +**Deprecated:** + +* `resample` function in resampler. Replaced with `wsinterp` with better functionality. +* Diffraction_object class, renamed to DiffractionObject + +**Fixed:** + +* additional information to users to relieve frustration in finding how to update global config +* Unittest to Pytest migration for test_loaddata.py +* file paths of the test files according to new \tests directory tree +* Typo for get_package_info example +* return type of `get_array_index` method in `DiffractionObject` to return integer instead of list + +**Removed:** + +* scattering_objects layer in importing diffraction_objects +* `user_config.py`. Replaced by `_load_config` and `check_and_build_global_config` in `tools.py`. +* Relative imports in parser's __init__.py +* `set_angles_from_list`, `set_angles_from`, `set_qs_from_range` methods in `DiffractionObject` + + 3.5.0 ===== diff --git a/news/DO-docstring.rst b/news/DO-docstring.rst deleted file mode 100644 index e3107199..00000000 --- a/news/DO-docstring.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* docstrings for `on_q`, `on_tth`, `on_d`, and `dump` in `diffraction_objects.py`. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/add-operations-tests.rst b/news/add-operations-tests.rst deleted file mode 100644 index e59edbd5..00000000 --- a/news/add-operations-tests.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* unit tests for __add__ operation for DiffractionObject - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/array_index.rst b/news/array_index.rst deleted file mode 100644 index 6f687373..00000000 --- a/news/array_index.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* function to return the index of the closest value to the specified value in an array. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/build-doc-automatic.rst b/news/build-doc-automatic.rst deleted file mode 100644 index 309fc888..00000000 --- a/news/build-doc-automatic.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* deploy github pages documentation on pre-release - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/capture-user.rst b/news/capture-user.rst deleted file mode 100644 index b789d46e..00000000 --- a/news/capture-user.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Better wording on the capture user info functionality - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/class-docstring.rst b/news/class-docstring.rst deleted file mode 100644 index a31cc6d6..00000000 --- a/news/class-docstring.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* class docstring for `DiffractionObject` - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/codespell.rst b/news/codespell.rst deleted file mode 100644 index 8c5ba6d2..00000000 --- a/news/codespell.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Spelling check via Codespell in pre-commit - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/config-UX.rst b/news/config-UX.rst deleted file mode 100644 index af826723..00000000 --- a/news/config-UX.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* additional information to users to relieve frustration in finding how to update global config - -**Security:** - -* diff --git a/news/configupdate.rst b/news/configupdate.rst deleted file mode 100644 index 0bc3ffa9..00000000 --- a/news/configupdate.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* no news added: covered in the news from the get_user_info work - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/constructor.rst b/news/constructor.rst deleted file mode 100644 index 39ffa77f..00000000 --- a/news/constructor.rst +++ /dev/null @@ -1,25 +0,0 @@ -**Added:** - -* - -**Changed:** - -* arrays and attributes now can be inserted when a DiffractionObject is instantiated -* data are now stored as a (len(x),4) numpy array with intensity in column 0, the q, then tth, then d -* `DiffractionObject.on_q`, `...on_tth` and `...on_d` are now methods and called as `DiffractionObject.on_q()` etc.` - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/d-q.rst b/news/d-q.rst deleted file mode 100644 index 92105f07..00000000 --- a/news/d-q.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functions to convert between d and q - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/d-tth.rst b/news/d-tth.rst deleted file mode 100644 index 2bfc091f..00000000 --- a/news/d-tth.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functions to convert between d and tth - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/deepcopy.rst b/news/deepcopy.rst deleted file mode 100644 index 578d360c..00000000 --- a/news/deepcopy.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* copy() method for DiffractionObject instance - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/deprecate.rst b/news/deprecate.rst deleted file mode 100644 index 31dd10e5..00000000 --- a/news/deprecate.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* Diffraction_object class, renamed to DiffractionObject - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/doc-update.rst b/news/doc-update.rst deleted file mode 100644 index 1993a616..00000000 --- a/news/doc-update.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* Paths in our documentation reflect changes made in code. - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/doccln.rst b/news/doccln.rst deleted file mode 100644 index 24127849..00000000 --- a/news/doccln.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Example docs for basic DiffractionObject usage - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/docformatter.rst b/news/docformatter.rst deleted file mode 100644 index 56368125..00000000 --- a/news/docformatter.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* docforamtter in pre-commit for automatic formatting of docstrings to PEP 257 - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/docs-do.rst b/news/docs-do.rst deleted file mode 100644 index 21faf520..00000000 --- a/news/docs-do.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* no news added: adding documentation for diffraction object operation examples - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/docstrings-refactor.rst b/news/docstrings-refactor.rst deleted file mode 100644 index 203b036e..00000000 --- a/news/docstrings-refactor.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Improved API documentation in `DiffractionObject` methods and properties using the NumPy docstring format and PEP 256 - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/dump.rst b/news/dump.rst deleted file mode 100644 index 7f99d586..00000000 --- a/news/dump.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functionality in dump to allow writing data on dspace - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/get-index.rst b/news/get-index.rst deleted file mode 100644 index eef8c787..00000000 --- a/news/get-index.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* return type of `get_array_index` method in `DiffractionObject` to return integer instead of list - -**Security:** - -* diff --git a/news/is-float.rst b/news/is-float.rst deleted file mode 100644 index 918600e5..00000000 --- a/news/is-float.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* Rename the `isfloat` function to `is_number`, and move it to the `diffpy/utils/utilsvalidators.py` directory - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/mu.rst b/news/mu.rst deleted file mode 100644 index bbf9cb1a..00000000 --- a/news/mu.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* function to compute x-ray attenuation coefficient (mu) using XrayDB - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/muD_calculator.rst b/news/muD_calculator.rst deleted file mode 100644 index a6731760..00000000 --- a/news/muD_calculator.rst +++ /dev/null @@ -1,24 +0,0 @@ -**Added:** - -* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile - from a line-scan through the sample - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/mv-input-scattering-quan.rst b/news/mv-input-scattering-quan.rst deleted file mode 100644 index 25e8aac9..00000000 --- a/news/mv-input-scattering-quan.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* Rename `input_scattering_quantity` to `input_data` in `DiffractionObject` init - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/no-empty-object.rst b/news/no-empty-object.rst deleted file mode 100644 index 7e4ec7a4..00000000 --- a/news/no-empty-object.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* `DiffractionObject` requires 3 input parameters of `xarray`, `yarray`, `xtype`, to be instantiated. It can be instantiated with empty arrays. - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/no-news.rst b/news/no-news.rst deleted file mode 100644 index 31779a23..00000000 --- a/news/no-news.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: just making sure the previous add, mul, div, and sub tests are working as expected. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/nsinterp.rst b/news/nsinterp.rst deleted file mode 100644 index 9b716b87..00000000 --- a/news/nsinterp.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Function nsinterp for automatic interpolation onto the Nyquist-Shannon grid. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/op-mul-sub-div.rst b/news/op-mul-sub-div.rst deleted file mode 100644 index 9fe75ccc..00000000 --- a/news/op-mul-sub-div.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* addition, multiplication, subtraction, and division operators between two DiffractionObject instances or a scalar value with another DiffractionObject for modifying yarray (intensity) values. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/prettier-pre-commit.rst b/news/prettier-pre-commit.rst deleted file mode 100644 index d61d93ed..00000000 --- a/news/prettier-pre-commit.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* prettier pre-commit hook for automatic linting of .yml, .json, and .md files - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/private_f.rst b/news/private_f.rst deleted file mode 100644 index b67df7b7..00000000 --- a/news/private_f.rst +++ /dev/null @@ -1,24 +0,0 @@ -**Added:** - -* - -**Changed:** - -* refactor `q_to_tth()` and `tth_to_q()` into `diffpy.utils.transforms` to make them available outside - DiffractionObject - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-comment.rst b/news/pytest-comment.rst deleted file mode 100644 index 9d0f81bb..00000000 --- a/news/pytest-comment.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Group's Pytest practices for using @pytest.mark.parametrize in test_diffraction_objects.py - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-handle-warning.rst b/news/pytest-handle-warning.rst deleted file mode 100644 index 81bf78bf..00000000 --- a/news/pytest-handle-warning.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* catch division by zero warning messages in tests - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-reformat-transform.rst b/news/pytest-reformat-transform.rst deleted file mode 100644 index 2bd19e87..00000000 --- a/news/pytest-reformat-transform.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: previous PR already contains has an news item for refactoring test_transform.py - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-test-refactor.rst b/news/pytest-test-refactor.rst deleted file mode 100644 index 08d3819b..00000000 --- a/news/pytest-test-refactor.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: refactoring tests that have been mentioned in previous news - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-warning-divide-zero.rst b/news/pytest-warning-divide-zero.rst deleted file mode 100644 index 4b84dba2..00000000 --- a/news/pytest-warning-divide-zero.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: previous news created for catching divide by zero warnings in pytest - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-warning.rst b/news/pytest-warning.rst deleted file mode 100644 index 8900165f..00000000 --- a/news/pytest-warning.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-warnings-others.rst b/news/pytest-warnings-others.rst deleted file mode 100644 index 8900165f..00000000 --- a/news/pytest-warnings-others.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/pytest-wavelength-warnings.rst b/news/pytest-wavelength-warnings.rst deleted file mode 100644 index 3446720a..00000000 --- a/news/pytest-wavelength-warnings.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* unit test for expected warning when no wavelength is provided for DiffractionObject init - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/remove-userconfig.rst b/news/remove-userconfig.rst deleted file mode 100644 index 967d9559..00000000 --- a/news/remove-userconfig.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* `user_config.py`. Replaced by `_load_config` and `check_and_build_global_config` in `tools.py`. - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/resample-dep.rst b/news/resample-dep.rst deleted file mode 100644 index 91bb3e3e..00000000 --- a/news/resample-dep.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* `resample` function in resampler. Replaced with `wsinterp` with better functionality. - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/resampler-relocation.rst b/news/resampler-relocation.rst deleted file mode 100644 index 220fc2f5..00000000 --- a/news/resampler-relocation.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* Moved resampler out of parsers, new path is diffpy.utils.resampler - -**Deprecated:** - -* - -**Removed:** - -* Relative imports in parser's __init__.py - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/rm-range-methods.rst b/news/rm-range-methods.rst deleted file mode 100644 index 8150da16..00000000 --- a/news/rm-range-methods.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* `set_angles_from_list`, `set_angles_from`, `set_qs_from_range` methods in `DiffractionObject` - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scaleto-comments.rst b/news/scaleto-comments.rst deleted file mode 100644 index 73d0f042..00000000 --- a/news/scaleto-comments.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* no news added - make test comments for `test_scale_to` more readable - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scaleto-max.rst b/news/scaleto-max.rst deleted file mode 100644 index d70e9d7f..00000000 --- a/news/scaleto-max.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* new feature in `scale_to()`: default scaling is based on the max q-value in each diffraction object. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scaleto.rst b/news/scaleto.rst deleted file mode 100644 index 6f6b0635..00000000 --- a/news/scaleto.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functionality to rescale diffraction objects, placing one on top of another at a specified point - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scattering-obj-valid.rst b/news/scattering-obj-valid.rst deleted file mode 100644 index adf4dfa9..00000000 --- a/news/scattering-obj-valid.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* validate xtype belongs to XQUANTITIES during DiffractionObject init - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/scattering_obt.rst b/news/scattering_obt.rst deleted file mode 100644 index 0090b88e..00000000 --- a/news/scattering_obt.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* scattering_objects layer in importing diffraction_objects - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/set-github-admin.rst b/news/set-github-admin.rst deleted file mode 100644 index 3f04cf1a..00000000 --- a/news/set-github-admin.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* sbillinge username as the authorized admin for GitHub release workflow in `build-wheel-release-upload.yml` - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/setter-property.rst b/news/setter-property.rst deleted file mode 100644 index 8b2ddc97..00000000 --- a/news/setter-property.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* prevent direct modification of `all_arrays` using `@property` - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/test-func-format-compact.rst b/news/test-func-format-compact.rst deleted file mode 100644 index ecb92ac0..00000000 --- a/news/test-func-format-compact.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* test comment format with compact style without extra line for each comment - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/test-refactor.rst b/news/test-refactor.rst deleted file mode 100644 index 095df231..00000000 --- a/news/test-refactor.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Unittest to Pytest migration for test_loaddata.py - -**Security:** - -* diff --git a/news/tests-restruct.rst b/news/tests-restruct.rst deleted file mode 100644 index 4b28c56f..00000000 --- a/news/tests-restruct.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* \tests directory tree to match \src - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* file paths of the test files according to new \tests directory tree - -**Security:** - -* diff --git a/news/tools-doc-update.rst b/news/tools-doc-update.rst deleted file mode 100644 index e04b7b17..00000000 --- a/news/tools-doc-update.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Information on how to update the default user information - -**Changed:** - -* Enumerated list for the different ways to set user information - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Typo for get_package_info example - -**Security:** - -* diff --git a/news/tth-q.rst b/news/tth-q.rst deleted file mode 100644 index c512bf9b..00000000 --- a/news/tth-q.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functionality to raise useful warning and error messages during angular conversion between two theta and q - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/user_info_doc.rst b/news/user_info_doc.rst deleted file mode 100644 index 43bee6bd..00000000 --- a/news/user_info_doc.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* no news added: simply adding documentation for config update workflow - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/userinfo.rst b/news/userinfo.rst deleted file mode 100644 index 124b49f8..00000000 --- a/news/userinfo.rst +++ /dev/null @@ -1,24 +0,0 @@ -**Added:** - -* - -**Changed:** - -* Refactor get_user_info to separate the tasks of getting the info from config files - and creating config files when they are missing. - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/uuid-rename.rst b/news/uuid-rename.rst deleted file mode 100644 index ecd7f22d..00000000 --- a/news/uuid-rename.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* DiffractionObject's "id" property renamed to "uuid" - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/uuid.rst b/news/uuid.rst deleted file mode 100644 index 474793f4..00000000 --- a/news/uuid.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* Gettable `id` property to `DiffractionObject` - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/valid-empty-do.rst b/news/valid-empty-do.rst deleted file mode 100644 index dfd98716..00000000 --- a/news/valid-empty-do.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* unit tests for initializing DiffractionObject with empty array in xarray and yarray - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/xtype.rst b/news/xtype.rst deleted file mode 100644 index 24a78758..00000000 --- a/news/xtype.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* functionality to return the 2D array based on the specified xtype - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* From b6c74c476b299394c4d5900c6cf0880b2cb0ff91 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 1 Jan 2025 16:04:03 -0500 Subject: [PATCH 384/445] docs: extra line break in 3.6.0 changelog.rst --- CHANGELOG.rst | 9 +++------ news/changelog-3.6.0-line-break.rst | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 news/changelog-3.6.0-line-break.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 16a17067..bc99672e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,8 +21,7 @@ Release Notes * Better wording on the capture user info functionality * Spelling check via Codespell in pre-commit * prettier pre-commit hook for automatic linting of .yml, .json, and .md files -* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile -from a line-scan through the sample +* Function that can be used to compute muD (absorption coefficient) from a file containing an absorption profile from a line-scan through the sample * sbillinge username as the authorized admin for GitHub release workflow in `build-wheel-release-upload.yml` * function to compute x-ray attenuation coefficient (mu) using XrayDB * class docstring for `DiffractionObject` @@ -45,12 +44,10 @@ from a line-scan through the sample **Changed:** -* Refactor get_user_info to separate the tasks of getting the info from config files -and creating config files when they are missing. +* Refactor get_user_info to separate the tasks of getting the info from config files and creating config files when they are missing. * test comment format with compact style without extra line for each comment * Rename `input_scattering_quantity` to `input_data` in `DiffractionObject` init -* refactor `q_to_tth()` and `tth_to_q()` into `diffpy.utils.transforms` to make them available outside -DiffractionObject +* refactor `q_to_tth()` and `tth_to_q()` into `diffpy.utils.transforms` to make them available outside DiffractionObject * Moved resampler out of parsers, new path is diffpy.utils.resampler * Rename the `isfloat` function to `is_number`, and move it to the `diffpy/utils/utilsvalidators.py` directory * arrays and attributes now can be inserted when a DiffractionObject is instantiated diff --git a/news/changelog-3.6.0-line-break.rst b/news/changelog-3.6.0-line-break.rst new file mode 100644 index 00000000..ac80a0a1 --- /dev/null +++ b/news/changelog-3.6.0-line-break.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Extra line break in each news after 3.6.0 in `CHANGELOG.rst` so that this rst can be rendered correctly when deployed + +**Fixed:** + +* + +**Security:** + +* From a04340df7c7aeac199f43d861711dd3e4be96535 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 1 Jan 2025 16:37:02 -0500 Subject: [PATCH 385/445] chore: synchronize README and index of the docs with updated text from docs --- README.rst | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 520d4029..cc86c81e 100644 --- a/README.rst +++ b/README.rst @@ -38,14 +38,16 @@ diffpy.utils Package ======================================================================== -General purpose shared utilities for the diffpy libraries. - -The diffpy.utils package provides functions for extracting array data from -variously formatted text files, an interpolation function based on the -Whittaker-Shannon formula that can be used to resample a PDF or other profile -function over a new grid, `DiffractionObject` for conveniently manipulating -diffraction data, and some wx GUI utilities used by the PDFgui -program. +General utilities for analyzing diffraction data + +The diffpy.utils package provides a number of functions and classes designed to help +researchers analyze their diffraction data. It also includes some functionality for +carrying out PDF analysis. Examples are parsers for reading common format diffraction +data files, ``DiffractionObjects`` that allow you to do algebra on diffraction patterns, +tools for better capture and propagation of metadata, +diffraction-friendly interpolation routines, as well as some other tools used across +diffpy libraries. + Citation -------- From 622c335c45f3395dada02d4e426ac2c1cea886cb Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 1 Jan 2025 16:38:09 -0500 Subject: [PATCH 386/445] news --- news/readme.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/readme.rst diff --git a/news/readme.rst b/news/readme.rst new file mode 100644 index 00000000..978de17c --- /dev/null +++ b/news/readme.rst @@ -0,0 +1,23 @@ +**Added:** + +* no news needed: just a chore of no import + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From f0350aa7e058b63c31b8ae4c3d8fcb75ae638361 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 1 Jan 2025 16:48:24 -0500 Subject: [PATCH 387/445] shynch pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4f9238ae..90560367 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ authors = [ maintainers = [ { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, ] -description = "Shared utilities for diffpy packages" +description = "General utilities for analyzing diffraction data" keywords = ["text data parsers", "wx grid", "diffraction objects"] readme = "README.rst" requires-python = ">=3.11, <3.14" From 62525fd010843b17173cf117ecdffb171139e55d Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 31 Jan 2025 16:19:53 -0500 Subject: [PATCH 388/445] style: change the line-width limit to 79 --- .flake8 | 2 +- .isort.cfg | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/pyproject.toml b/pyproject.toml index 90560367..10a6f890 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ ignore-words = ".codespell/ignore_words.txt" skip = "*.cif,*.dat" [tool.black] -line-length = 115 +line-length = 79 include = '\.pyi?$' exclude = ''' /( From e2f789c2f7217670f86467c81886aabe7f387a08 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 31 Jan 2025 16:23:52 -0500 Subject: [PATCH 389/445] style: apply black and flake8 to all files in the project --- doc/source/conf.py | 12 +- src/diffpy/utils/diffraction_objects.py | 74 +++++-- src/diffpy/utils/parsers/custom_exceptions.py | 4 +- src/diffpy/utils/parsers/loaddata.py | 25 ++- src/diffpy/utils/parsers/serialization.py | 8 +- src/diffpy/utils/tools.py | 70 +++++-- src/diffpy/utils/transforms.py | 8 +- src/diffpy/utils/wx/gridutils.py | 12 +- tests/conftest.py | 18 +- tests/test_diffraction_objects.py | 193 ++++++++++++++---- tests/test_loaddata.py | 4 +- tests/test_serialization.py | 61 ++++-- tests/test_tools.py | 136 +++++++++--- tests/test_transforms.py | 103 ++++++++-- 14 files changed, 585 insertions(+), 143 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 721f940e..045aba0a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -221,7 +221,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ("index", "diffpy.utils.tex", "diffpy.utils Documentation", ab_authors, "manual"), + ( + "index", + "diffpy.utils.tex", + "diffpy.utils Documentation", + ab_authors, + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -249,7 +255,9 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("index", "diffpy.utils", "diffpy.utils Documentation", ab_authors, 1)] +man_pages = [ + ("index", "diffpy.utils", "diffpy.utils Documentation", ab_authors, 1) +] # If true, show URL addresses after external links. # man_show_urls = False diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 99b6b7a1..1276e08d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -6,13 +6,29 @@ import numpy as np from diffpy.utils.tools import get_package_info -from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q +from diffpy.utils.transforms import ( + d_to_q, + d_to_tth, + q_to_d, + q_to_tth, + tth_to_d, + tth_to_q, +) QQUANTITIES = ["q"] ANGLEQUANTITIES = ["angle", "tth", "twotheta", "2theta"] DQUANTITIES = ["d", "dspace"] XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES -XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] +XUNITS = [ + "degrees", + "radians", + "rad", + "deg", + "inv_angs", + "inv_nm", + "nm-1", + "A-1", +] x_values_not_equal_emsg = ( "The two objects have different values in x arrays (my_do.all_arrays[:, [1, 2, 3]]). " @@ -129,9 +145,13 @@ def __init__( """ self._uuid = uuid.uuid4() - self._input_data(xarray, yarray, xtype, wavelength, scat_quantity, name, metadata) + self._input_data( + xarray, yarray, xtype, wavelength, scat_quantity, name, metadata + ) - def _input_data(self, 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)) if len(xarray) != len(yarray): @@ -152,8 +172,12 @@ def _input_data(self, xarray, yarray, xtype, wavelength, scat_quantity, name, me def __eq__(self, other): if not isinstance(other, DiffractionObject): return NotImplemented - self_attributes = [key for key in self.__dict__ if not key.startswith("_")] - other_attributes = [key for key in other.__dict__ if not key.startswith("_")] + self_attributes = [ + key for key in self.__dict__ if not key.startswith("_") + ] + other_attributes = [ + key for key in other.__dict__ if not key.startswith("_") + ] if not sorted(self_attributes) == sorted(other_attributes): return False for key in self_attributes: @@ -167,8 +191,13 @@ def __eq__(self, other): or not np.isclose(value, other_value, rtol=1e-5) ): return False - elif isinstance(value, list) and all(isinstance(i, np.ndarray) for i in value): - if not all(np.allclose(i, j, rtol=1e-5) for i, j in zip(value, other_value)): + elif isinstance(value, list) and all( + isinstance(i, np.ndarray) for i in value + ): + if not all( + np.allclose(i, j, rtol=1e-5) + for i, j in zip(value, other_value) + ): return False else: if value != other_value: @@ -303,7 +332,9 @@ def _check_operation_compatibility(self, other): if isinstance(other, DiffractionObject): if self.all_arrays.shape != other.all_arrays.shape: raise ValueError(x_values_not_equal_emsg) - if not np.allclose(self.all_arrays[:, [1, 2, 3]], other.all_arrays[:, [1, 2, 3]]): + if not np.allclose( + self.all_arrays[:, [1, 2, 3]], other.all_arrays[:, [1, 2, 3]] + ): raise ValueError(x_values_not_equal_emsg) @property @@ -381,7 +412,9 @@ def get_array_index(self, xtype, xvalue): xtype = self._input_xtype xarray = self.on_xtype(xtype)[0] if len(xarray) == 0: - raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.") + raise ValueError( + f"The '{xtype}' array is empty. Please ensure it is initialized." + ) index = (np.abs(xarray - xvalue)).argmin() return index @@ -447,7 +480,9 @@ def on_d(self): """ return [self.all_arrays[:, 3], self.all_arrays[:, 0]] - def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): + def scale_to( + self, target_diff_object, q=None, tth=None, d=None, offset=None + ): """Return a new diffraction object which is the current object but rescaled in y to the target. @@ -486,7 +521,10 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): if count == 0: q_target_max = max(target_diff_object.on_q()[1]) q_self_max = max(self.on_q()[1]) - scaled_do._all_arrays[:, 0] = scaled_do._all_arrays[:, 0] * q_target_max / q_self_max + offset + scaled_do._all_arrays[:, 0] = ( + scaled_do._all_arrays[:, 0] * q_target_max / q_self_max + + offset + ) return scaled_do xtype = "q" if q is not None else "tth" if tth is not None else "d" @@ -497,7 +535,9 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None): xindex_data = (np.abs(data[0] - xvalue)).argmin() xindex_target = (np.abs(target[0] - xvalue)).argmin() - scaled_do._all_arrays[:, 0] = data[1] * target[1][xindex_target] / data[1][xindex_data] + offset + scaled_do._all_arrays[:, 0] = ( + data[1] * target[1][xindex_target] / data[1][xindex_data] + offset + ) return scaled_do def on_xtype(self, xtype): @@ -561,12 +601,16 @@ def dump(self, filepath, xtype=None): if xtype in QQUANTITIES: data_to_save = np.column_stack((self.on_q()[0], self.on_q()[1])) elif xtype in ANGLEQUANTITIES: - data_to_save = np.column_stack((self.on_tth()[0], self.on_tth()[1])) + data_to_save = np.column_stack( + (self.on_tth()[0], self.on_tth()[1]) + ) elif xtype in DQUANTITIES: data_to_save = np.column_stack((self.on_d()[0], self.on_d()[1])) else: warnings.warn(_xtype_wmsg(xtype)) - self.metadata.update(get_package_info("diffpy.utils", metadata=self.metadata)) + self.metadata.update( + get_package_info("diffpy.utils", metadata=self.metadata) + ) self.metadata["creation_time"] = datetime.datetime.now() with open(filepath, "w") as f: diff --git a/src/diffpy/utils/parsers/custom_exceptions.py b/src/diffpy/utils/parsers/custom_exceptions.py index ffb6aca6..f5fa9717 100644 --- a/src/diffpy/utils/parsers/custom_exceptions.py +++ b/src/diffpy/utils/parsers/custom_exceptions.py @@ -51,5 +51,7 @@ class ImproperSizeError(Exception): def __init__(self, bad_object, message=None): if message is None: - self.message = f"The size of {bad_object} is different than expected." + self.message = ( + f"The size of {bad_object} is different than expected." + ) super().__init__(self.message) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 8369e516..8154f8d5 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -20,7 +20,9 @@ from diffpy.utils import validators -def loadData(filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs): +def loadData( + filename, minrows=10, headers=False, hdel="=", hignore=None, **kwargs +): """Find and load data from a text file. The data block is identified as the first matrix block of at least minrows rows and constant number of columns. @@ -105,7 +107,9 @@ def countcolumnsvalues(line): # Check if file exists before trying to open if not os.path.exists(filename): - raise IOError(f"File {filename} cannot be found. Please rerun the program specifying a valid filename.") + raise IOError( + f"File {filename} cannot be found. Please rerun the program specifying a valid filename." + ) # make sure fid gets cleaned up with open(filename, "rb") as fid: @@ -134,7 +138,10 @@ def countcolumnsvalues(line): if hignore is not None: for tag in hignore: taglen = len(tag) - if len(hpair[0]) >= taglen and hpair[0][:taglen] == tag: + if ( + len(hpair[0]) >= taglen + and hpair[0][:taglen] == tag + ): flag = False # add header data if flag: @@ -258,7 +265,13 @@ def _findDataBlocks(self): # nf - number of words, ok - has data self._linerecs = numpy.recarray( (nlines,), - dtype=[("idx", int), ("nw0", int), ("nw1", int), ("nf", int), ("ok", bool)], + dtype=[ + ("idx", int), + ("nw0", int), + ("nw1", int), + ("nf", int), + ("ok", bool), + ], ) lr = self._linerecs lr.idx = numpy.arange(nlines) @@ -319,7 +332,9 @@ def _findDataBlocks(self): if self.usecols is None: data = numpy.reshape(lw.value[bb1.nw0 : ee1.nw1], (-1, bb1.nf)) else: - tdata = numpy.empty((len(self.usecols), dend - dbeg), dtype=float) + tdata = numpy.empty( + (len(self.usecols), dend - dbeg), dtype=float + ) for j, trow in zip(self.usecols, tdata): j %= bb1.nf trow[:] = lw.value[bb1.nw0 + j : ee1.nw1 : bb1.nf] diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index 20b34b31..bf585b7f 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -86,8 +86,12 @@ def serialize_data( num_columns = [len(row) for row in data_table] max_columns = max(num_columns) num_col_names = len(dt_colnames) - if max_columns < num_col_names: # assume numpy.loadtxt gives non-irregular array - raise ImproperSizeError("More entries in dt_colnames than columns in data_table.") + if ( + max_columns < num_col_names + ): # assume numpy.loadtxt gives non-irregular array + raise ImproperSizeError( + "More entries in dt_colnames than columns in data_table." + ) named_columns = 0 for idx in range(num_col_names): colname = dt_colnames[idx] diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 61868072..5924df23 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -84,7 +84,11 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): The dictionary containing username, email and orcid of the user/owner, and any other information stored in the global or local config files. """ - runtime_info = {"owner_name": owner_name, "owner_email": owner_email, "owner_orcid": owner_orcid} + runtime_info = { + "owner_name": owner_name, + "owner_email": owner_email, + "owner_orcid": owner_orcid, + } for key, value in copy(runtime_info).items(): if value is None or value == "": del runtime_info[key] @@ -141,7 +145,9 @@ def check_and_build_global_config(skip_config_creation=False): "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" ) print(intro_text) - username = input("Please enter the name you would want future work to be credited to: ").strip() + username = input( + "Please enter the name you would want future work to be credited to: " + ).strip() email = input("Please enter your email: ").strip() orcid = input("Please enter your orcid ID if you know it: ").strip() config = { @@ -208,7 +214,9 @@ def get_density_from_cloud(sample_composition, mp_token=""): ) -def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None, packing_fraction=None): +def compute_mu_using_xraydb( + sample_composition, energy, sample_mass_density=None, packing_fraction=None +): """Compute the attenuation coefficient (mu) using the XrayDB database. Computes mu based on the sample composition and energy. @@ -241,9 +249,19 @@ def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None "Please rerun specifying only one." ) if packing_fraction is not None: - sample_mass_density = get_density_from_cloud(sample_composition) * packing_fraction + sample_mass_density = ( + get_density_from_cloud(sample_composition) * packing_fraction + ) energy_eV = energy * 1000 - mu = material_mu(sample_composition, energy_eV, density=sample_mass_density, kind="total") / 10 + mu = ( + material_mu( + sample_composition, + energy_eV, + density=sample_mass_density, + kind="total", + ) + / 10 + ) return mu @@ -267,7 +285,11 @@ def _model_function(z, diameter, z0, I0, mud, slope): dz = z - z0 length = np.piecewise( dz, - [dz < min_radius, (min_radius <= dz) & (dz <= max_radius), dz > max_radius], + [ + dz < min_radius, + (min_radius <= dz) & (dz <= max_radius), + dz > max_radius, + ], [0, lambda dz: 2 * np.sqrt((diameter / 2) ** 2 - dz**2), 0], ) return (I0 - slope * z) * np.exp(-mud / diameter * length) @@ -278,8 +300,12 @@ def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): convolution), then perform convolution (note that the convolved I values are the same as modeled I values if slit width is close to 0)""" n_points = len(z) - z_left_pad = np.linspace(z.min() - n_points * (z[1] - z[0]), z.min(), n_points) - z_right_pad = np.linspace(z.max(), z.max() + n_points * (z[1] - z[0]), n_points) + z_left_pad = np.linspace( + z.min() - n_points * (z[1] - z[0]), z.min(), n_points + ) + z_right_pad = np.linspace( + z.max(), z.max() + n_points * (z[1] - z[0]), n_points + ) z_extended = np.concatenate([z_left_pad, z, z_right_pad]) I_extended = _model_function(z_extended, diameter, z0, I0, mud, slope) kernel = _top_hat(z_extended - z_extended.mean(), half_slit_width) @@ -296,7 +322,9 @@ def _objective_function(params, z, observed_data): observed/experimental data by minimizing the sum of squared residuals between the observed data and the convolved model data.""" diameter, half_slit_width, z0, I0, mud, slope = params - convolved_model_data = _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope) + convolved_model_data = _extend_z_and_convolve( + z, diameter, half_slit_width, z0, I0, mud, slope + ) residuals = observed_data - convolved_model_data return np.sum(residuals**2) @@ -304,16 +332,27 @@ def _objective_function(params, z, observed_data): def _compute_single_mud(z_data, I_data): """Perform dual annealing optimization and extract the parameters.""" bounds = [ - (1e-5, z_data.max() - z_data.min()), # diameter: [small positive value, upper bound] - (0, (z_data.max() - z_data.min()) / 2), # half slit width: [0, upper bound] + ( + 1e-5, + z_data.max() - z_data.min(), + ), # diameter: [small positive value, upper bound] + ( + 0, + (z_data.max() - z_data.min()) / 2, + ), # half slit width: [0, upper bound] (z_data.min(), z_data.max()), # z0: [min z, max z] - (1e-5, I_data.max()), # I0: [small positive value, max observed intensity] + ( + 1e-5, + I_data.max(), + ), # I0: [small positive value, max observed intensity] (1e-5, 20), # muD: [small positive value, upper bound] (-100000, 100000), # slope: [lower bound, upper bound] ] result = dual_annealing(_objective_function, bounds, args=(z_data, I_data)) diameter, half_slit_width, z0, I0, mud, slope = result.x - convolved_fitted_signal = _extend_z_and_convolve(z_data, diameter, half_slit_width, z0, I0, mud, slope) + convolved_fitted_signal = _extend_z_and_convolve( + z_data, diameter, half_slit_width, z0, I0, mud, slope + ) residuals = I_data - convolved_fitted_signal rmse = np.sqrt(np.mean(residuals**2)) return mud, rmse @@ -343,5 +382,8 @@ def compute_mud(filepath): The best-fit mu*D value. """ z_data, I_data = loadData(filepath, unpack=True) - best_mud, _ = min((_compute_single_mud(z_data, I_data) for _ in range(20)), key=lambda pair: pair[1]) + best_mud, _ = min( + (_compute_single_mud(z_data, I_data) for _ in range(20)), + key=lambda pair: pair[1], + ) return best_mud diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 21c069b1..693bb693 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -9,14 +9,14 @@ "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." ) -invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors." +invalid_tth_emsg = ( + "Two theta exceeds 180 degrees. Please check the input values for errors." +) invalid_q_or_d_or_wavelength_emsg = ( "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) -inf_output_imsg = ( - "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." -) +inf_output_imsg = "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." def _validate_inputs(q, wavelength): diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index a01e8f92..d6f2874a 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -26,7 +26,9 @@ def getSelectionRows(grid): rset.update(grid.GetSelectedRows()) for r, c in grid.GetSelectedCells(): rset.add(r) - blocks = zip(grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight()) + blocks = zip( + grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight() + ) for tl, br in blocks: rset.update(range(tl[0], br[0] + 1)) rv = sorted(rset) @@ -42,7 +44,9 @@ def getSelectionColumns(grid): cset.update(grid.GetSelectedCols()) for r, c in grid.GetSelectedCells(): cset.add(c) - blocks = zip(grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight()) + blocks = zip( + grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight() + ) for tl, br in blocks: cset.update(range(tl[1], br[1] + 1)) rv = sorted(cset) @@ -64,7 +68,9 @@ def getSelectedCells(grid): rcset.update(zip(cols * [r], allcols)) for c in grid.GetSelectedCols(): rcset.update(zip(allrows, rows * [c])) - blocks = zip(grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight()) + blocks = zip( + grid.GetSelectionBlockTopLeft(), grid.GetSelectionBlockBottomRight() + ) for tl, br in blocks: brows = range(tl[0], br[0] + 1) bcols = range(tl[1], br[1] + 1) diff --git a/tests/conftest.py b/tests/conftest.py index 296ae14f..b6d3bf3c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,19 +38,31 @@ def _load(filename): @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) + 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") + return DiffractionObject( + wavelength=2 * np.pi, + xarray=np.array([30, 60]), + yarray=np.array([1, 2]), + xtype="tth", + ) @pytest.fixture def do_minimal_d(): # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values - return DiffractionObject(wavelength=1.54, xarray=np.array([1, 2]), yarray=np.array([1, 2]), xtype="d") + return DiffractionObject( + wavelength=1.54, + xarray=np.array([1, 2]), + yarray=np.array([1, 2]), + xtype="d", + ) @pytest.fixture diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 13b3719b..7db4cc33 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -142,10 +142,16 @@ ], ) def test_diffraction_objects_equality( - do_args_1, do_args_2, expected_equality, wavelength_warning_expected, wavelength_warning_msg + do_args_1, + do_args_2, + expected_equality, + wavelength_warning_expected, + wavelength_warning_msg, ): if wavelength_warning_expected: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): do_1 = DiffractionObject(**do_args_1) do_2 = DiffractionObject(**do_args_2) else: @@ -184,7 +190,12 @@ def test_init_invalid_xtype(): f"Please rerun specifying an xtype from {*XQUANTITIES, }" ), ): - return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="invalid_type", wavelength=1.54) + return DiffractionObject( + xarray=np.empty(0), + yarray=np.empty(0), + xtype="invalid_type", + wavelength=1.54, + ) @pytest.mark.parametrize( @@ -304,7 +315,9 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): target_do = DiffractionObject(**target_do_args) scaled_do = original_do.scale_to(target_do, **scale_inputs) # Check the intensity data is the same as expected - assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) + assert np.allclose( + scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"] + ) @pytest.mark.parametrize( @@ -416,13 +429,25 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): ) def test_get_array_index(do_args, get_array_index_inputs, expected_index): do = DiffractionObject(**do_args) - actual_index = do.get_array_index(get_array_index_inputs["xtype"], get_array_index_inputs["value"]) + actual_index = do.get_array_index( + get_array_index_inputs["xtype"], get_array_index_inputs["value"] + ) assert actual_index == expected_index def test_get_array_index_bad(): - do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth") - with pytest.raises(ValueError, match=re.escape("The 'tth' array is empty. Please ensure it is initialized.")): + do = DiffractionObject( + wavelength=2 * np.pi, + xarray=np.array([]), + yarray=np.array([]), + xtype="tth", + ) + with pytest.raises( + ValueError, + match=re.escape( + "The 'tth' array is empty. Please ensure it is initialized." + ), + ): do.get_array_index(xtype="tth", xvalue=30) @@ -430,7 +455,9 @@ def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): do = DiffractionObject( wavelength=1.54, name="test", @@ -438,7 +465,11 @@ def test_dump(tmp_path, mocker): xarray=np.array(x), yarray=np.array(y), xtype="q", - metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, + metadata={ + "thing1": 1, + "thing2": "thing2", + "package_info": {"package2": "3.4.5"}, + }, ) mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): @@ -547,7 +578,9 @@ def test_dump(tmp_path, mocker): ), ( # C4: Same as C3, but with an optional scat_quantity argument, expect non-empty string for scat_quantity { - "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "xarray": np.array( + [np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi] + ), "yarray": np.array([1.0, 2.0, 3.0]), "xtype": "d", "wavelength": 4.0 * np.pi, @@ -586,15 +619,23 @@ def test_init_valid( wavelength_warning_msg, ): if divide_by_zero_warning_expected: - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): actual_do_dict = DiffractionObject(**do_init_args).__dict__ elif wavelength_warning_expected: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): actual_do_dict = DiffractionObject(**do_init_args).__dict__ else: actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( - actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_uuid']" + actual_do_dict, + expected_do_dict, + ignore_order=True, + significant_digits=13, + exclude_paths="root['_uuid']", ) assert diff == {} @@ -624,7 +665,10 @@ def test_init_invalid_args( def test_all_array_getter(do_minimal_tth): actual_do = do_minimal_tth print(actual_do.all_arrays) - expected_all_arrays = [[1, 0.51763809, 30, 12.13818192], [2, 1, 60, 6.28318531]] + expected_all_arrays = [ + [1, 0.51763809, 30, 12.13818192], + [2, 1, 60, 6.28318531], + ] assert np.allclose(actual_do.all_arrays, expected_all_arrays) @@ -647,7 +691,9 @@ def test_uuid_getter(do_minimal): def test_uuid_getter_with_mock(mocker, do_minimal): mocker.patch.object( - DiffractionObject, "uuid", new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") + DiffractionObject, + "uuid", + new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87"), ) do = do_minimal assert do.uuid == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87") @@ -671,7 +717,10 @@ def test_xarray_yarray_length_mismatch(): "re-initialize 'DiffractionObject'with valid 'xarray' and 'yarray's", ): DiffractionObject( - xarray=np.array([1.0, 2.0]), yarray=np.array([0.0, 0.0, 0.0]), xtype="tth", wavelength=1.54 + xarray=np.array([1.0, 2.0]), + yarray=np.array([0.0, 0.0, 0.0]), + xtype="tth", + wavelength=1.54, ) @@ -755,9 +804,13 @@ def test_copy_object(do_minimal): ), ], ) -def test_scalar_operations(operation, starting_yarray, scalar_value, expected_yarray, do_minimal_tth): +def test_scalar_operations( + operation, starting_yarray, scalar_value, expected_yarray, do_minimal_tth +): do = do_minimal_tth - expected_xarray_constant = np.array([[0.51763809, 30.0, 12.13818192], [1.0, 60.0, 6.28318531]]) + expected_xarray_constant = np.array( + [[0.51763809, 30.0, 12.13818192], [1.0, 60.0, 6.28318531]] + ) assert np.allclose(do.all_arrays[:, [1, 2, 3]], expected_xarray_constant) assert np.allclose(do.all_arrays[:, 0], starting_yarray) if operation == "add": @@ -775,8 +828,12 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya assert np.allclose(do_right_op.all_arrays[:, 0], expected_yarray) assert np.allclose(do_left_op.all_arrays[:, 0], expected_yarray) # Ensure x-values are unchanged - assert np.allclose(do_right_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant) - assert np.allclose(do_left_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant) + assert np.allclose( + do_right_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant + ) + assert np.allclose( + do_left_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant + ) @pytest.mark.parametrize( @@ -785,23 +842,63 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya # Test addition, subtraction, multiplication, and division of two DO objects ( # Test addition of two DO objects, expect combined yarray values "add", - np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), - np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array( + [ + [2.0, 0.51763809, 30.0, 12.13818192], + [4.0, 1.0, 60.0, 6.28318531], + ] + ), + np.array( + [ + [2.0, 0.51763809, 30.0, 12.13818192], + [4.0, 1.0, 60.0, 6.28318531], + ] + ), ), ( # Test subtraction of two DO objects, expect differences in yarray values "sub", - np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), - np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), + np.array( + [ + [0.0, 0.51763809, 30.0, 12.13818192], + [0.0, 1.0, 60.0, 6.28318531], + ] + ), + np.array( + [ + [0.0, 0.51763809, 30.0, 12.13818192], + [0.0, 1.0, 60.0, 6.28318531], + ] + ), ), ( # Test multiplication of two DO objects, expect multiplication in yarray values "mul", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [4.0, 1.0, 60.0, 6.28318531], + ] + ), + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [4.0, 1.0, 60.0, 6.28318531], + ] + ), ), ( # Test division of two DO objects, expect division in yarray values "div", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [1.0, 1.0, 60.0, 6.28318531], + ] + ), + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [1.0, 1.0, 60.0, 6.28318531], + ] + ), ), ], ) @@ -814,10 +911,22 @@ def test_binary_operator_on_do( do_1 = do_minimal_tth do_2 = do_minimal_tth assert np.allclose( - do_1.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) + do_1.all_arrays, + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [2.0, 1.0, 60.0, 6.28318531], + ] + ), ) assert np.allclose( - do_2.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) + do_2.all_arrays, + np.array( + [ + [1.0, 0.51763809, 30.0, 12.13818192], + [2.0, 1.0, 60.0, 6.28318531], + ] + ), ) if operation == "add": @@ -833,8 +942,12 @@ def test_binary_operator_on_do( do_1_y_modified = do_1 / do_2 do_2_y_modified = do_2 / do_1 - assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) - assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) + assert np.allclose( + do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified + ) + assert np.allclose( + do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified + ) def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @@ -848,9 +961,13 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): (lambda x, y: x / y), # Test division ] for operation in operations: - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + with pytest.raises( + TypeError, match=re.escape(invalid_add_type_error_msg) + ): operation(do, invalid_value) - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + with pytest.raises( + TypeError, match=re.escape(invalid_add_type_error_msg) + ): operation(invalid_value, do) @@ -861,7 +978,9 @@ def test_operator_invalid_xarray_values_not_equal( # Add two DO objects with different xarray values but equal in shape, expect ValueError do_1 = do_minimal_tth do_2 = do_minimal_d - with pytest.raises(ValueError, match=re.escape(x_values_not_equal_error_msg)): + with pytest.raises( + ValueError, match=re.escape(x_values_not_equal_error_msg) + ): if operation == "add": do_1 + do_2 elif operation == "sub": @@ -879,7 +998,9 @@ def test_operator_invalid_xarray_shape_not_equal( # Add two DO objects with different xarrays shape, expect ValueError do_1 = do_minimal do_2 = do_minimal_tth - with pytest.raises(ValueError, match=re.escape(x_values_not_equal_error_msg)): + with pytest.raises( + ValueError, match=re.escape(x_values_not_equal_error_msg) + ): if operation == "add": do_1 + do_2 elif operation == "sub": diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index f825139c..7580c672 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -76,5 +76,7 @@ def test_loadData_headers(datafile): delimiter = ": " # what our data should be separated by # Load data with headers - hdata = loadData(loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore) + hdata = loadData( + loaddatawithheaders, headers=True, hdel=delimiter, hignore=hignore + ) assert hdata == expected diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 0ba397ad..039877f6 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -3,7 +3,10 @@ import numpy import pytest -from diffpy.utils.parsers.custom_exceptions import ImproperSizeError, UnsupportedTypeError +from diffpy.utils.parsers.custom_exceptions import ( + ImproperSizeError, + UnsupportedTypeError, +) from diffpy.utils.parsers.loaddata import loadData from diffpy.utils.parsers.serialization import deserialize_data, serialize_data @@ -23,7 +26,13 @@ def test_load_multiple(tmp_path, datafile): data_table = loadData(headerfile) # check path extraction - generated_data = serialize_data(headerfile, hdata, data_table, dt_colnames=["r", "gr"], show_path=True) + generated_data = serialize_data( + headerfile, + hdata, + data_table, + dt_colnames=["r", "gr"], + show_path=True, + ) assert headerfile == Path(generated_data[headerfile.name].pop("path")) # rerun without path information and save to file @@ -60,28 +69,54 @@ def test_exceptions(datafile): # various dt_colnames inputs with pytest.raises(ImproperSizeError): - serialize_data(loadfile, hdata, data_table, dt_colnames=["one", "two", "three is too many"]) + serialize_data( + loadfile, + hdata, + data_table, + dt_colnames=["one", "two", "three is too many"], + ) # check proper output - normal = serialize_data(loadfile, hdata, data_table, dt_colnames=["r", "gr"]) + normal = serialize_data( + loadfile, hdata, data_table, dt_colnames=["r", "gr"] + ) data_name = list(normal.keys())[0] r_list = normal[data_name]["r"] gr_list = normal[data_name]["gr"] # three equivalent ways to denote no column names - missing_parameter = serialize_data(loadfile, hdata, data_table, show_path=False) - empty_parameter = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[]) - none_entry_parameter = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, None]) + missing_parameter = serialize_data( + loadfile, hdata, data_table, show_path=False + ) + empty_parameter = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[] + ) + none_entry_parameter = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[None, None] + ) # check equivalence assert missing_parameter == empty_parameter assert missing_parameter == none_entry_parameter - assert numpy.allclose(missing_parameter[data_name]["data table"], data_table) + assert numpy.allclose( + missing_parameter[data_name]["data table"], data_table + ) # extract a single column - r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=["r"]) - gr_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "gr"]) - incorrect_r_extract = serialize_data(loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "r"]) + r_extract = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=["r"] + ) + gr_extract = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "gr"] + ) + incorrect_r_extract = serialize_data( + loadfile, hdata, data_table, show_path=False, dt_colnames=[None, "r"] + ) # check proper columns extracted - assert numpy.allclose(gr_extract[data_name]["gr"], incorrect_r_extract[data_name]["r"]) + assert numpy.allclose( + gr_extract[data_name]["gr"], incorrect_r_extract[data_name]["r"] + ) assert "r" not in gr_extract[data_name] - assert "gr" not in r_extract[data_name] and "gr" not in incorrect_r_extract[data_name] + assert ( + "gr" not in r_extract[data_name] + and "gr" not in incorrect_r_extract[data_name] + ) # check correct values extracted assert numpy.allclose(r_extract[data_name]["r"], r_list) assert numpy.allclose(gr_extract[data_name]["gr"], gr_list) diff --git a/tests/test_tools.py b/tests/test_tools.py index 4843fede..8d69c6c0 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -20,30 +20,55 @@ "runtime_inputs, expected", [ # config file in home is present, no config in cwd. various runtime values passed # C1: nothing passed in, expect uname, email, orcid from home_config - ({}, {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}), + ( + {}, + { + "owner_name": "home_ownername", + "owner_email": "home@email.com", + "owner_orcid": "home_orcid", + }, + ), # C2: empty strings passed in, expect uname, email, orcid from home_config ( {"owner_name": "", "owner_email": "", "owner_orcid": ""}, - {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "home_ownername", + "owner_email": "home@email.com", + "owner_orcid": "home_orcid", + }, ), # C3: just owner name passed in at runtime. expect runtime_oname but others from config ( {"owner_name": "runtime_ownername"}, - {"owner_name": "runtime_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "runtime_ownername", + "owner_email": "home@email.com", + "owner_orcid": "home_orcid", + }, ), # C4: just owner email passed in at runtime. expect runtime_email but others from config ( {"owner_email": "runtime@email.com"}, - {"owner_name": "home_ownername", "owner_email": "runtime@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "home_ownername", + "owner_email": "runtime@email.com", + "owner_orcid": "home_orcid", + }, ), # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config ( {"owner_orcid": "runtime_orcid"}, - {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "runtime_orcid"}, + { + "owner_name": "home_ownername", + "owner_email": "home@email.com", + "owner_orcid": "runtime_orcid", + }, ), ], ) -def test_get_user_info_with_home_conf_file(runtime_inputs, expected, user_filesystem, mocker): +def test_get_user_info_with_home_conf_file( + runtime_inputs, expected, user_filesystem, mocker +): # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] # is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) @@ -57,35 +82,63 @@ def test_get_user_info_with_home_conf_file(runtime_inputs, expected, user_filesy [ # tests as before but now config file present in cwd and home but orcid # missing in the cwd config # C1: nothing passed in, expect uname, email from local config, orcid from home_config - ({}, {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}), + ( + {}, + { + "owner_name": "cwd_ownername", + "owner_email": "cwd@email.com", + "owner_orcid": "home_orcid", + }, + ), # C2: empty strings passed in, expect uname, email, orcid from home_config ( {"owner_name": "", "owner_email": "", "owner_orcid": ""}, - {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "cwd_ownername", + "owner_email": "cwd@email.com", + "owner_orcid": "home_orcid", + }, ), # C3: just owner name passed in at runtime. expect runtime_oname but others from config ( {"owner_name": "runtime_ownername"}, - {"owner_name": "runtime_ownername", "owner_email": "cwd@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "runtime_ownername", + "owner_email": "cwd@email.com", + "owner_orcid": "home_orcid", + }, ), # C4: just owner email passed in at runtime. expect runtime_email but others from config ( {"owner_email": "runtime@email.com"}, - {"owner_name": "cwd_ownername", "owner_email": "runtime@email.com", "owner_orcid": "home_orcid"}, + { + "owner_name": "cwd_ownername", + "owner_email": "runtime@email.com", + "owner_orcid": "home_orcid", + }, ), # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config ( {"owner_orcid": "runtime_orcid"}, - {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com", "owner_orcid": "runtime_orcid"}, + { + "owner_name": "cwd_ownername", + "owner_email": "cwd@email.com", + "owner_orcid": "runtime_orcid", + }, ), ], ) -def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_filesystem, mocker): +def test_get_user_info_with_local_conf_file( + runtime_inputs, expected, user_filesystem, mocker +): # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] # is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) - local_config_data = {"owner_name": "cwd_ownername", "owner_email": "cwd@email.com"} + local_config_data = { + "owner_name": "cwd_ownername", + "owner_email": "cwd@email.com", + } with open(user_filesystem[1] / "diffpyconfig.json", "w") as f: json.dump(local_config_data, f) actual = get_user_info(**runtime_inputs) @@ -97,16 +150,25 @@ def test_get_user_info_with_local_conf_file(runtime_inputs, expected, user_files [ # Check check_and_build_global_config() builds correct config when config is found missing ( # C1: user inputs valid name, email and orcid {"user_inputs": ["input_name", "input@email.com", "input_orcid"]}, - {"owner_email": "input@email.com", "owner_orcid": "input_orcid", "owner_name": "input_name"}, + { + "owner_email": "input@email.com", + "owner_orcid": "input_orcid", + "owner_name": "input_name", + }, ), - ({"user_inputs": ["", "", ""]}, None), # C2: empty strings passed in, expect no config file created + ( + {"user_inputs": ["", "", ""]}, + None, + ), # C2: empty strings passed in, expect no config file created ( # C3: just username input, expect config file but with some empty values {"user_inputs": ["input_name", "", ""]}, {"owner_email": "", "owner_orcid": "", "owner_name": "input_name"}, ), ], ) -def test_check_and_build_global_config(test_inputs, expected, user_filesystem, mocker): +def test_check_and_build_global_config( + test_inputs, expected, user_filesystem, mocker +): # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] # is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) @@ -131,7 +193,11 @@ def test_check_and_build_global_config_file_exists(user_filesystem, mocker): mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) confile = user_filesystem[0] / "diffpyconfig.json" - expected = {"owner_name": "home_ownername", "owner_email": "home@email.com", "owner_orcid": "home_orcid"} + expected = { + "owner_name": "home_ownername", + "owner_email": "home@email.com", + "owner_orcid": "home_orcid", + } actual_bool = check_and_build_global_config() assert actual_bool is True with open(confile, "r") as f: @@ -152,11 +218,29 @@ def test_check_and_build_global_config_skipped(user_filesystem, mocker): params_package_info = [ (["diffpy.utils", None], {"package_info": {"diffpy.utils": "3.3.0"}}), - (["package1", None], {"package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), - (["package1", {"thing1": 1}], {"thing1": 1, "package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}), ( - ["package1", {"package_info": {"package1": "1.1.0", "package2": "3.4.5"}}], - {"package_info": {"package1": "1.2.3", "package2": "3.4.5", "diffpy.utils": "3.3.0"}}, + ["package1", None], + {"package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}}, + ), + ( + ["package1", {"thing1": 1}], + { + "thing1": 1, + "package_info": {"package1": "1.2.3", "diffpy.utils": "3.3.0"}, + }, + ), + ( + [ + "package1", + {"package_info": {"package1": "1.1.0", "package2": "3.4.5"}}, + ], + { + "package_info": { + "package1": "1.2.3", + "package2": "3.4.5", + "diffpy.utils": "3.3.0", + } + }, ), ] @@ -164,7 +248,11 @@ def test_check_and_build_global_config_skipped(user_filesystem, mocker): @pytest.mark.parametrize("inputs, expected", params_package_info) def test_get_package_info(monkeypatch, inputs, expected): monkeypatch.setattr( - importlib.metadata, "version", lambda package_name: "3.3.0" if package_name == "diffpy.utils" else "1.2.3" + importlib.metadata, + "version", + lambda package_name: ( + "3.3.0" if package_name == "diffpy.utils" else "1.2.3" + ), ) actual_metadata = get_package_info(inputs[0], metadata=inputs[1]) assert actual_metadata == expected @@ -202,7 +290,9 @@ def test_compute_mu_using_xraydb_bad(inputs): def test_compute_mud(tmp_path): diameter, slit_width, z0, I0, mud, slope = 1, 0.1, 0, 1e5, 3, 0 z_data = np.linspace(-1, 1, 50) - convolved_I_data = _extend_z_and_convolve(z_data, diameter, slit_width, z0, I0, mud, slope) + convolved_I_data = _extend_z_and_convolve( + z_data, diameter, slit_width, z0, I0, mud, slope + ) directory = Path(tmp_path) file = directory / "testfile" diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 0396c420..4617584c 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -3,7 +3,14 @@ import numpy as np import pytest -from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q +from diffpy.utils.transforms import ( + d_to_q, + d_to_tth, + q_to_d, + q_to_tth, + tth_to_d, + tth_to_q, +) @pytest.mark.parametrize( @@ -30,7 +37,9 @@ ) def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): if wavelength is None: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): actual_tth = q_to_tth(q, wavelength) else: actual_tth = q_to_tth(q, wavelength) @@ -53,7 +62,9 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): ), ], ) -def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wavelength_error_msg): +def test_q_to_tth_bad( + wavelength, q, expected_error_type, invalid_q_or_d_or_wavelength_error_msg +): expected_error_msg = invalid_q_or_d_or_wavelength_error_msg with pytest.raises(expected_error_type, match=expected_error_msg): q_to_tth(wavelength, q) @@ -83,7 +94,9 @@ def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wave ) def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): if wavelength is None: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): actual_q = tth_to_q(tth, wavelength) else: actual_q = tth_to_q(tth, wavelength) @@ -109,7 +122,9 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): ), ], ) -def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): +def test_tth_to_q_bad( + wavelength, tth, expected_error_type, expected_error_msg +): with pytest.raises(expected_error_type, match=expected_error_msg): tth_to_q(tth, wavelength) @@ -122,12 +137,16 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): (np.array([]), np.array([]), False), # C2: ( # 1. Valid q values, expect d values without warning - np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), + np.array( + [0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi] + ), np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), False, ), ( # 2. Valid q values containing 0, expect d values with divide by zero warning - np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), + np.array( + [0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi] + ), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), True, ), @@ -135,7 +154,9 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): ) def test_q_to_d(q, expected_d, warning_expected): if warning_expected: - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): actual_d = q_to_d(q) else: actual_d = q_to_d(q) @@ -157,7 +178,9 @@ def test_q_to_d(q, expected_d, warning_expected): ) def test_d_to_q(d, expected_q, zero_divide_error_expected): if zero_divide_error_expected: - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): actual_q = d_to_q(d) else: actual_q = d_to_q(d) @@ -173,7 +196,12 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): # C2: Empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), # C3: User specified valid tth values between 0-180 degrees (without wavelength) - (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), + ( + None, + np.array([0, 30, 60, 90, 120, 180]), + np.array([0, 1, 2, 3, 4, 5]), + False, + ), ( # C4: User specified valid tth values between 0-180 degrees (with wavelength) 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), @@ -182,12 +210,22 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): ), ], ) -def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, wavelength_warning_msg): +def test_tth_to_d( + wavelength, + tth, + expected_d, + divide_by_zero_warning_expected, + wavelength_warning_msg, +): if wavelength is None: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): actual_d = tth_to_d(tth, wavelength) elif divide_by_zero_warning_expected: - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): actual_d = tth_to_d(tth, wavelength) else: actual_d = tth_to_d(tth, wavelength) @@ -211,7 +249,9 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, ), ], ) -def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_msg): +def test_tth_to_d_invalid( + wavelength, tth, expected_error_type, expected_error_msg +): with pytest.raises(expected_error_type, match=expected_error_msg): tth_to_d(tth, wavelength) @@ -224,22 +264,41 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m # C2: Empty d values with wavelength, expect empty tth values (4 * np.pi, np.empty((0)), np.empty(0), False), # C3: Valid d values, no wavelength, expect valid and non-empty tth values - (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), + ( + None, + np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), + np.array([0, 1, 2, 3, 4, 5]), + True, + ), ( # C4: Valid d values with wavelength, expect valid and non-empty thh values 4 * np.pi, - np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), + np.array( + [4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi] + ), np.array([60.0, 90.0, 120.0]), False, ), ], ) -def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, wavelength_warning_msg): +def test_d_to_tth( + wavelength, + d, + expected_tth, + divide_by_zero_warning_expected, + wavelength_warning_msg, +): if wavelength is None and not divide_by_zero_warning_expected: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): actual_tth = d_to_tth(d, wavelength) elif wavelength is None and divide_by_zero_warning_expected: - with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): - with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + with pytest.warns( + UserWarning, match=re.escape(wavelength_warning_msg) + ): + with pytest.warns( + RuntimeWarning, match="divide by zero encountered in divide" + ): actual_tth = d_to_tth(d, wavelength) else: actual_tth = d_to_tth(d, wavelength) @@ -255,7 +314,9 @@ def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) -def test_d_to_tth_bad(wavelength, d, expected_error_type, invalid_q_or_d_or_wavelength_error_msg): +def test_d_to_tth_bad( + wavelength, d, expected_error_type, invalid_q_or_d_or_wavelength_error_msg +): expected_error_msg = invalid_q_or_d_or_wavelength_error_msg with pytest.raises(expected_error_type, match=expected_error_msg): d_to_tth(d, wavelength) From f4aebe83df09835bebcde425fe4b1c38ac7b7ca2 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Sun, 2 Feb 2025 20:44:25 -0500 Subject: [PATCH 390/445] style: apply manual line-shortening to files --- news/linelength79.rst | 23 ++++ src/diffpy/utils/diffraction_objects.py | 123 ++++++++++++-------- src/diffpy/utils/parsers/loaddata.py | 70 +++++++----- src/diffpy/utils/parsers/serialization.py | 35 ++++-- src/diffpy/utils/resampler.py | 49 ++++---- src/diffpy/utils/tools.py | 131 +++++++++++++--------- src/diffpy/utils/transforms.py | 31 +++-- src/diffpy/utils/validators.py | 5 +- tests/conftest.py | 37 +++--- tests/test_diffraction_objects.py | 103 +++++++++++------ tests/test_loaddata.py | 6 +- tests/test_resample.py | 3 +- tests/test_tools.py | 61 ++++++---- tests/test_transforms.py | 74 ++++++++---- 14 files changed, 482 insertions(+), 269 deletions(-) create mode 100644 news/linelength79.rst diff --git a/news/linelength79.rst b/news/linelength79.rst new file mode 100644 index 00000000..be12d2fb --- /dev/null +++ b/news/linelength79.rst @@ -0,0 +1,23 @@ +**Added:** + +* update isort, flake8 and black to set line limit to 79 + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 1276e08d..25f32f4d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -31,15 +31,17 @@ ] x_values_not_equal_emsg = ( - "The two objects have different values in x arrays (my_do.all_arrays[:, [1, 2, 3]]). " - "Please ensure the x values of the two objects are identical by re-instantiating " - "the DiffractionObject with the correct x value inputs." + "The two objects have different values in x arrays " + "(my_do.all_arrays[:, [1, 2, 3]]). " + "Please ensure the x values of the two objects are identical by " + "re-instantiating the DiffractionObject with the correct x value inputs." ) invalid_add_type_emsg = ( - "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " - "Please rerun by adding another DiffractionObject instance or a scalar value. " - "e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" + "You may only add a DiffractionObject with another DiffractionObject or " + "a scalar value. " + "Please rerun by adding another DiffractionObject instance or a " + "scalar value. e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" ) @@ -70,11 +72,14 @@ class DiffractionObject: Attributes ---------- scat_quantity : str - The type of scattering experiment (e.g., "x-ray", "neutron"). Default is an empty string "". + The type of scattering experiment (e.g., "x-ray", "neutron"). Default + is an empty string "". wavelength : float - The wavelength of the incoming beam, specified in angstroms (Å). Default is none. + The wavelength of the incoming beam, specified in angstroms (Å). + Default is none. name: str - The name or label for the scattering data. Default is an empty string "". + The name or label for the scattering data. Default is an empty string + "". qmin : float The minimum q value. qmax : float @@ -108,7 +113,8 @@ def __init__( yarray : ndarray The dependent variable array corresponding to intensity values. xtype : str - The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. + The type of the independent variable in `xarray`. Must be one of + {*XQUANTITIES}. wavelength : float, optional, default is None. The wavelength of the incoming beam, specified in angstroms (Å) scat_quantity : str, optional, default is an empty string "". @@ -124,7 +130,7 @@ def __init__( >>> 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) + >>> x = np.array([0.12, 0.24, 0.31, 0.4]) # independent variable (e.g., q) # noqa: E501 >>> y = np.array([10, 20, 40, 60]) # intensity values >>> metadata = { ... "sample": "rock salt from the beach", @@ -211,23 +217,29 @@ def __add__(self, other): Parameters ---------- other : DiffractionObject, int, or float - The item to be added. If `other` is a scalar value, this value will be added to each element of the - yarray of this DiffractionObject instance. If `other` is another DiffractionObject, the yarrays of the - two DiffractionObjects will be combined element-wise. The result is a new DiffractionObject instance, - representing the addition and using the xarray from the left-hand side DiffractionObject. + The item to be added. If `other` is a scalar value, this value + will be added to each element of the yarray of this + DiffractionObject instance. If `other` is another DiffractionObject + , the yarrays of the two DiffractionObjects will be combined + element -wise. The result is a new DiffractionObject instance, + representing the addition and using the xarray from the left-hand + side DiffractionObject. Returns ------- DiffractionObject - The new DiffractionObject instance with modified yarray values. This instance is a deep copy of the - original with the additions applied. + The new DiffractionObject instance with modified yarray values. + This instance is a deep copy of the original with the additions + applied. Raises ------ ValueError - Raised when the xarrays of two DiffractionObject instances are not equal. + Raised when the xarrays of two DiffractionObject instances are + not equal. TypeError - Raised when `other` is not an instance of DiffractionObject, int, or float. + Raised when `other` is not an instance of DiffractionObject, int, + or float. Examples -------- @@ -253,12 +265,14 @@ def __sub__(self, other): """Subtract scalar value or another DiffractionObject to the yarray of the DiffractionObject. - This method behaves similarly to the `__add__` method, but performs subtraction instead of addition. - For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + This method behaves similarly to the `__add__` method, but performs + subtraction instead of addition. For details on parameters, returns + , and exceptions, refer to the documentation for `__add__`. Examples -------- - Subtract a scalar value from the yarray of a DiffractionObject instance: + Subtract a scalar value from the yarray of a DiffractionObject + instance: >>> new_do = my_do - 10.1 Subtract the yarrays of two DiffractionObject instances: @@ -279,12 +293,14 @@ def __mul__(self, other): """Multiply a scalar value or another DiffractionObject with the yarray of this DiffractionObject. - This method behaves similarly to the `__add__` method, but performs multiplication instead of addition. - For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + This method behaves similarly to the `__add__` method, but performs + multiplication instead of addition. For details on parameters, + returns, and exceptions, refer to the documentation for `__add__`. Examples -------- - Multiply a scalar value with the yarray of a DiffractionObject instance: + Multiply a scalar value with the yarray of a DiffractionObject + instance: >>> new_do = my_do * 3.5 Multiply the yarrays of two DiffractionObject instances: @@ -305,8 +321,9 @@ def __truediv__(self, other): """Divide the yarray of this DiffractionObject by a scalar value or another DiffractionObject. - This method behaves similarly to the `__add__` method, but performs division instead of addition. - For details on parameters, returns, and exceptions, refer to the documentation for `__add__`. + This method behaves similarly to the `__add__` method, but performs + division instead of addition. For details on parameters, returns, + and exceptions, refer to the documentation for `__add__`. Examples -------- @@ -344,8 +361,8 @@ def all_arrays(self): Returns ------- ndarray - The shape (len(data), 4) 2D array with columns containing the `yarray` (intensity) - and the `xarray` values in q, tth, and d. + The shape (len(data), 4) 2D array with columns containing the ` + yarray` (intensity) and the `xarray` values in q, tth, and d. Examples -------- @@ -399,21 +416,24 @@ def get_array_index(self, xtype, xvalue): Parameters ---------- xtype : str - The type of the independent variable in `xarray`. Must be one of {*XQUANTITIES}. + The type of the independent variable in `xarray`. Must be one + of {*XQUANTITIES}. xvalue : float The value of the xtype to find the closest index for. Returns ------- index : int - The index of the closest value in the array associated with the specified xtype and the value provided. + The index of the closest value in the array associated with the + specified xtype and the value provided. """ xtype = self._input_xtype xarray = self.on_xtype(xtype)[0] if len(xarray) == 0: raise ValueError( - f"The '{xtype}' array is empty. Please ensure it is initialized." + f"The '{xtype}' array is empty. " + "Please ensure it is initialized." ) index = (np.abs(xarray - xvalue)).argmin() return index @@ -486,10 +506,13 @@ def scale_to( """Return a new diffraction object which is the current object but rescaled in y to the target. - By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max intensity from each object. - Otherwise, y-value in the target at the closest specified x-value will be used as the factor to scale to. - The entire array is scaled by this factor so that one object places on top of the other at that point. - If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. + By default, if `q`, `tth`, or `d` are not provided, scaling is + based on the max intensity from each object. Otherwise, y-value in + the target at the closest specified x-value will be used as the + factor to scale to. The entire array is scaled by this factor so + that one object places on top of the other at that point. If + multiple values of `q`, `tth`, or `d` are provided, an error will + be raised. Parameters ---------- @@ -497,8 +520,9 @@ def scale_to( The diffraction object you want to scale the current one onto. q, tth, d : float, optional, default is None - The value of the x-array where you want the curves to line up vertically. - Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. + The value of the x-array where you want the curves to line up + vertically. Specify a value on one of the allowed grids, q, tth, + or d), e.g., q=10. offset : float, optional, default is None The offset to add to the scaled y-values. @@ -546,7 +570,8 @@ def on_xtype(self, xtype): Parameters ---------- xtype : str - The type of quantity for the independent variable chosen from {*XQUANTITIES, } + The type of quantity for the independent variable chosen from + {*XQUANTITIES, } Raises ------ @@ -556,7 +581,8 @@ def on_xtype(self, xtype): Returns ------- (xarray, yarray) : tuple of ndarray - The tuple containing two 1D numpy arrays with x and y data for the specified xtype. + The tuple containing two 1D numpy arrays with x and y data for + the specified xtype. """ if xtype.lower() in ANGLEQUANTITIES: return self.on_tth() @@ -576,12 +602,13 @@ def dump(self, filepath, xtype=None): filepath : str The filepath where the diffraction object will be dumped xtype : str, optional, default is q - The type of quantity for the independent variable chosen from {*XQUANTITIES, } + The type of quantity for the independent variable chosen from + {*XQUANTITIES, } Examples -------- - To save a diffraction object to a file named "diffraction_data.chi" in the current directory - with the independent variable 'q': + To save a diffraction object to a file named "diffraction_data.chi" + in the current directory with the independent variable 'q': >>> file = "diffraction_data.chi" >>> do.dump(file, xtype="q") @@ -591,7 +618,8 @@ def dump(self, filepath, xtype=None): >>> file = "./output/diffraction_data.chi" >>> do.dump(file, xtype="q") - To save the diffraction data with a different independent variable, such as 'tth': + To save the diffraction data with a different independent variable, + such as 'tth': >>> file = "diffraction_data_tth.chi" >>> do.dump(file, xtype="tth") @@ -615,7 +643,9 @@ def dump(self, filepath, xtype=None): with open(filepath, "w") as f: f.write( - f"[DiffractionObject]\nname = {self.name}\nwavelength = {self.wavelength}\n" + f"[DiffractionObject]\n" + f"name = {self.name}\n" + f"wavelength = {self.wavelength}\n" f"scat_quantity = {self.scat_quantity}\n" ) for key, value in self.metadata.items(): @@ -629,6 +659,7 @@ def copy(self): Returns ------- DiffractionObject - The new instance of DiffractionObject, which is a deep copy of the current instance. + The new instance of DiffractionObject, which is a deep copy of + the current instance. """ return deepcopy(self) diff --git a/src/diffpy/utils/parsers/loaddata.py b/src/diffpy/utils/parsers/loaddata.py index 8154f8d5..05d37497 100644 --- a/src/diffpy/utils/parsers/loaddata.py +++ b/src/diffpy/utils/parsers/loaddata.py @@ -25,53 +25,62 @@ def loadData( ): """Find and load data from a text file. - The data block is identified as the first matrix block of at least minrows rows and constant number of columns. - This seems to work for most of the datafiles including those generated by diffpy programs. + The data block is identified as the first matrix block of at least + minrows rows and constant number of columns. This seems to work for most + of the datafiles including those generated by diffpy programs. Parameters ---------- filename Name of the file we want to load data from. minrows: int - Minimum number of rows in the first data block. All rows must have the same number of floating - point values. + Minimum number of rows in the first data block. All rows must have + the same number of floating point values. headers: bool - when False (default), the function returns a numpy array of the data in the data block. - When True, the function instead returns a dictionary of parameters and their corresponding - values parsed from header (information prior the data block). See hdel and hignore for options - to help with parsing header information. + when False (default), the function returns a numpy array of the data + in the data block. When True, the function instead returns a + dictionary of parameters and their corresponding values parsed from + header (information prior the data block). See hdel and hignore for + options to help with parsing header information. hdel: str - (Only used when headers enabled.) Delimiter for parsing header information (default '='). e.g. using - default hdel, the line 'parameter = p_value' is put into the dictionary as {parameter: p_value}. + (Only used when headers enabled.) Delimiter for parsing header + information (default '='). e.g. using default hdel, the line ' + parameter = p_value' is put into the dictionary as + {parameter: p_value}. hignore: list - (Only used when headers enabled.) Ignore header rows beginning with any elements in hignore. - e.g. hignore=['# ', '['] causes the following lines to be skipped: '# qmax=10', '[defaults]'. + (Only used when headers enabled.) Ignore header rows beginning with + any elements in hignore. e.g. hignore=['# ', '['] causes the + following lines to be skipped: '# qmax=10', '[defaults]'. kwargs: - Keyword arguments that are passed to numpy.loadtxt including the following arguments below. (See - numpy.loadtxt for more details.) Only pass kwargs used by numpy.loadtxt. + Keyword arguments that are passed to numpy.loadtxt including the + following arguments below. (See numpy.loadtxt for more details.) Only + pass kwargs used by numpy.loadtxt. Useful kwargs ============= comments: str, sequence of str - The characters or list of characters used to indicate the start of a comment (default '#'). - Comment lines are ignored. + The characters or list of characters used to indicate the start of a + comment (default '#'). Comment lines are ignored. delimiter: str - Delimiter for the data in the block (default use whitespace). For comma-separated data blocks, - set delimiter to ','. + Delimiter for the data in the block (default use whitespace). For + comma-separated data blocks, set delimiter to ','. unpack: bool - Return data as a sequence of columns that allows tuple unpacking such as x, y = - loadData(FILENAME, unpack=True). Note transposing the loaded array as loadData(FILENAME).T has the same - effect. + Return data as a sequence of columns that allows tuple unpacking such + as x, y = loadData(FILENAME, unpack=True). Note transposing the + loaded array as loadData(FILENAME).T has the same effect. usecols: - Zero-based index of columns to be loaded, by default use all detected columns. The reading skips - data blocks that do not have the usecols-specified columns. + Zero-based index of columns to be loaded, by default use all detected + columns. The reading skips data blocks that do not have the usecols- + specified columns. Returns ------- data_block: ndarray - A numpy array containing the found data block. (This is not returned if headers is enabled.) + A numpy array containing the found data block. (This is not returned + if headers is enabled.) hdata: dict - If headers are enabled, return a dictionary of parameters read from the header. + If headers are enabled, return a dictionary of parameters read from + the header. """ from numpy import array, loadtxt @@ -108,7 +117,10 @@ def countcolumnsvalues(line): # Check if file exists before trying to open if not os.path.exists(filename): raise IOError( - f"File {filename} cannot be found. Please rerun the program specifying a valid filename." + ( + f"File {filename} cannot be found. " + "Please rerun the program specifying a valid filename." + ) ) # make sure fid gets cleaned up @@ -194,7 +206,8 @@ class TextDataLoader(object): minrows: int Minimum number of rows in the first data block. (Default 10.) usecols: tuple - Which columns in our dataset to use. Ignores all other columns. If None (default), use all columns. + Which columns in our dataset to use. Ignores all other columns. If + None (default), use all columns. skiprows Rows in dataset to skip. (Currently not functional.) """ @@ -242,7 +255,8 @@ def readfp(self, fp, append=False): File details include: * File name. * All data blocks findable by loadData. - * Headers (if present) for each data block. (Generally the headers contain column name information). + * Headers (if present) for each data block. (Generally the headers + contain column name information). """ self._reset() # try to read lines from fp first diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index bf585b7f..7531c77f 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -47,16 +47,19 @@ def serialize_data( data_table: list or ndarray Data table. dt_colnames: list - Names of each column in data_table. Every name in data_table_cols will be put into the Dictionary - as a key with a value of that column in data_table (stored as a List). Put None for columns - without names. If dt_cols has less non-None entries than columns in data_table, - the pair {'data table': data_table} will be put in the dictionary. - (Default None: only entry {'data table': data_table} will be added to dictionary.) + Names of each column in data_table. Every name in data_table_cols + will be put into the Dictionary as a key with a value of that column + in data_table (stored as a List). Put None for columns without names. + If dt_cols has less non-None entries than columns in data_table, the + pair {'data table': data_table} will be put in the dictionary. + (Default None: only entry {'data table': data_table} will be added to + dictionary.) show_path: bool - include a path element in the database entry (default True). If 'path' is not included in hddata, - extract path from filename. + include a path element in the database entry (default True). If + 'path' is not included in hddata, extract path from filename. serial_file - Serial language file to dump dictionary into. If None (default), no dumping will occur. + Serial language file to dump dictionary into. If None (default), no + dumping will occur. Returns ------- @@ -79,7 +82,8 @@ def serialize_data( data.update(hdata) # second add named columns in dt_cols - # performed second to prioritize overwriting hdata entries with data_table column entries + # performed second to prioritize overwriting hdata entries with data_ + # table column entries named_columns = 0 # initial value max_columns = 1 # higher than named_columns to trigger 'data table' entry if dt_colnames is not None: @@ -98,17 +102,24 @@ def serialize_data( if colname is not None: if colname in hdata.keys(): warnings.warn( - f"Entry '{colname}' in hdata has been overwritten by a data_table entry.", + ( + f"Entry '{colname}' in hdata has been " + "overwritten by a data_table entry." + ), RuntimeWarning, ) data.update({colname: list(data_table[:, idx])}) named_columns += 1 - # finally add data_table as an entry named 'data table' if not all columns were parsed + # finally add data_table as an entry named 'data table' if not all + # columns were parsed if named_columns < max_columns: if "data table" in data.keys(): warnings.warn( - "Entry 'data table' in hdata has been overwritten by data_table.", + ( + "Entry 'data table' in hdata has been " + "overwritten by data_table." + ), RuntimeWarning, ) data.update({"data table": data_table}) diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 115087a2..ca93761c 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -22,11 +22,11 @@ def wsinterp(x, xp, fp, left=None, right=None): """One-dimensional Whittaker-Shannon interpolation. - Reconstruct a continuous signal from discrete data points by utilizing sinc functions - as interpolation kernels. This function interpolates the values of fp (array), - which are defined over xp (array), at new points x (array or float). - The implementation is based on E. T. Whittaker's 1915 paper - (https://doi.org/10.1017/S0370164600017806). + Reconstruct a continuous signal from discrete data points by utilizing + sinc functions as interpolation kernels. This function interpolates the + values of fp (array), which are defined over xp (array), at new points x + (array or float). The implementation is based on E. T. Whittaker's 1915 + paper (https://doi.org/10.1017/S0370164600017806). Parameters ---------- @@ -37,17 +37,18 @@ def wsinterp(x, xp, fp, left=None, right=None): fp: ndarray The array of y values associated with xp. left: float - If given, set fp for x < xp[0] to left. Otherwise, if left is None (default) or not given, - set fp for x < xp[0] to fp evaluated at xp[-1]. + If given, set fp for x < xp[0] to left. Otherwise, if left is None + (default) or not given, set fp for x < xp[0] to fp evaluated at xp[-1]. right: float - If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for - x > xp[-1] to fp evaluated at xp[-1]. + If given, set fp for x > xp[-1] to right. Otherwise, if right is None + (default) or not given, set fp for x > xp[-1] to fp evaluated at + xp[-1]. Returns ------- ndarray or float - The interpolated values at points x. Returns a single float if x is a scalar, - otherwise returns a numpy.ndarray. + The interpolated values at points x. Returns a single float if x is a + scalar, otherwise returns a numpy.ndarray. """ scalar = np.isscalar(x) if scalar: @@ -82,10 +83,11 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon grid. - Takes a band-limited function fp and original grid xp and resamples fp on the NS grid. - Uses the minimum number of points N required by the Nyquist sampling theorem. - N = (qmax-qmin)(rmax-rmin)/pi, where rmin and rmax are the ends of the real-space ranges. - fp must be finite, and the user inputs qmin and qmax of the frequency-domain. + Takes a band-limited function fp and original grid xp and resamples fp on + the NS grid. Uses the minimum number of points N required by the Nyquist + sampling theorem. N = (qmax-qmin)(rmax-rmin)/pi, where rmin and rmax are + the ends of the real-space ranges. fp must be finite, and the user inputs + qmin and qmax of the frequency-domain. Parameters ---------- @@ -103,8 +105,8 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): x: ndarray The Nyquist-Shannon grid computed for the given qmin and qmax. fp_at_x: ndarray - The interpolated values at points x. Returns a single float if x is a scalar, - otherwise returns a numpy.ndarray. + The interpolated values at points x. Returns a single float if x is a + scalar, otherwise returns a numpy.ndarray. """ # Ensure numpy array xp = np.array(xp) @@ -122,8 +124,9 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): def resample(r, s, dr): """Resample a PDF on a new grid. - This uses the Whittaker-Shannon interpolation formula to put s1 on a new grid if dr is less than the sampling - interval of r1, or linear interpolation if dr is greater than the sampling interval of r1. + This uses the Whittaker-Shannon interpolation formula to put s1 on a new + grid if dr is less than the sampling interval of r1, or linear + interpolation if dr is greater than the sampling interval of r1. Parameters ---------- @@ -140,8 +143,12 @@ def resample(r, s, dr): """ warnings.warn( - "The 'resample' function is deprecated and will be removed in a future release (3.8.0). \n" - "'resample' has been renamed 'wsinterp' to better reflect functionality. Please use 'wsinterp' instead.", + ( + "The 'resample' function is deprecated and will be removed " + "in a future release (3.8.0). \n" + "'resample' has been renamed 'wsinterp' to better reflect " + "functionality. Please use 'wsinterp' instead." + ), DeprecationWarning, stacklevel=2, ) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 5924df23..63e10ba2 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -22,7 +22,8 @@ def _stringify(string_value): Returns ------- str - The original string if string_value is not None, otherwise an empty string. + The original string if string_value is not None, otherwise an empty + string. """ return string_value if string_value is not None else "" @@ -53,36 +54,40 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): """Get name, email, and orcid of the owner/user from various sources and return it as a metadata dictionary. - The function looks for the information in json format configuration files with the name 'diffpyconfig.json'. - These can be in the user's home directory and in the current working directory. The information in the - config files are combined, with the local config overriding the home-directory one. Values for - owner_name, owner_email, and owner_orcid may be passed in to the function and these override the values - in the config files. - - A template for the config file is below. Create a text file called 'diffpyconfig.json' in your home directory - and copy-paste the template into it, editing it with your real information. + The function looks for the information in json format configuration files + with the name 'diffpyconfig.json'. These can be in the user's home + directory and in the current working directory. The information in the + config files are combined, with the local config overriding the + home- directory one. Values for owner_name, owner_email, and owner_orcid + may be passed in to the function and these override the values in the + config files. + + A template for the config file is below. Create a text file called ' + diffpyconfig.json' in your home directory and copy-paste the template + into it, editing it with your real information. { "owner_name": ">", "owner_email": ">@email.com", - "owner_orcid": ">" + "owner_orcid": ">" # noqa: E501 } - You may also store any other global-level information that you would like associated with your - diffraction data in this file + You may also store any other global-level information that you would like + associated with your diffraction data in this file Parameters ---------- - owner_name : str, optional, default is the value stored in the global or local config file. - The name of the user who will show as owner in the metadata that is stored with the data - owner_email : str, optional, default is the value stored in the global or local config file. - The email of the user/owner - owner_orcid : str, optional, default is the value stored in the global or local config file. - The ORCID id of the user/owner + owner_name : str, optional, default is the value stored in the global or + local config file. The name of the user who will show as owner in the + metadata that is stored with the data + owner_email : str, optional, default is the value stored in the global or + local config file. The email of the user/owner + owner_orcid : str, optional, default is the value stored in the global or + local config file. The ORCID id of the user/owner Returns ------- user_info : dict - The dictionary containing username, email and orcid of the user/owner, and any other information - stored in the global or local config files. + The dictionary containing username, email and orcid of the user/owner + , and any other information stored in the global or local config files. """ runtime_info = { "owner_name": owner_name, @@ -104,24 +109,27 @@ def check_and_build_global_config(skip_config_creation=False): """Check for a global diffpu config file in user's home directory and creates one if it is missing. - The file it looks for is called diffpyconfig.json. This can contain anything in json format, but - minimally contains information about the computer owner. The information is used - when diffpy objects are created and saved to files or databases to retain ownership information - of datasets. For example, it is used by diffpy.utils.tools.get_user_info(). + The file it looks for is called diffpyconfig.json. This can contain + anything in json format, but minimally contains information about the + computer owner. The information is used when diffpy objects are created + and saved to files or databases to retain ownership information of + datasets. For example, it is used by diffpy.utils.tools.get_user_info(). - If the function finds no config file in the user's home directory it interrupts execution - and prompts the user for name, email, and orcid information. It then creates the config file - with this information inside it. + If the function finds no config file in the user's home directory it + interrupts execution and prompts the user for name, email, and orcid + information. It then creates the config file with this information + inside it. The function returns True if the file exists and False otherwise. - If you would like to check for a file but not run the file creation workflow you can set - the optional argument skip_config_creation to True. + If you would like to check for a file but not run the file creation + workflow you can set the optional argument skip_config_creation to True. Parameters ---------- skip_config_creation : bool, optional, default is False. - The boolean that will override the creation workflow even if no config file exists. + The boolean that will override the creation workflow even if no + config file exists. Returns ------- @@ -136,13 +144,14 @@ def check_and_build_global_config(skip_config_creation=False): if skip_config_creation: return config_exists intro_text = ( - "No global configuration file was found containing information about the user to " - "associate with the data.\n By following the prompts below you can add your name " - "and email to this file on the current " - "computer and your name will be automatically associated with subsequent diffpy data by default.\n" - "This is not recommended on a shared or public computer. " - "You will only have to do that once.\n" - "For more information, please refer to www.diffpy.org/diffpy.utils/examples/toolsexample.html" + "No global configuration file was found containing information about " + "the user to associate with the data.\n By following the prompts " + "below you can add your name and email to this file on the current " + "computer and your name will be automatically associated with " + "subsequent diffpy data by default.\n This is not recommended on a " + "shared or public computer. You will only have to do that once.\n " + "For more information, please refer to www.diffpy.org/diffpy.utils/ " + "examples/toolsexample.html " ) print(intro_text) username = input( @@ -160,14 +169,15 @@ def check_and_build_global_config(skip_config_creation=False): with open(config_path, "w") as f: f.write(json.dumps(config)) outro_text = ( - f"The config file at {Path().home() / 'diffpyconfig.json'} has been created. " - f"The values {config} were entered.\n" - f"These values will be inserted as metadata with your data in apps that use " - f"diffpy.get_user_info(). If you would like to update these values, either " - f"delete the config file and this workflow will rerun next time you run this " - f"program. Or you may open the config file in a text editor and manually edit the" - f"entries. For more information, see: " - f"https://diffpy.github.io/diffpy.utils/examples/tools_example.html" + f"The config file at {Path().home() / 'diffpyconfig.json'} has " + f"been created. The values {config} were entered.\n These values " + "will be inserted as metadata with your data in apps that use " + "diffpy.get_user_info(). If you would like to update these values " + ", either delete the config file and this workflow will rerun " + "next time you run this program. Or you may open the config " + "file in a text editor and manually edit the entries. For more " + "information, see: " + "https://diffpy.github.io/diffpy.utils/examples/tools_example.html" ) print(outro_text) config_exists = True @@ -177,13 +187,15 @@ def check_and_build_global_config(skip_config_creation=False): def get_package_info(package_names, metadata=None): """Fetch package version and updates it into (given) metadata. - Package info stored in metadata as {'package_info': {'package_name': 'version_number'}}. + Package info stored in metadata as + {'package_info': {'package_name': 'version_number'}}. ---------- package_name : str or list The name of the package(s) to retrieve the version number for. metadata : dict - The dictionary to store the package info. If not provided, a new dictionary will be created. + The dictionary to store the package info. If not provided, a new + dictionary will be created. Returns ------- @@ -208,7 +220,8 @@ def get_density_from_cloud(sample_composition, mp_token=""): It is not implemented yet. """ raise NotImplementedError( - "So sorry, density computation from composition is not implemented right now. " + "So sorry, density computation from composition is not implemented " + "right now. " "We hope to have this implemented in the next release. " "Please rerun specifying a sample mass density." ) @@ -221,7 +234,8 @@ def compute_mu_using_xraydb( Computes mu based on the sample composition and energy. User should provide a sample mass density or a packing fraction. - If neither density nor packing fraction is specified, or if both are specified, a ValueError will be raised. + If neither density nor packing fraction is specified, + or if both are specified, a ValueError will be raised. Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu. Parameters @@ -245,7 +259,8 @@ def compute_mu_using_xraydb( sample_mass_density is not None and packing_fraction is not None ): raise ValueError( - "You must specify either sample_mass_density or packing_fraction, but not both. " + "You must specify either sample_mass_density or packing_fraction, " + "but not both. " "Please rerun specifying only one." ) if packing_fraction is not None: @@ -275,8 +290,10 @@ def _model_function(z, diameter, z0, I0, mud, slope): """ Compute the model function with the following steps: 1. Let dz = z-z0, so that dz is centered at 0 - 2. Compute length l that is the effective length for computing intensity I = I0 * e^{-mu * l}: - - For dz within the capillary diameter, l is the chord length of the circle at position dz + 2. Compute length l that is the effective length for computing intensity + I = I0 * e^{-mu * l}: + - For dz within the capillary diameter, l is the chord length of + the circle at position dz - For dz outside this range, l = 0 3. Apply a linear adjustment to I0 by taking I0 as I0 - slope * z """ @@ -309,7 +326,7 @@ def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): z_extended = np.concatenate([z_left_pad, z, z_right_pad]) I_extended = _model_function(z_extended, diameter, z0, I0, mud, slope) kernel = _top_hat(z_extended - z_extended.mean(), half_slit_width) - I_convolved = I_extended # this takes care of the case where slit width is close to 0 + I_convolved = I_extended # this takes care of the case where slit width is close to 0 # noqa: E501 if kernel.sum() != 0: kernel /= kernel.sum() I_convolved = convolve(I_extended, kernel, mode="same") @@ -364,11 +381,13 @@ def compute_mud(filepath): This function loads z-scan data and fits it to a model that convolves a top-hat function with I = I0 * e^{-mu * l}. - The fitting procedure is run multiple times, and we return the best-fit parameters based on the lowest rmse. + The fitting procedure is run multiple times, and we return the best-fit + parameters based on the lowest rmse. The full mathematical details are described in the paper: - An ad hoc Absorption Correction for Reliable Pair-Distribution Functions from Low Energy x-ray Sources, - Yucong Chen, Till Schertenleib, Andrew Yang, Pascal Schouwink, Wendy L. Queen and Simon J. L. Billinge, + An ad hoc Absorption Correction for Reliable Pair-Distribution Functions + from Low Energy x-ray Sources, Yucong Chen, Till Schertenleib, Andrew Yang + , Pascal Schouwink, Wendy L. Queen and Simon J. L. Billinge, in preparation. Parameters diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 693bb693..56eebc68 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -4,19 +4,24 @@ import numpy as np wavelength_warning_emsg = ( - "No wavelength has been specified. You can continue to use the DiffractionObject, but " - "some of its powerful features will not be available. " - "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " - "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." + "No wavelength has been specified. You can continue to use the " + "DiffractionObject, but some of its powerful features will not be " + "available. To specify a wavelength, if you have " + "do = DiffractionObject(xarray, yarray, 'tth'), you may set " + "do.wavelength = 1.54 for a wavelength of 1.54 angstroms. " ) invalid_tth_emsg = ( "Two theta exceeds 180 degrees. Please check the input values for errors." ) invalid_q_or_d_or_wavelength_emsg = ( - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values." + "The supplied input array and wavelength will result in an impossible " + "two-theta. Please check these values and re-instantiate the " + "DiffractionObject with correct values. " +) +inf_output_imsg = ( + "INFO: The largest output value in the array is infinite. " + "This is allowed, but it will not be plotted." ) -inf_output_imsg = "INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted." def _validate_inputs(q, wavelength): @@ -73,7 +78,8 @@ def q_to_tth(q, wavelength): def tth_to_q(tth, wavelength): r"""Helper function to convert two-theta to q on independent variable axis. - If wavelength is missing, returns independent variable axis as integer indexes. + If wavelength is missing, returns independent variable axis as integer + indexes. By definition the relationship is: @@ -100,7 +106,8 @@ def tth_to_q(tth, wavelength): ------- q : ndarray The 1D array of :math:`q` values np.array([qs]). - The units for the q-values are the inverse of the units of the provided wavelength. + The units for the q-values are the inverse of the units of the + provided wavelength. """ tth.astype(float) if np.any(np.deg2rad(tth) > np.pi): @@ -139,7 +146,8 @@ def q_to_d(q): def tth_to_d(tth, wavelength): r"""Helper function to convert two-theta to d on independent variable axis. - The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. + The formula is .. + math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. Here we convert tth to q first, then to d. @@ -191,7 +199,8 @@ def d_to_q(d): def d_to_tth(d, wavelength): r"""Helper function to convert d to two-theta on independent variable axis. - The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). + The formula is .. + math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). Here we convert d to q first, then to tth. diff --git a/src/diffpy/utils/validators.py b/src/diffpy/utils/validators.py index 91a461bf..c173f2bd 100644 --- a/src/diffpy/utils/validators.py +++ b/src/diffpy/utils/validators.py @@ -1,8 +1,9 @@ def is_number(string): """Check if the provided string can be converted to a float. - Since integers can be converted to floats, this function will return True for integers as well. - Hence, we can use this function to check if a string is a number. + Since integers can be converted to floats, this function will return True + for integers as well. Hence, we can use this function to check if a + string is a number. Parameters ---------- diff --git a/tests/conftest.py b/tests/conftest.py index b6d3bf3c..fcccb186 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,7 +37,8 @@ def _load(filename): @pytest.fixture def do_minimal(): - # Create an instance of DiffractionObject with empty xarray and yarray values, and a non-empty wavelength + # 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 ) @@ -45,7 +46,8 @@ def do_minimal(): @pytest.fixture def do_minimal_tth(): - # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values + # Create an instance of DiffractionObject with non-empty xarray, yarray, + # and wavelength values return DiffractionObject( wavelength=2 * np.pi, xarray=np.array([30, 60]), @@ -56,7 +58,8 @@ def do_minimal_tth(): @pytest.fixture def do_minimal_d(): - # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values + # Create an instance of DiffractionObject with non-empty xarray, yarray, + # and wavelength values return DiffractionObject( wavelength=1.54, xarray=np.array([1, 2]), @@ -68,9 +71,11 @@ def do_minimal_d(): @pytest.fixture def wavelength_warning_msg(): return ( - "No wavelength has been specified. You can continue to use the DiffractionObject, but " - "some of its powerful features will not be available. " - "To specify a wavelength, if you have do = DiffractionObject(xarray, yarray, 'tth'), " + "No wavelength has been specified. You can continue to use the " + "DiffractionObject, but some of its powerful features will not be " + "available. " + "To specify a wavelength, if you have " + "do = DiffractionObject(xarray, yarray, 'tth'), " "you may set do.wavelength = 1.54 for a wavelength of 1.54 angstroms." ) @@ -78,16 +83,20 @@ def wavelength_warning_msg(): @pytest.fixture def invalid_q_or_d_or_wavelength_error_msg(): return ( - "The supplied input array and wavelength will result in an impossible two-theta. " - "Please check these values and re-instantiate the DiffractionObject with correct values." + "The supplied input array and wavelength will result in an " + "impossible two-theta. " + "Please check these values and re-instantiate the DiffractionObject " + "with correct values." ) @pytest.fixture def invalid_add_type_error_msg(): return ( - "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " - "Please rerun by adding another DiffractionObject instance or a scalar value. " + "You may only add a DiffractionObject with another DiffractionObject " + "or a scalar value. " + "Please rerun by adding another DiffractionObject instance or a " + "scalar value. " "e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" ) @@ -95,7 +104,9 @@ def invalid_add_type_error_msg(): @pytest.fixture def x_values_not_equal_error_msg(): return ( - "The two objects have different values in x arrays (my_do.all_arrays[:, [1, 2, 3]]). " - "Please ensure the x values of the two objects are identical by re-instantiating " - "the DiffractionObject with the correct x value inputs." + "The two objects have different values in x arrays " + "(my_do.all_arrays[:, [1, 2, 3]]). " + "Please ensure the x values of the two objects are identical " + "by re-instantiating the DiffractionObject with the correct x value " + "inputs." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 7db4cc33..8ed1dd3b 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -202,7 +202,8 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test whether the original y-array is scaled as expected - ( # C1: none of q, tth, d, provided, expect to scale on the maximal intensity from each object + ( # C1: none of q, tth, d, provided, expect to scale on the maximal + # intensity from each object { "xarray": np.array([0.1, 0.2, 0.3]), "yarray": np.array([1, 2, 3]), @@ -275,8 +276,8 @@ def test_init_invalid_xtype(): {"tth": 60}, {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), - ( # C5.1: Reuse test case from C1, none of q, tth, d, provided, but include an offset, - # expect scaled y-array in C1 to shift up by 2 + ( # C5.1: Reuse test case from C1, none of q, tth, d, provided, but + # include an offset, expect scaled y-array in C1 to shift up by 2 { "xarray": np.array([0.1, 0.2, 0.3]), "yarray": np.array([1, 2, 3]), @@ -292,7 +293,8 @@ def test_init_invalid_xtype(): {"offset": 2}, {"xtype": "q", "yarray": np.array([12, 22, 32])}, ), - ( # C5.2: Reuse test case from C4, but include an offset, expect scaled y-array in C4 to shift up by 2 + ( # C5.2: Reuse test case from C4, but include an offset, expect + # scaled y-array in C4 to shift up by 2 { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), @@ -358,8 +360,10 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): @pytest.mark.parametrize( "do_args, get_array_index_inputs, expected_index", [ - # Test get_array_index() returns the expected index given xtype and value - ( # C1: Target value is in the xarray and xtype is identical, expect exact index match + # Test get_array_index() returns the expected index given xtype and + # value + ( # C1: Target value is in the xarray and xtype is identical, expect + # exact index match { "wavelength": 4 * np.pi, "xarray": np.array([30.005, 60]), @@ -372,7 +376,8 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, 0, ), - ( # C2: Target value lies in the array, expect the (first) closest index + ( # C2: Target value lies in the array, expect the (first) closest + # index { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -477,8 +482,13 @@ def test_dump(tmp_path, mocker): with open(file, "r") as f: actual = f.read() expected = ( - "[DiffractionObject]\nname = test\nwavelength = 1.54\nscat_quantity = x-ray\nthing1 = 1\n" - "thing2 = thing2\npackage_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n" + "[DiffractionObject]\n" + "name = test\n" + "wavelength = 1.54\n" + "scat_quantity = x-ray\n" + "thing1 = 1\n" + "thing2 = thing2\n" + "package_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n" "creation_time = 2012-01-14 00:00:00\n\n" "#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n" "1.000000000000000000e+00 1.000000000000000000e+00\n" @@ -492,10 +502,15 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( - "do_init_args, expected_do_dict, divide_by_zero_warning_expected, wavelength_warning_expected", + ( + "do_init_args, expected_do_dict, " + "divide_by_zero_warning_expected, wavelength_warning_expected" + ), [ - # Test __dict__ of DiffractionObject instance initialized with valid arguments - ( # C1: Instantiate DO with empty arrays, expect it to be a valid DO, but with everything empty + # Test __dict__ of DiffractionObject instance initialized with valid + # arguments + ( # C1: Instantiate DO with empty arrays, expect it to be a valid DO, + # but with everything empty { "xarray": np.empty(0), "yarray": np.empty(0), @@ -518,8 +533,9 @@ def test_dump(tmp_path, mocker): False, True, ), - ( # C2: Instantiate just DO with empty array like in C1 but with wavelength, xtype, name, and metadata - # expect a valid DO with empty arrays, but with some non-array attributes + ( # C2: Instantiate just DO with empty array like in C1 but with + # wavelength, xtype, name, and metadata expect a valid DO with + # empty arrays, but with some non-array attributes { "xarray": np.empty(0), "yarray": np.empty(0), @@ -545,8 +561,9 @@ def test_dump(tmp_path, mocker): False, False, ), - ( # C3: Minimum arguments provided for init with non-empty values for xarray and yarray and wavelength - # expect all attributes set without None + ( # C3: Minimum arguments provided for init with non-empty values + # for xarray and yarray and wavelength expect all attributes set + # without None { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -576,7 +593,8 @@ def test_dump(tmp_path, mocker): True, False, ), - ( # C4: Same as C3, but with an optional scat_quantity argument, expect non-empty string for scat_quantity + ( # C4: Same as C3, but with an optional scat_quantity argument, + # expect non-empty string for scat_quantity { "xarray": np.array( [np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi] @@ -643,12 +661,18 @@ def test_init_valid( @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - # Test expected error messages when 3 required arguments not provided in DiffractionObject init - ( # C1: No arguments provided, expect 3 required positional arguments error + # Test expected error messages when 3 required arguments not provided + # in DiffractionObject init + ( # C1: No arguments provided, expect 3 required positional + # arguments error {}, - "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ( + "missing 3 required positional arguments: " + "'xarray', 'yarray', and 'xtype'" + ), ), - ( # C2: Only xarray and yarray provided, expect 1 required positional argument error + ( # C2: Only xarray and yarray provided, expect 1 required + # positional argument error {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, "missing 1 required positional argument: 'xtype'", ), @@ -704,7 +728,10 @@ def test_uuid_setter_error(do_minimal): with pytest.raises( AttributeError, - match="Direct modification of attribute 'uuid' is not allowed. Please use 'input_data' to modify 'uuid'.", + match=( + "Direct modification of attribute 'uuid' is not allowed. " + "Please use 'input_data' to modify 'uuid'." + ), ): do.uuid = uuid.uuid4() @@ -749,8 +776,10 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( "operation, starting_yarray, scalar_value, expected_yarray", [ - # Test scalar addition, subtraction, multiplication, and division to y-values by adding a scalar value - # C1: Test scalar addition to y-values (intensity), expect no change to x-values (q, tth, d) + # Test scalar addition, subtraction, multiplication, and division to + # y-values by adding a scalar value + # C1: Test scalar addition to y-values (intensity), expect no change + # to x-values (q, tth, d) ( # 1. Add 5 "add", np.array([1.0, 2.0]), @@ -763,7 +792,8 @@ def test_copy_object(do_minimal): 5.1, np.array([6.1, 7.1]), ), - # C2: Test scalar subtraction to y-values (intensity), expect no change to x-values (q, tth, d) + # C2: Test scalar subtraction to y-values (intensity), expect no + # change to x-values (q, tth, d) ( # 1. Subtract 1 "sub", np.array([1.0, 2.0]), @@ -776,7 +806,8 @@ def test_copy_object(do_minimal): 0.5, np.array([0.5, 1.5]), ), - # C3: Test scalar multiplication to y-values (intensity), expect no change to x-values (q, tth, d) + # C3: Test scalar multiplication to y-values (intensity), expect no + # change to x-values (q, tth, d) ( # 1. Multiply by 2 "mul", np.array([1.0, 2.0]), @@ -789,7 +820,8 @@ def test_copy_object(do_minimal): 2.5, np.array([2.5, 5.0]), ), - # C4: Test scalar division to y-values (intensity), expect no change to x-values (q, tth, d) + # C4: Test scalar division to y-values (intensity), expect no change + # to x-values (q, tth, d) ( # 1. Divide by 2 "div", np.array([1.0, 2.0]), @@ -837,9 +869,13 @@ def test_scalar_operations( @pytest.mark.parametrize( - "operation, expected_do_1_all_arrays_with_y_modified, expected_do_2_all_arrays_with_y_modified", + ( + "operation, expected_do_1_all_arrays_with_y_modified, " + "expected_do_2_all_arrays_with_y_modified" + ), [ - # Test addition, subtraction, multiplication, and division of two DO objects + # Test addition, subtraction, multiplication, and division of two DO + # objects ( # Test addition of two DO objects, expect combined yarray values "add", np.array( @@ -855,7 +891,8 @@ def test_scalar_operations( ] ), ), - ( # Test subtraction of two DO objects, expect differences in yarray values + ( # Test subtraction of two DO objects, expect differences in yarray + # values "sub", np.array( [ @@ -870,7 +907,8 @@ def test_scalar_operations( ] ), ), - ( # Test multiplication of two DO objects, expect multiplication in yarray values + ( # Test multiplication of two DO objects, expect multiplication in + # yarray values "mul", np.array( [ @@ -975,7 +1013,8 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): def test_operator_invalid_xarray_values_not_equal( operation, do_minimal_tth, do_minimal_d, x_values_not_equal_error_msg ): - # Add two DO objects with different xarray values but equal in shape, expect ValueError + # Add two DO objects with different xarray values but equal in shape, + # expect ValueError do_1 = do_minimal_tth do_2 = do_minimal_d with pytest.raises( diff --git a/tests/test_loaddata.py b/tests/test_loaddata.py index 7580c672..82d947ee 100644 --- a/tests/test_loaddata.py +++ b/tests/test_loaddata.py @@ -15,9 +15,9 @@ def test_loadData_default(datafile): with pytest.raises(IOError) as err: loadData("doesnotexist.txt") - assert ( - str(err.value) - == "File doesnotexist.txt cannot be found. Please rerun the program specifying a valid filename." + assert str(err.value) == ( + "File doesnotexist.txt cannot be found. " + "Please rerun the program specifying a valid filename." ) # The default minrows=10 makes it read from the third line diff --git a/tests/test_resample.py b/tests/test_resample.py index 6e6294ed..14f6190e 100644 --- a/tests/test_resample.py +++ b/tests/test_resample.py @@ -7,7 +7,8 @@ def test_wsinterp(): - # FIXME: if another SW interp function exists, run comparisons for interpolated points + # FIXME: if another SW interp function exists, run comparisons for + # interpolated points # Sampling rate ssr = 44100**-1 # Standard sampling rate for human-hearable frequencies diff --git a/tests/test_tools.py b/tests/test_tools.py index 8d69c6c0..6be3870f 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -18,7 +18,8 @@ @pytest.mark.parametrize( "runtime_inputs, expected", - [ # config file in home is present, no config in cwd. various runtime values passed + [ # config file in home is present, no config in cwd. various runtime + # values passed # C1: nothing passed in, expect uname, email, orcid from home_config ( {}, @@ -28,7 +29,8 @@ "owner_orcid": "home_orcid", }, ), - # C2: empty strings passed in, expect uname, email, orcid from home_config + # C2: empty strings passed in, expect uname, email, orcid from + # home_config ( {"owner_name": "", "owner_email": "", "owner_orcid": ""}, { @@ -37,7 +39,8 @@ "owner_orcid": "home_orcid", }, ), - # C3: just owner name passed in at runtime. expect runtime_oname but others from config + # C3: just owner name passed in at runtime. expect runtime_oname but + # others from config ( {"owner_name": "runtime_ownername"}, { @@ -46,7 +49,8 @@ "owner_orcid": "home_orcid", }, ), - # C4: just owner email passed in at runtime. expect runtime_email but others from config + # C4: just owner email passed in at runtime. expect runtime_email + # but others from config ( {"owner_email": "runtime@email.com"}, { @@ -55,7 +59,8 @@ "owner_orcid": "home_orcid", }, ), - # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config + # C5: just owner ci passed in at runtime. expect runtime_orcid but + # others from config ( {"owner_orcid": "runtime_orcid"}, { @@ -69,8 +74,8 @@ def test_get_user_info_with_home_conf_file( runtime_inputs, expected, user_filesystem, mocker ): - # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] - # is tmp_dir/cwd_dir + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, + # user_filesystem[1] is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) actual = get_user_info(**runtime_inputs) @@ -81,7 +86,8 @@ def test_get_user_info_with_home_conf_file( "runtime_inputs, expected", [ # tests as before but now config file present in cwd and home but orcid # missing in the cwd config - # C1: nothing passed in, expect uname, email from local config, orcid from home_config + # C1: nothing passed in, expect uname, email from local config, orcid + # from home_config ( {}, { @@ -90,7 +96,8 @@ def test_get_user_info_with_home_conf_file( "owner_orcid": "home_orcid", }, ), - # C2: empty strings passed in, expect uname, email, orcid from home_config + # C2: empty strings passed in, expect uname, email, orcid from + # home_config ( {"owner_name": "", "owner_email": "", "owner_orcid": ""}, { @@ -99,7 +106,8 @@ def test_get_user_info_with_home_conf_file( "owner_orcid": "home_orcid", }, ), - # C3: just owner name passed in at runtime. expect runtime_oname but others from config + # C3: just owner name passed in at runtime. expect runtime_oname but + # others from config ( {"owner_name": "runtime_ownername"}, { @@ -108,7 +116,8 @@ def test_get_user_info_with_home_conf_file( "owner_orcid": "home_orcid", }, ), - # C4: just owner email passed in at runtime. expect runtime_email but others from config + # C4: just owner email passed in at runtime. expect runtime_email + # but others from config ( {"owner_email": "runtime@email.com"}, { @@ -117,7 +126,8 @@ def test_get_user_info_with_home_conf_file( "owner_orcid": "home_orcid", }, ), - # C5: just owner ci passed in at runtime. expect runtime_orcid but others from config + # C5: just owner ci passed in at runtime. expect runtime_orcid but + # others from config ( {"owner_orcid": "runtime_orcid"}, { @@ -131,8 +141,8 @@ def test_get_user_info_with_home_conf_file( def test_get_user_info_with_local_conf_file( runtime_inputs, expected, user_filesystem, mocker ): - # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] - # is tmp_dir/cwd_dir + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, + # user_filesystem[1] is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) local_config_data = { @@ -147,7 +157,8 @@ def test_get_user_info_with_local_conf_file( @pytest.mark.parametrize( "test_inputs,expected", - [ # Check check_and_build_global_config() builds correct config when config is found missing + [ # Check check_and_build_global_config() builds correct config when + # config is found missing ( # C1: user inputs valid name, email and orcid {"user_inputs": ["input_name", "input@email.com", "input_orcid"]}, { @@ -160,7 +171,8 @@ def test_get_user_info_with_local_conf_file( {"user_inputs": ["", "", ""]}, None, ), # C2: empty strings passed in, expect no config file created - ( # C3: just username input, expect config file but with some empty values + ( # C3: just username input, expect config file but with some empty + # values {"user_inputs": ["input_name", "", ""]}, {"owner_email": "", "owner_orcid": "", "owner_name": "input_name"}, ), @@ -169,8 +181,8 @@ def test_get_user_info_with_local_conf_file( def test_check_and_build_global_config( test_inputs, expected, user_filesystem, mocker ): - # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, user_filesystem[1] - # is tmp_dir/cwd_dir + # user_filesystem[0] is tmp_dir/home_dir with the global config file in it, + # user_filesystem[1] is tmp_dir/cwd_dir mocker.patch.object(Path, "home", return_value=user_filesystem[0]) os.chdir(user_filesystem[1]) confile = user_filesystem[0] / "diffpyconfig.json" @@ -262,7 +274,8 @@ def test_get_package_info(monkeypatch, inputs, expected): "inputs", [ # Test when the function has invalid inputs - ( # C1: Both mass density and packing fraction are provided, expect ValueError exception + ( # C1: Both mass density and packing fraction are provided, + # expect ValueError exception { "sample_composition": "SiO2", "energy": 10, @@ -270,7 +283,8 @@ def test_get_package_info(monkeypatch, inputs, expected): "packing_fraction": 1, } ), - ( # C2: None of mass density or packing fraction are provided, expect ValueError exception + ( # C2: None of mass density or packing fraction are provided, + # expect ValueError exception { "sample_composition": "SiO2", "energy": 10, @@ -281,8 +295,11 @@ def test_get_package_info(monkeypatch, inputs, expected): def test_compute_mu_using_xraydb_bad(inputs): with pytest.raises( ValueError, - match="You must specify either sample_mass_density or packing_fraction, but not both. " - "Please rerun specifying only one.", + match=( + "You must specify either sample_mass_density or " + "packing_fraction, but not both. " + "Please rerun specifying only one." + ), ): compute_mu_using_xraydb(**inputs) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 4617584c..7f0775c7 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -20,15 +20,18 @@ # C1: Allow empty array q to compute tth with or without wavelength # 1. Wavelength provided, expect empty array of tth (4 * np.pi, np.empty((0)), np.empty(0)), - # 2. No wavelength provided, expected empty array of tth and wavelength UserWarning + # 2. No wavelength provided, expected empty array of tth and + # wavelength UserWarning (None, np.empty((0)), np.empty((0))), # C2: Use non-empty q values to compute tth with or without wavelength - ( # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning + ( # 1. No wavelength provided, expect valid tth values in degrees + # with wavelength UserWarning None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - ( # 2. Wavelength provided, expect tth values of 2*arcsin(q) in degrees + ( # 2. Wavelength provided, expect tth values of 2*arcsin(q) in + # degrees 4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0]), @@ -50,12 +53,14 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): "wavelength, q, expected_error_type", [ # Test ValeuError in q to tth conversion with invalid two-theta values. - ( # C1: Invalid q values that result in tth > 180 degrees, expect ValueError + ( # C1: Invalid q values that result in tth > 180 degrees, + # expect ValueError 4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), ValueError, ), - ( # C2: Wrong wavelength that results in tth > 180 degrees, expect ValueError + ( # C2: Wrong wavelength that results in tth > 180 degrees, + # expect ValueError 100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), ValueError, @@ -77,15 +82,18 @@ def test_q_to_tth_bad( # C1: Allow empty tth values to compute 1, with or without wavelength # 1. Wavelength provided, expect empty array of q (None, np.array([]), np.array([])), - # 2. No wavelength provided, expected empty array of q and wavelength UserWarning + # 2. No wavelength provided, expected empty array of q and wavelength + # UserWarning (4 * np.pi, np.array([]), np.array([])), - # C2: Use non-empty tth values between 0-180 degrees to compute q, with or without wavelength + # C2: Use non-empty tth values between 0-180 degrees to compute q, + # with or without wavelength ( # 1. No wavelength provided, expect valid q values between 0-1 None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), - ( # 2. Wavelength provided, expect expected q values are sin15, sin30, sin45, sin60, sin90 + ( # 2. Wavelength provided, expect expected q values are + # sin15, sin30, sin45, sin60, sin90 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), @@ -107,18 +115,25 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # C1: Invalid tth value of > 180 degrees provided, with or without wavelength + # C1: Invalid tth value of > 180 degrees provided, + # with or without wavelength ( # 1. No wavelength provided, expect two theta ValueError None, np.array([0, 30, 60, 90, 120, 181]), ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", + ( + "Two theta exceeds 180 degrees. " + "Please check the input values for errors." + ), ), ( # 2. Wavelength provided, expect two theta ValueError 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", + ( + "Two theta exceeds 180 degrees. " + "Please check the input values for errors." + ), ), ], ) @@ -143,7 +158,8 @@ def test_tth_to_q_bad( np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), False, ), - ( # 2. Valid q values containing 0, expect d values with divide by zero warning + ( # 2. Valid q values containing 0, + # expect d values with divide by zero warning np.array( [0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi] ), @@ -195,14 +211,16 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): (None, np.array([]), np.array([]), False), # C2: Empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), - # C3: User specified valid tth values between 0-180 degrees (without wavelength) + # C3: User specified valid tth values between 0-180 degrees + # (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False, ), - ( # C4: User specified valid tth values between 0-180 degrees (with wavelength) + ( # C4: User specified valid tth values between 0-180 degrees + # (with wavelength) 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), @@ -235,17 +253,25 @@ def test_tth_to_d( @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - ( # C1: Invalid tth value of > 180 degrees, no wavelength, expect two theta ValueError + ( # C1: Invalid tth value of > 180 degrees, no wavelength, + # expect two theta ValueError None, np.array([0, 30, 60, 90, 120, 181]), ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", + ( + "Two theta exceeds 180 degrees. " + "Please check the input values for errors." + ), ), - ( # C2: Invalid tth value of > 180 degrees with wavelength, expect two theta ValueError + ( # C2: Invalid tth value of > 180 degrees with wavelength, + # expect two theta ValueError 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), ValueError, - "Two theta exceeds 180 degrees. Please check the input values for errors.", + ( + "Two theta exceeds 180 degrees. " + "Please check the input values for errors." + ), ), ], ) @@ -263,14 +289,16 @@ def test_tth_to_d_invalid( (None, np.empty((0)), np.empty((0)), False), # C2: Empty d values with wavelength, expect empty tth values (4 * np.pi, np.empty((0)), np.empty(0), False), - # C3: Valid d values, no wavelength, expect valid and non-empty tth values + # C3: Valid d values, no wavelength, + # expect valid and non-empty tth values ( None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True, ), - ( # C4: Valid d values with wavelength, expect valid and non-empty thh values + ( # C4: Valid d values with wavelength, + # expect valid and non-empty thh values 4 * np.pi, np.array( [4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi] @@ -308,9 +336,11 @@ def test_d_to_tth( @pytest.mark.parametrize( "wavelength, d, expected_error_type", [ - # C1: Invalid d values that result in tth > 180 degrees, expect invalid q, d, or wavelength ValueError + # C1: Invalid d values that result in tth > 180 degrees, + # expect invalid q, d, or wavelength ValueError (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), - # C2: Wrong wavelength that result in tth > 180 degreesm, expect invalid q, d, or wavelength ValueError + # C2: Wrong wavelength that result in tth > 180 degreesm, + # expect invalid q, d, or wavelength ValueError (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) From 03effc390a65fc88f27d58d936c4a5b346aca99a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 10 Jun 2025 17:13:00 -0400 Subject: [PATCH 391/445] skpkg: update .github dir with skpkg --- .github/ISSUE_TEMPLATE/release_checklist.md | 14 +++++++------- .../pull_request_template.md | 15 +++++++++++++++ .github/workflows/build-wheel-release-upload.yml | 6 +++--- .github/workflows/check-news-item.yml | 2 +- .../matrix-and-codecov-on-merge-to-main.yml | 2 +- .github/workflows/publish-docs-on-release.yml | 6 +----- .github/workflows/tests-on-pr.yml | 5 +---- 7 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index fa94779e..6107962c 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -13,30 +13,30 @@ assignees: "" - [ ] License information is verified as correct. If you are unsure, please comment below. - [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are missing), tutorials, and other human-written text is up-to-date with any changes in the code. -- [ ] Installation instructions in the README, documentation, and the website (e.g., diffpy.org) are updated. +- [ ] Installation instructions in the README, documentation, and the website are updated. - [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. - [ ] Grammar and writing quality are checked (no typos). - [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. -Please mention @sbillinge here when you are ready for PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: +Please tag the maintainer (e.g., @username) in the comment here when you are ready for the PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: ### PyPI/GitHub full-release preparation checklist: - [ ] Create a new conda environment and install the rc from PyPI (`pip install ==??`) - [ ] License information on PyPI is correct. -- [ ] Docs are deployed successfully to `https://www.diffpy.org/`. +- [ ] Docs are deployed successfully to `https:///`. - [ ] Successfully run all tests, tutorial examples or do functional testing. -Please let @sbillinge know that all checks are done and the package is ready for full release. +Please let the maintainer know that all checks are done and the package is ready for full release. ### conda-forge release preparation checklist: - + - [ ] Ensure that the full release has appeared on PyPI successfully. - [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. -- [ ] Close any open issues on the feedstock. Reach out to @bobleesj if you have questions. -- [ ] Tag @sbillinge and @bobleesj for conda-forge release. +- [ ] Close any open issues on the feedstock. Reach out to the maintainer if you have questions. +- [ ] Tag the maintainer for conda-forge release. ### Post-release checklist diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 00000000..1099d862 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,15 @@ +### What problem does this PR address? + + + +### What should the reviewer(s) do? + + + + diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index db92d9d7..485aa356 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -8,11 +8,11 @@ on: jobs: release: - uses: Billingegroup/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 with: project: diffpy.utils - github_admin_username: sbillinge - + c_extension: false + maintainer_GITHUB_username: sbillinge secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} PAT_TOKEN: ${{ secrets.PAT_TOKEN }} diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml index aa040f09..9e0f9f16 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -7,6 +7,6 @@ on: jobs: check-news-item: - uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.utils diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index 8543c786..a6a0d7cf 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -12,7 +12,7 @@ on: jobs: matrix-coverage: - uses: Billingegroup/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.utils c_extension: false diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml index e222ac53..a2dd338c 100644 --- a/.github/workflows/publish-docs-on-release.yml +++ b/.github/workflows/publish-docs-on-release.yml @@ -1,15 +1,11 @@ name: Deploy Documentation on Release on: - release: - types: [published, prereleased] workflow_dispatch: jobs: docs: - permissions: - contents: write - uses: Billingegroup/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 with: project: diffpy.utils c_extension: false diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index baac1aeb..5e4b8fec 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -1,15 +1,12 @@ name: Tests on PR on: - push: - branches: - - main pull_request: workflow_dispatch: jobs: tests-on-pr: - uses: Billingegroup/release-scripts/.github/workflows/_tests-on-pr.yml@v0 + uses: scikit-package/release-scripts/.github/workflows/_tests-on-pr.yml@v0 with: project: diffpy.utils c_extension: false From 6cb227380e87859b7f5df7059600c08a5bcf790a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 10 Jun 2025 17:13:37 -0400 Subject: [PATCH 392/445] skpkg: gitignore file from skpkg --- .gitignore | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index a25212ea..099e2948 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __pycache__/ .Python env/ build/ +_build/ develop-eggs/ dist/ downloads/ @@ -90,10 +91,3 @@ target/ # Ipython Notebook .ipynb_checkpoints - -# version information -setup.cfg -/src/diffpy/*/version.cfg - -# Rever -rever/ From f70eaa0ada15b4fa610571177b19be627fcf87e2 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 10 Jun 2025 17:16:56 -0400 Subject: [PATCH 393/445] skpkg: small modifications to files in src --- src/diffpy/utils/__init__.py | 6 ++++-- src/diffpy/utils/version.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/__init__.py b/src/diffpy/utils/__init__.py index 12f4a49d..9b9f7a69 100644 --- a/src/diffpy/utils/__init__.py +++ b/src/diffpy/utils/__init__.py @@ -1,10 +1,10 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2024 The Trustees of Columbia University in the City of New York. +# (c) 2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Billinge Group members and community contributors. +# File coded by: Simon Billinge, Billinge Group members. # # See GitHub contributions for a more detailed list of contributors. # https://github.com/diffpy/diffpy.utils/graphs/contributors @@ -19,3 +19,5 @@ # silence the pyflakes syntax checker assert __version__ or True + +# End of file \ No newline at end of file diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index e74c47bd..07d612a3 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -1,10 +1,10 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2024 The Trustees of Columbia University in the City of New York. +# (c) 2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Billinge Group members and community contributors. +# File coded by: Simon Billinge, Billinge Group members. # # See GitHub contributions for a more detailed list of contributors. # https://github.com/diffpy/diffpy.utils/graphs/contributors @@ -21,3 +21,5 @@ from importlib.metadata import version __version__ = version("diffpy.utils") + +# End of file \ No newline at end of file From 629141bdd6c9b5e9400d77861ed3a5376fbd9eb2 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 10 Jun 2025 17:20:43 -0400 Subject: [PATCH 394/445] precommit: end of file fixer --- src/diffpy/utils/__init__.py | 2 +- src/diffpy/utils/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/__init__.py b/src/diffpy/utils/__init__.py index 9b9f7a69..c8aad4a1 100644 --- a/src/diffpy/utils/__init__.py +++ b/src/diffpy/utils/__init__.py @@ -20,4 +20,4 @@ # silence the pyflakes syntax checker assert __version__ or True -# End of file \ No newline at end of file +# End of file diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index 07d612a3..a17ccb5c 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -22,4 +22,4 @@ __version__ = version("diffpy.utils") -# End of file \ No newline at end of file +# End of file From 21c9220b90a1beb194b34c1b7d91ab646795238b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 10 Jun 2025 17:24:04 -0400 Subject: [PATCH 395/445] chore: news and pre-commit file --- .pre-commit-config.yaml | 5 ++--- news/setup-CI.rst | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 news/setup-CI.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6dca6f1e..0e4a84d1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,21 +44,20 @@ repos: name: Prevent Commit to Main Branch args: ["--branch", "main"] stages: [pre-commit] - # codespell - spell checker for source code - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - id: codespell additional_dependencies: - tomli - # prettier - multi formatter for json, yaml, md + # prettier - multi formatter for .json, .yml, and .md files - repo: https://github.com/pre-commit/mirrors-prettier rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 hooks: - id: prettier additional_dependencies: - "prettier@^3.2.4" - # docformatter - formats docstrings using PEP 257 + # docformatter - PEP 257 compliant docstring formatter - repo: https://github.com/s-weigand/docformatter rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c hooks: diff --git a/news/setup-CI.rst b/news/setup-CI.rst new file mode 100644 index 00000000..bf692d68 --- /dev/null +++ b/news/setup-CI.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Update github CI to scikit-package standard + +**Security:** + +* From ae8caf460d5bd8849a02df213900173b360a759b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 11:06:56 -0400 Subject: [PATCH 396/445] skpkg: migration of docs --- doc/source/conf.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 045aba0a..ae2443a1 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -18,6 +18,12 @@ from importlib.metadata import version from pathlib import Path +# Attempt to import the version dynamically from GitHub tag. +try: + fullversion = version("diffpy.utils") +except Exception: + fullversion = "No version found. The correct version will appear in the released version." # noqa: E501 + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use Path().resolve() to make it absolute, like shown here. @@ -43,6 +49,7 @@ "sphinx.ext.viewcode", "sphinx.ext.intersphinx", "sphinx_rtd_theme", + "sphinx_copybutton", "m2r", ] @@ -88,6 +95,11 @@ # substitute YEAR in the copyright string copyright = copyright.replace("%Y", year) +# For sphinx_copybutton extension. +# Do not copy "$" for shell commands in code-blocks. +copybutton_prompt_text = r"^\$ " +copybutton_prompt_is_regexp = True + # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["build"] @@ -123,6 +135,14 @@ # html_theme = "sphinx_rtd_theme" +html_context = { + "display_github": True, + "github_user": "diffpy", + "github_repo": "diffpy.utils", + "github_version": "main", + "conf_py_path": "/doc/source/", +} + # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. @@ -256,7 +276,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ("index", "diffpy.utils", "diffpy.utils Documentation", ab_authors, 1) + ( + "index", + "diffpy.utils", + "diffpy.utils Documentation", + ab_authors, + 1, + ) ] # If true, show URL addresses after external links. From 199cce8d6615be9d2c810bd1a964caac2c86bc53 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 11:15:15 -0400 Subject: [PATCH 397/445] skpkg: add config files for authors, changelog, code of conduct, license, README --- CHANGELOG.rst | 2 +- CODE_OF_CONDUCT.rst | 2 +- README.rst | 11 ++++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bc99672e..48c7a51e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,5 @@ ============= -Release Notes +Release notes ============= .. current developments diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst index ff9c3561..e8199ca5 100644 --- a/CODE_OF_CONDUCT.rst +++ b/CODE_OF_CONDUCT.rst @@ -8,7 +8,7 @@ Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, +identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. diff --git a/README.rst b/README.rst index cc86c81e..5fb8421e 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ :target: https://diffpy.github.io/diffpy.utils :height: 100px -|PyPi| |Forge| |PythonVersion| |PR| +|PyPI| |Forge| |PythonVersion| |PR| |CI| |Codecov| |Black| |Tracking| @@ -26,7 +26,7 @@ .. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff -.. |PyPi| image:: https://img.shields.io/pypi/v/diffpy.utils +.. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.utils :target: https://pypi.org/project/diffpy.utils/ .. |PythonVersion| image:: https://img.shields.io/pypi/pyversions/diffpy.utils @@ -141,4 +141,9 @@ Before contributing, please read our `Code of Conduct `_ or email Prof. Simon Billinge at sb2896@columbia.edu. +For more information on diffpy.utils please visit the project `web-page `_ or email Simon Billinge at sb2896@columbia.edu. + +Acknowledgements +---------------- + +``diffpy.utils`` is built and maintained with `scikit-package `_. From b14be774e119cf04ebbf8aaeeaa9dd680b9e34f0 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 11:33:12 -0400 Subject: [PATCH 398/445] chore: news --- news/doc-migration.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/doc-migration.rst diff --git a/news/doc-migration.rst b/news/doc-migration.rst new file mode 100644 index 00000000..b0ec659f --- /dev/null +++ b/news/doc-migration.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Support ``scikit-package`` Level 5 standard (https://scikit-package.github.io/scikit-package/). + +**Security:** + +* From 11ccb9d13cf1a5d78b5cf4062650dca9c4f75f4e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 12:20:53 -0400 Subject: [PATCH 399/445] skpkg: remove deprecated env.yml file --- environment.yml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 environment.yml diff --git a/environment.yml b/environment.yml deleted file mode 100644 index 42088bbc..00000000 --- a/environment.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: diffpy.utils -channels: - - conda-forge -dependencies: - - python=3 - - pip From d8e4f008742054176455d1f59e9f35becf362065 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 12:21:56 -0400 Subject: [PATCH 400/445] skpkg: remove socioeconomic from codespell --- .codespell/ignore_words.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/.codespell/ignore_words.txt b/.codespell/ignore_words.txt index 9757d7c0..04b4fcfa 100644 --- a/.codespell/ignore_words.txt +++ b/.codespell/ignore_words.txt @@ -4,8 +4,5 @@ ;; abbreviation for "materials" often used in a journal title mater -;; alternative use of socioeconomic -socio-economic - ;; Frobenius norm used in np.linalg.norm fro From b1717b8073afa172f6c1afbf80cf98dc81ace2ba Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 12:23:06 -0400 Subject: [PATCH 401/445] skpkg: small changes to some config files --- .flake8 | 2 ++ .isort.cfg | 1 + .readthedocs.yaml | 13 +++++++++++++ pyproject.toml | 8 ++++---- 4 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 .readthedocs.yaml diff --git a/.flake8 b/.flake8 index 5a56eddd..7b2865c1 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,5 @@ +# As of now, flake8 does not natively support configuration via pyproject.toml +# https://github.com/microsoft/vscode-flake8/issues/135 [flake8] exclude = .git, diff --git a/.isort.cfg b/.isort.cfg index 6d831957..86f162b8 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,4 +1,5 @@ [settings] +# Keep import statement below line_length character limit line_length = 79 multi_line_output = 3 include_trailing_comma = True diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..47f7a017 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "latest" + +python: + install: + - requirements: requirements/docs.txt + +sphinx: + configuration: doc/source/conf.py diff --git a/pyproject.toml b/pyproject.toml index 10a6f890..ef9192f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,13 +6,13 @@ build-backend = "setuptools.build_meta" name = "diffpy.utils" dynamic=['version', 'dependencies'] authors = [ - { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, + { name="Simon Billinge", email="sb2896@columbia.edu" }, ] maintainers = [ - { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, + { name="Simon Billinge", email="sb2896@columbia.edu" }, ] description = "General utilities for analyzing diffraction data" -keywords = ["text data parsers", "wx grid", "diffraction objects"] +keywords = ['text data parsers', 'wx grid', 'diffraction objects'] readme = "README.rst" requires-python = ">=3.11, <3.14" classifiers = [ @@ -27,7 +27,7 @@ classifiers = [ 'Operating System :: Unix', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.13', 'Topic :: Scientific/Engineering :: Physics', 'Topic :: Scientific/Engineering :: Chemistry', ] From 629a67047e7ce292e2705ccc93896aedf3780337 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 12:23:44 -0400 Subject: [PATCH 402/445] doc: add sphinx-copybutton as dependency --- requirements/docs.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/docs.txt b/requirements/docs.txt index ab17b1c8..463381e3 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -2,3 +2,4 @@ sphinx sphinx_rtd_theme doctr m2r +sphinx-copybutton From 45a52e20ee9f3f88cadf8681d2b3f05d4d29364a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 12:29:03 -0400 Subject: [PATCH 403/445] chore: news --- news/news-and-misc.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/news-and-misc.rst diff --git a/news/news-and-misc.rst b/news/news-and-misc.rst new file mode 100644 index 00000000..9882947c --- /dev/null +++ b/news/news-and-misc.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add ``spinx-copybutton`` as a dependency for doc building. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Remove ``environment.yml``. + +**Fixed:** + +* + +**Security:** + +* From 9f3e15307cb9e36ad575780b48e0a7475c5662e1 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 11 Jun 2025 13:49:37 -0400 Subject: [PATCH 404/445] chore: update news to no news --- news/news-and-misc.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/news/news-and-misc.rst b/news/news-and-misc.rst index 9882947c..339e8df3 100644 --- a/news/news-and-misc.rst +++ b/news/news-and-misc.rst @@ -1,6 +1,6 @@ **Added:** -* Add ``spinx-copybutton`` as a dependency for doc building. +* No news item needed, bringing package up to ``scikit-package 5v0.1.0`` standards. **Changed:** @@ -12,7 +12,7 @@ **Removed:** -* Remove ``environment.yml``. +* **Fixed:** From f8ddac638fd38b71721244452f8d922344524eba Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Tue, 22 Jul 2025 13:39:14 -0400 Subject: [PATCH 405/445] style: update to `CODE-OF-CONDUCT.rst`, `requirements/tests.text` and `docs/` according to new group standard. --- CODE_OF_CONDUCT.rst => CODE-OF-CONDUCT.rst | 0 README.rst | 2 +- {doc => docs}/Makefile | 0 {doc => docs}/make.bat | 0 {doc => docs}/source/_static/.placeholder | 0 .../source/api/diffpy.utils.parsers.rst | 0 {doc => docs}/source/api/diffpy.utils.rst | 0 {doc => docs}/source/api/diffpy.utils.wx.rst | 0 {doc => docs}/source/conf.py | 4 +-- .../examples/diffraction_objects_example.rst | 0 .../examples/example_data/parser_data.zip | Bin {doc => docs}/source/examples/examples.rst | 0 .../source/examples/parsers_example.rst | 0 .../source/examples/resample_example.rst | 0 .../source/examples/tools_example.rst | 0 .../source/examples/transforms_example.rst | 0 {doc => docs}/source/index.rst | 0 {doc => docs}/source/license.rst | 0 {doc => docs}/source/release.rst | 0 .../utilities/diffraction_objects_utility.rst | 0 .../source/utilities/parsers_utility.rst | 0 .../source/utilities/resample_utility.rst | 0 .../source/utilities/tools_utility.rst | 0 .../source/utilities/transforms_utility.rst | 0 {doc => docs}/source/utilities/utilities.rst | 0 news/group-standard.rst | 23 ++++++++++++++++++ requirements/{test.txt => tests.txt} | 0 27 files changed, 26 insertions(+), 3 deletions(-) rename CODE_OF_CONDUCT.rst => CODE-OF-CONDUCT.rst (100%) rename {doc => docs}/Makefile (100%) rename {doc => docs}/make.bat (100%) rename {doc => docs}/source/_static/.placeholder (100%) rename {doc => docs}/source/api/diffpy.utils.parsers.rst (100%) rename {doc => docs}/source/api/diffpy.utils.rst (100%) rename {doc => docs}/source/api/diffpy.utils.wx.rst (100%) rename {doc => docs}/source/conf.py (99%) rename {doc => docs}/source/examples/diffraction_objects_example.rst (100%) rename {doc => docs}/source/examples/example_data/parser_data.zip (100%) rename {doc => docs}/source/examples/examples.rst (100%) rename {doc => docs}/source/examples/parsers_example.rst (100%) rename {doc => docs}/source/examples/resample_example.rst (100%) rename {doc => docs}/source/examples/tools_example.rst (100%) rename {doc => docs}/source/examples/transforms_example.rst (100%) rename {doc => docs}/source/index.rst (100%) rename {doc => docs}/source/license.rst (100%) rename {doc => docs}/source/release.rst (100%) rename {doc => docs}/source/utilities/diffraction_objects_utility.rst (100%) rename {doc => docs}/source/utilities/parsers_utility.rst (100%) rename {doc => docs}/source/utilities/resample_utility.rst (100%) rename {doc => docs}/source/utilities/tools_utility.rst (100%) rename {doc => docs}/source/utilities/transforms_utility.rst (100%) rename {doc => docs}/source/utilities/utilities.rst (100%) create mode 100644 news/group-standard.rst rename requirements/{test.txt => tests.txt} (100%) diff --git a/CODE_OF_CONDUCT.rst b/CODE-OF-CONDUCT.rst similarity index 100% rename from CODE_OF_CONDUCT.rst rename to CODE-OF-CONDUCT.rst diff --git a/README.rst b/README.rst index 5fb8421e..cf989cdf 100644 --- a/README.rst +++ b/README.rst @@ -136,7 +136,7 @@ trying to commit again. Improvements and fixes are always appreciated. -Before contributing, please read our `Code of Conduct `_. +Before contributing, please read our `Code of Conduct `_. Contact ------- diff --git a/doc/Makefile b/docs/Makefile similarity index 100% rename from doc/Makefile rename to docs/Makefile diff --git a/doc/make.bat b/docs/make.bat similarity index 100% rename from doc/make.bat rename to docs/make.bat diff --git a/doc/source/_static/.placeholder b/docs/source/_static/.placeholder similarity index 100% rename from doc/source/_static/.placeholder rename to docs/source/_static/.placeholder diff --git a/doc/source/api/diffpy.utils.parsers.rst b/docs/source/api/diffpy.utils.parsers.rst similarity index 100% rename from doc/source/api/diffpy.utils.parsers.rst rename to docs/source/api/diffpy.utils.parsers.rst diff --git a/doc/source/api/diffpy.utils.rst b/docs/source/api/diffpy.utils.rst similarity index 100% rename from doc/source/api/diffpy.utils.rst rename to docs/source/api/diffpy.utils.rst diff --git a/doc/source/api/diffpy.utils.wx.rst b/docs/source/api/diffpy.utils.wx.rst similarity index 100% rename from doc/source/api/diffpy.utils.wx.rst rename to docs/source/api/diffpy.utils.wx.rst diff --git a/doc/source/conf.py b/docs/source/conf.py similarity index 99% rename from doc/source/conf.py rename to docs/source/conf.py index ae2443a1..6a8add42 100644 --- a/doc/source/conf.py +++ b/docs/source/conf.py @@ -140,7 +140,7 @@ "github_user": "diffpy", "github_repo": "diffpy.utils", "github_version": "main", - "conf_py_path": "/doc/source/", + "conf_py_path": "/docs/source/", } # Theme options are theme-specific and customize the look and feel of a theme @@ -223,7 +223,7 @@ # Output file base name for HTML help builder. basename = "diffpy.utils".replace(" ", "").replace(".", "") -htmlhelp_basename = basename + "doc" +htmlhelp_basename = basename + "docs" # -- Options for LaTeX output --------------------------------------------- diff --git a/doc/source/examples/diffraction_objects_example.rst b/docs/source/examples/diffraction_objects_example.rst similarity index 100% rename from doc/source/examples/diffraction_objects_example.rst rename to docs/source/examples/diffraction_objects_example.rst diff --git a/doc/source/examples/example_data/parser_data.zip b/docs/source/examples/example_data/parser_data.zip similarity index 100% rename from doc/source/examples/example_data/parser_data.zip rename to docs/source/examples/example_data/parser_data.zip diff --git a/doc/source/examples/examples.rst b/docs/source/examples/examples.rst similarity index 100% rename from doc/source/examples/examples.rst rename to docs/source/examples/examples.rst diff --git a/doc/source/examples/parsers_example.rst b/docs/source/examples/parsers_example.rst similarity index 100% rename from doc/source/examples/parsers_example.rst rename to docs/source/examples/parsers_example.rst diff --git a/doc/source/examples/resample_example.rst b/docs/source/examples/resample_example.rst similarity index 100% rename from doc/source/examples/resample_example.rst rename to docs/source/examples/resample_example.rst diff --git a/doc/source/examples/tools_example.rst b/docs/source/examples/tools_example.rst similarity index 100% rename from doc/source/examples/tools_example.rst rename to docs/source/examples/tools_example.rst diff --git a/doc/source/examples/transforms_example.rst b/docs/source/examples/transforms_example.rst similarity index 100% rename from doc/source/examples/transforms_example.rst rename to docs/source/examples/transforms_example.rst diff --git a/doc/source/index.rst b/docs/source/index.rst similarity index 100% rename from doc/source/index.rst rename to docs/source/index.rst diff --git a/doc/source/license.rst b/docs/source/license.rst similarity index 100% rename from doc/source/license.rst rename to docs/source/license.rst diff --git a/doc/source/release.rst b/docs/source/release.rst similarity index 100% rename from doc/source/release.rst rename to docs/source/release.rst diff --git a/doc/source/utilities/diffraction_objects_utility.rst b/docs/source/utilities/diffraction_objects_utility.rst similarity index 100% rename from doc/source/utilities/diffraction_objects_utility.rst rename to docs/source/utilities/diffraction_objects_utility.rst diff --git a/doc/source/utilities/parsers_utility.rst b/docs/source/utilities/parsers_utility.rst similarity index 100% rename from doc/source/utilities/parsers_utility.rst rename to docs/source/utilities/parsers_utility.rst diff --git a/doc/source/utilities/resample_utility.rst b/docs/source/utilities/resample_utility.rst similarity index 100% rename from doc/source/utilities/resample_utility.rst rename to docs/source/utilities/resample_utility.rst diff --git a/doc/source/utilities/tools_utility.rst b/docs/source/utilities/tools_utility.rst similarity index 100% rename from doc/source/utilities/tools_utility.rst rename to docs/source/utilities/tools_utility.rst diff --git a/doc/source/utilities/transforms_utility.rst b/docs/source/utilities/transforms_utility.rst similarity index 100% rename from doc/source/utilities/transforms_utility.rst rename to docs/source/utilities/transforms_utility.rst diff --git a/doc/source/utilities/utilities.rst b/docs/source/utilities/utilities.rst similarity index 100% rename from doc/source/utilities/utilities.rst rename to docs/source/utilities/utilities.rst diff --git a/news/group-standard.rst b/news/group-standard.rst new file mode 100644 index 00000000..69ba1d29 --- /dev/null +++ b/news/group-standard.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: Use ``CODE-OF-CONDUCT,rst``, ``requirements/tests.text`` and ``docs`` according to new group standard. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/requirements/test.txt b/requirements/tests.txt similarity index 100% rename from requirements/test.txt rename to requirements/tests.txt From e00193016876a48b176283ad7ab91a9259ce67ef Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Tue, 22 Jul 2025 13:55:50 -0400 Subject: [PATCH 406/445] fix: replace the old file name in the project --- .flake8 | 2 +- .readthedocs.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.flake8 b/.flake8 index 7b2865c1..88077af0 100644 --- a/.flake8 +++ b/.flake8 @@ -6,7 +6,7 @@ exclude = __pycache__, build, dist, - doc/source/conf.py + docs/source/conf.py 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 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 47f7a017..aaa88895 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,4 +10,4 @@ python: - requirements: requirements/docs.txt sphinx: - configuration: doc/source/conf.py + configuration: docs/source/conf.py From 0cb0a0489d5201a5d2f7df9f1fc3c4037d1ce74a Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Thu, 24 Jul 2025 13:48:21 -0400 Subject: [PATCH 407/445] docs: fix the example of using `DiffractionObject.get_array_index` in the documentation --- .../examples/diffraction_objects_example.rst | 6 ++--- news/fix-docs-example.rst | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 news/fix-docs-example.rst diff --git a/docs/source/examples/diffraction_objects_example.rst b/docs/source/examples/diffraction_objects_example.rst index 1122bf21..8794c719 100644 --- a/docs/source/examples/diffraction_objects_example.rst +++ b/docs/source/examples/diffraction_objects_example.rst @@ -180,7 +180,7 @@ For example, attempting to add a diffraction object and a string will raise an e .. code-block:: python - tth_ninety_index = diff_object1.get_array_index(90, xtype="tth") + tth_ninety_index = diff_object1.get_array_index(xvalue=90, xtype="tth") intensity_at_ninety = diff_object1.on_tth()[1][tth_ninety_index] If you do not specify an ``xtype``, it will default to the ``xtype`` used when creating the ``DiffractionObject``. @@ -190,8 +190,8 @@ you can find its closest index for ``q=0.25`` by typing either of the following: .. code-block:: python print(do._input_xtype) # remind ourselves which array was input. prints "q" in this case. - index = do.get_array_index(0.25) # no xtype passed, defaults to do._input_xtype, or in this example, q - index = do.get_array_index(0.25, xtype="q") # explicitly choose an xtype to specify a value + index = do.get_array_index(xvalue=0.25) # no xtype passed, defaults to do._input_xtype, or in this example, q + index = do.get_array_index(xvalue=0.25, xtype="q") # explicitly choose an xtype to specify a value 5. The ``dump`` function saves the diffraction data and relevant information to an xy format file with headers (widely used chi format used, for example, by Fit2D and diffpy. These files can be read by ``LoadData()`` diff --git a/news/fix-docs-example.rst b/news/fix-docs-example.rst new file mode 100644 index 00000000..ef351fae --- /dev/null +++ b/news/fix-docs-example.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: Fix the example of using ``DiffractionObject.get_array_index`` in the documentation. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 14ec0bc17f123fd125b3b0eb877a75e35bb17dd4 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Thu, 24 Jul 2025 15:05:41 -0400 Subject: [PATCH 408/445] docs: remove a duplicate code block --- docs/source/examples/resample_example.rst | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/source/examples/resample_example.rst b/docs/source/examples/resample_example.rst index 4884f5c9..ba28390b 100644 --- a/docs/source/examples/resample_example.rst +++ b/docs/source/examples/resample_example.rst @@ -23,18 +23,6 @@ given enough datapoints. Each data table has two columns: first is the grid and second is the function value. To extract the columns, we can utilize the serialize function ... -.. code-block:: python - - from diffpy.utils.parsers.serialization import serialize_data - nickel_data = serialize_data('Nickel.gr', {}, nickel_datatable, dt_colnames=['grid', 'func']) - nickel_grid = nickel_data['Nickel.gr']['grid'] - nickel_func = nickel_data['Nickel.gr']['func'] - target_data = serialize_data('NiTarget.gr', {}, nitarget_datatable, dt_colnames=['grid', 'function']) - target_grid = nickel_data['Nickel.gr']['grid'] - target_func = nickel_data['Nickel.gr']['func'] - -To extract the columns, we can utilize the serialize function ... - .. code-block:: python from diffpy.utils.parsers.serialization import serialize_data From 174f2be1ebc27129f061d34e9fcf787896adc2c3 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Thu, 24 Jul 2025 15:07:41 -0400 Subject: [PATCH 409/445] docs: fix the `.rst` markup --- docs/source/examples/parsers_example.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/source/examples/parsers_example.rst b/docs/source/examples/parsers_example.rst index 92135f3b..09220a3a 100644 --- a/docs/source/examples/parsers_example.rst +++ b/docs/source/examples/parsers_example.rst @@ -104,8 +104,7 @@ Now we can extract specific data table columns from the dictionary. parsed_file_data = serialize_data('', hdata, data_table, serial_file='') - The returned value, ``parsed_file_data``, is the dictionary we just added to ``serialfile.json``. - To extract the data from the serial file, we use ``deserialize_data``. +The returned value, ``parsed_file_data``, is the dictionary we just added to ``serialfile.json``. To extract the data from the serial file, we use ``deserialize_data``. .. code-block:: python From 829d62a609fc1ce6432eb14e61ee03030722f8f0 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Thu, 24 Jul 2025 15:08:08 -0400 Subject: [PATCH 410/445] chore: update news --- news/fix-docs-example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/fix-docs-example.rst b/news/fix-docs-example.rst index ef351fae..0048f8a3 100644 --- a/news/fix-docs-example.rst +++ b/news/fix-docs-example.rst @@ -1,6 +1,6 @@ **Added:** -* No news added: Fix the example of using ``DiffractionObject.get_array_index`` in the documentation. +* No news added: Fix some examples in the documentation. **Changed:** From 8e43660cc139a412a20a74adba66a263a6281cd6 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 25 Jul 2025 11:18:43 -0400 Subject: [PATCH 411/445] test: update the test code for `DiffractionObject.get_array_index` --- news/fix-get-array-index.rst | 23 +++++++++++++++++++++++ tests/test_diffraction_objects.py | 10 +++++----- 2 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 news/fix-get-array-index.rst diff --git a/news/fix-get-array-index.rst b/news/fix-get-array-index.rst new file mode 100644 index 00000000..67814f58 --- /dev/null +++ b/news/fix-get-array-index.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Let ``DiffractionObject.get_array_index`` to use the ``xtype`` from its inputs. + +**Security:** + +* diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 8ed1dd3b..aa8cb7d0 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -376,8 +376,8 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, 0, ), - ( # C2: Target value lies in the array, expect the (first) closest - # index + # C2: Target value lies in the array, expect the closest index + ( # 1. same xtype { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -390,7 +390,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, 0, ), - ( + ( # 2. different xtype { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -399,9 +399,9 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, { "xtype": "q", - "value": 0.25, + "value": 0.5, }, - 0, + 1, ), # C3: Target value out of the range, expect the closest index ( # 1. Test with xtype of "q" From bedf468755208eab679616085515c64fbda4536c Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 25 Jul 2025 15:54:48 -0400 Subject: [PATCH 412/445] fix: let `DiffractionObject.get_array_index` to use an optional input `xtype` --- src/diffpy/utils/diffraction_objects.py | 17 +++++++----- tests/test_diffraction_objects.py | 36 ++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 25f32f4d..64a40447 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -409,17 +409,17 @@ def uuid(self): def uuid(self, _): raise AttributeError(_setter_wmsg("uuid")) - def get_array_index(self, xtype, xvalue): - """Return the index of the closest value in the array associated with + def get_array_index(self, xvalue, xtype=None): + f"""Return the index of the closest value in the array associated with the specified xtype and the value provided. Parameters ---------- - xtype : str - The type of the independent variable in `xarray`. Must be one - of {*XQUANTITIES}. xvalue : float The value of the xtype to find the closest index for. + xtype : str, optional + The type of the independent variable in `xarray`. Must be one + of {*XQUANTITIES,}. Default is {self._input_xtype} Returns ------- @@ -427,8 +427,11 @@ def get_array_index(self, xtype, xvalue): The index of the closest value in the array associated with the specified xtype and the value provided. """ - - xtype = self._input_xtype + if xtype is None: + xtype = self._input_xtype + else: + if xtype not in XQUANTITIES: + raise ValueError(_xtype_wmsg(xtype)) xarray = self.on_xtype(xtype)[0] if len(xarray) == 0: raise ValueError( diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aa8cb7d0..59ee5934 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -377,7 +377,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): 0, ), # C2: Target value lies in the array, expect the closest index - ( # 1. same xtype + ( # 1. xtype(tth) is equal to self._input_xtype(tth) { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -390,7 +390,20 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): }, 0, ), - ( # 2. different xtype + ( # 2. use default xtype(equal to self._input_xtype) + { + "wavelength": 4 * np.pi, + "xarray": np.array([30, 60]), + "yarray": np.array([1, 2]), + "xtype": "tth", + }, + { + "xtype": None, + "value": 45, + }, + 0, + ), + ( # 3. xtype(q) is different from self._input_xtype(tth) { "wavelength": 4 * np.pi, "xarray": np.array([30, 60]), @@ -435,12 +448,13 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): def test_get_array_index(do_args, get_array_index_inputs, expected_index): do = DiffractionObject(**do_args) actual_index = do.get_array_index( - get_array_index_inputs["xtype"], get_array_index_inputs["value"] + get_array_index_inputs["value"], get_array_index_inputs["xtype"] ) assert actual_index == expected_index def test_get_array_index_bad(): + # empty array in DiffractionObject do = DiffractionObject( wavelength=2 * np.pi, xarray=np.array([]), @@ -454,6 +468,22 @@ def test_get_array_index_bad(): ), ): do.get_array_index(xtype="tth", xvalue=30) + # non-existing xtype + do = DiffractionObject( + wavelength=4 * np.pi, + xarray=np.array([30, 60]), + yarray=np.array([1, 2]), + xtype="tth", + ) + non_existing_xtype = "non_existing_xtype" + with pytest.raises( + ValueError, + match=re.escape( + f"I don't know how to handle the xtype, '{non_existing_xtype}'. " + f"Please rerun specifying an xtype from {*XQUANTITIES, }" + ), + ): + do.get_array_index(xtype=non_existing_xtype, xvalue=30) def test_dump(tmp_path, mocker): From eaeafe58af624b769bf2bf366b54f9c4b1eb170e Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 25 Jul 2025 15:57:45 -0400 Subject: [PATCH 413/445] chore: fix pre-commit --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 64a40447..ec09b414 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -419,7 +419,7 @@ def get_array_index(self, xvalue, xtype=None): The value of the xtype to find the closest index for. xtype : str, optional The type of the independent variable in `xarray`. Must be one - of {*XQUANTITIES,}. Default is {self._input_xtype} + of {*XQUANTITIES, }. Default is {self._input_xtype} Returns ------- From 3ea591b6e660fbf55e4205bf4eaa0e7081f1fc31 Mon Sep 17 00:00:00 2001 From: Yuchen Ethan Xiao Date: Fri, 25 Jul 2025 16:45:46 -0400 Subject: [PATCH 414/445] docs: fix the `diffpy.utils.transforms.d_to_tth` example --- docs/source/examples/transforms_example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/examples/transforms_example.rst b/docs/source/examples/transforms_example.rst index 2fc40c4a..70d63fb4 100644 --- a/docs/source/examples/transforms_example.rst +++ b/docs/source/examples/transforms_example.rst @@ -59,5 +59,5 @@ This example will demonstrate how to use the functions in the from diffpy.utils.transforms import d_to_tth wavelength = 0.71 - d = np.array([1.0, 0.8, 0.6, 0.4, 0.2]) + d = np.array([1.0, 0.8, 0.6, 0.4]) tth = d_to_tth(d, wavelength) From 208436c913c1775fc386748726ba40be51ae73bb Mon Sep 17 00:00:00 2001 From: sbillinge <4254545+sbillinge@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:01:28 +0000 Subject: [PATCH 415/445] update changelog --- CHANGELOG.rst | 18 ++++++++++++++++++ news/changelog-3.6.0-line-break.rst | 23 ----------------------- news/doc-migration.rst | 23 ----------------------- news/fix-docs-example.rst | 23 ----------------------- news/fix-get-array-index.rst | 23 ----------------------- news/group-standard.rst | 23 ----------------------- news/linelength79.rst | 23 ----------------------- news/news-and-misc.rst | 23 ----------------------- news/readme.rst | 23 ----------------------- news/setup-CI.rst | 23 ----------------------- 10 files changed, 18 insertions(+), 207 deletions(-) delete mode 100644 news/changelog-3.6.0-line-break.rst delete mode 100644 news/doc-migration.rst delete mode 100644 news/fix-docs-example.rst delete mode 100644 news/fix-get-array-index.rst delete mode 100644 news/group-standard.rst delete mode 100644 news/linelength79.rst delete mode 100644 news/news-and-misc.rst delete mode 100644 news/readme.rst delete mode 100644 news/setup-CI.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 48c7a51e..302e6949 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,24 @@ Release notes .. current developments +3.6.1 +===== + +**Added:** + +* update isort, flake8 and black to set line limit to 79 + +**Fixed:** + +* Support ``scikit-package`` Level 5 standard (https://scikit-package.github.io/scikit-package/). +* Update github CI to scikit-package standard +* Let ``DiffractionObject.get_array_index`` to use the ``xtype`` from its inputs. + +**Removed:** + +* Extra line break in each news after 3.6.0 in `CHANGELOG.rst` so that this rst can be rendered correctly when deployed + + 3.6.0 ===== diff --git a/news/changelog-3.6.0-line-break.rst b/news/changelog-3.6.0-line-break.rst deleted file mode 100644 index ac80a0a1..00000000 --- a/news/changelog-3.6.0-line-break.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* Extra line break in each news after 3.6.0 in `CHANGELOG.rst` so that this rst can be rendered correctly when deployed - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/doc-migration.rst b/news/doc-migration.rst deleted file mode 100644 index b0ec659f..00000000 --- a/news/doc-migration.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Support ``scikit-package`` Level 5 standard (https://scikit-package.github.io/scikit-package/). - -**Security:** - -* diff --git a/news/fix-docs-example.rst b/news/fix-docs-example.rst deleted file mode 100644 index 0048f8a3..00000000 --- a/news/fix-docs-example.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: Fix some examples in the documentation. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/fix-get-array-index.rst b/news/fix-get-array-index.rst deleted file mode 100644 index 67814f58..00000000 --- a/news/fix-get-array-index.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Let ``DiffractionObject.get_array_index`` to use the ``xtype`` from its inputs. - -**Security:** - -* diff --git a/news/group-standard.rst b/news/group-standard.rst deleted file mode 100644 index 69ba1d29..00000000 --- a/news/group-standard.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news added: Use ``CODE-OF-CONDUCT,rst``, ``requirements/tests.text`` and ``docs`` according to new group standard. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/linelength79.rst b/news/linelength79.rst deleted file mode 100644 index be12d2fb..00000000 --- a/news/linelength79.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* update isort, flake8 and black to set line limit to 79 - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/news-and-misc.rst b/news/news-and-misc.rst deleted file mode 100644 index 339e8df3..00000000 --- a/news/news-and-misc.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* No news item needed, bringing package up to ``scikit-package 5v0.1.0`` standards. - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/readme.rst b/news/readme.rst deleted file mode 100644 index 978de17c..00000000 --- a/news/readme.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* no news needed: just a chore of no import - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* - -**Security:** - -* diff --git a/news/setup-CI.rst b/news/setup-CI.rst deleted file mode 100644 index bf692d68..00000000 --- a/news/setup-CI.rst +++ /dev/null @@ -1,23 +0,0 @@ -**Added:** - -* - -**Changed:** - -* - -**Deprecated:** - -* - -**Removed:** - -* - -**Fixed:** - -* Update github CI to scikit-package standard - -**Security:** - -* From c14814b1f27b603154972c9a8f35751e62338185 Mon Sep 17 00:00:00 2001 From: Tieqiong Zhang Date: Thu, 21 Aug 2025 16:59:14 -0500 Subject: [PATCH 416/445] Normalize paths before assert --- news/headerfile.rst | 23 +++++++++++++++++++++++ tests/test_serialization.py | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 news/headerfile.rst diff --git a/news/headerfile.rst b/news/headerfile.rst new file mode 100644 index 00000000..13b35c28 --- /dev/null +++ b/news/headerfile.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Normalize paths before assert in `test_serialization/test_load_multiple` to prevent possible Windows short/long name mismatch. + +**Security:** + +* diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 039877f6..049d325c 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -33,7 +33,9 @@ def test_load_multiple(tmp_path, datafile): dt_colnames=["r", "gr"], show_path=True, ) - assert headerfile == Path(generated_data[headerfile.name].pop("path")) + assert Path(headerfile).resolve() == Path( + generated_data[headerfile.name].pop("path") + ) # rerun without path information and save to file generated_data = serialize_data( From 02a294e15b7ba2534a959554451a00485e3c1d1b Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 20 Nov 2025 15:38:44 -0500 Subject: [PATCH 417/445] fix: fix docstring to interpolate right entry. --- news/docstring-fix.rst | 24 ++++++++++++++++++++++++ src/diffpy/utils/resampler.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 news/docstring-fix.rst diff --git a/news/docstring-fix.rst b/news/docstring-fix.rst new file mode 100644 index 00000000..11ec4ce8 --- /dev/null +++ b/news/docstring-fix.rst @@ -0,0 +1,24 @@ +**Added:** + +* No News Added: Fix docstring + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* + diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index ca93761c..6003574e 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -38,7 +38,7 @@ def wsinterp(x, xp, fp, left=None, right=None): The array of y values associated with xp. left: float If given, set fp for x < xp[0] to left. Otherwise, if left is None - (default) or not given, set fp for x < xp[0] to fp evaluated at xp[-1]. + (default) or not given, set fp for x < xp[0] to fp evaluated at xp[0]. right: float If given, set fp for x > xp[-1] to right. Otherwise, if right is None (default) or not given, set fp for x > xp[-1] to fp evaluated at From 3dee8fadb438752f44c45a8023a910b400f47418 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:39:30 +0000 Subject: [PATCH 418/445] [pre-commit.ci] auto fixes from pre-commit hooks --- news/docstring-fix.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/news/docstring-fix.rst b/news/docstring-fix.rst index 11ec4ce8..fc596a63 100644 --- a/news/docstring-fix.rst +++ b/news/docstring-fix.rst @@ -21,4 +21,3 @@ **Security:** * - From 93f030cbd591b34a8ff36fad199348a598c35b75 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 20 Nov 2025 16:43:56 -0500 Subject: [PATCH 419/445] fix: fix format of docs build so that no warning arise --- docs/source/api/diffpy.utils.rst | 8 --- src/diffpy/utils/diffraction_objects.py | 36 ++++++------- src/diffpy/utils/resampler.py | 16 +++--- src/diffpy/utils/tools.py | 68 ++++++++++++++----------- src/diffpy/utils/transforms.py | 24 ++++----- 5 files changed, 75 insertions(+), 77 deletions(-) diff --git a/docs/source/api/diffpy.utils.rst b/docs/source/api/diffpy.utils.rst index e4de1d4f..c50dd0cd 100644 --- a/docs/source/api/diffpy.utils.rst +++ b/docs/source/api/diffpy.utils.rst @@ -44,14 +44,6 @@ diffpy.utils.tools module :undoc-members: :show-inheritance: -diffpy.utils.user_config module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.utils.user_config - :members: - :undoc-members: - :show-inheritance: - diffpy.utils.diffraction_objects module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index ec09b414..733d3705 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -108,18 +108,18 @@ def __init__( Parameters ---------- - xarray : ndarray + xarray : ``ndarray`` The independent variable array containing "q", "tth", or "d" values. - yarray : ndarray + yarray : ``ndarray`` 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, default is None. + wavelength : float, ``optional``, default is None. The wavelength of the incoming beam, specified in angstroms (Å) - scat_quantity : str, optional, default is an empty string "". + scat_quantity : str, ``optional``, default is an empty string "". The type of scattering experiment (e.g., "x-ray", "neutron"). - name : str, optional, default is an empty string "". + name : str, ``optional``, default is an empty string "". The name or label for the scattering data. metadata : dict, optional, default is an empty dictionary {} The additional metadata associated with the diffraction object. @@ -360,7 +360,7 @@ def all_arrays(self): Returns ------- - ndarray + ``ndarray`` The shape (len(data), 4) 2D array with columns containing the ` yarray` (intensity) and the `xarray` values in q, tth, and d. @@ -386,7 +386,7 @@ def input_xtype(self): Returns ------- input_xtype : str - The type of `xarray`, which must be one of {*XQUANTITIES}. + The type of `xarray`, which must be one of ``{*XQUANTITIES}``. """ return self._input_xtype @@ -400,7 +400,7 @@ def uuid(self): Returns ------- - uuid : UUID + uuid : ``UUID`` The unique identifier of the DiffractionObject instance. """ return self._uuid @@ -478,7 +478,7 @@ def on_q(self): Returns ------- - (q-array, y-array) : tuple of ndarray + (q-array, y-array) : tuple of ``ndarray`` The tuple containing two 1D numpy arrays with q and y data """ return [self.all_arrays[:, 1], self.all_arrays[:, 0]] @@ -488,7 +488,7 @@ def on_tth(self): Returns ------- - (tth-array, y-array) : tuple of ndarray + (tth-array, y-array) : tuple of ``ndarray`` The tuple containing two 1D numpy arrays with tth and y data """ return [self.all_arrays[:, 2], self.all_arrays[:, 0]] @@ -498,7 +498,7 @@ def on_d(self): Returns ------- - (d-array, y-array) : tuple of ndarray + (d-array, y-array) : tuple of ``ndarray`` The tuple containing two 1D numpy arrays with d and y data """ return [self.all_arrays[:, 3], self.all_arrays[:, 0]] @@ -522,12 +522,12 @@ def scale_to( target_diff_object: DiffractionObject The diffraction object you want to scale the current one onto. - q, tth, d : float, optional, default is None + q, tth, d : float, ``optional``, default is None The value of the x-array where you want the curves to line up vertically. Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. - offset : float, optional, default is None + offset : float, ``optional``, default is None The offset to add to the scaled y-values. Returns @@ -574,16 +574,16 @@ def on_xtype(self, xtype): ---------- xtype : str The type of quantity for the independent variable chosen from - {*XQUANTITIES, } + ``{*XQUANTITIES, }`` Raises ------ ValueError - Raised when the specified xtype is not among {*XQUANTITIES, } + Raised when the specified xtype is not among ``{*XQUANTITIES, }`` Returns ------- - (xarray, yarray) : tuple of ndarray + (xarray, yarray) : tuple of ``ndarray`` The tuple containing two 1D numpy arrays with x and y data for the specified xtype. """ @@ -604,9 +604,9 @@ def dump(self, filepath, xtype=None): ---------- filepath : str The filepath where the diffraction object will be dumped - xtype : str, optional, default is q + xtype : str, ``optional``, default is q The type of quantity for the independent variable chosen from - {*XQUANTITIES, } + ``{*XQUANTITIES, }`` Examples -------- diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index ca93761c..3cf6790d 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -30,11 +30,11 @@ def wsinterp(x, xp, fp, left=None, right=None): Parameters ---------- - x: ndarray + x: ``ndarray`` The x values at which interpolation is computed. - xp: ndarray + xp: ``ndarray`` The array of known x values. - fp: ndarray + fp: ``ndarray`` The array of y values associated with xp. left: float If given, set fp for x < xp[0] to left. Otherwise, if left is None @@ -46,7 +46,7 @@ def wsinterp(x, xp, fp, left=None, right=None): Returns ------- - ndarray or float + ``ndarray`` or float The interpolated values at points x. Returns a single float if x is a scalar, otherwise returns a numpy.ndarray. """ @@ -91,9 +91,9 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): Parameters ---------- - xp: ndarray + xp: ``ndarray`` The array of known x values. - fp: ndarray + fp: ``ndarray`` The array of y values associated with xp. qmin: float The lower band limit in the frequency domain. @@ -102,7 +102,7 @@ def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): Returns ------- - x: ndarray + x: ``ndarray`` The Nyquist-Shannon grid computed for the given qmin and qmax. fp_at_x: ndarray The interpolated values at points x. Returns a single float if x is a @@ -139,7 +139,7 @@ def resample(r, s, dr): Returns ------- - Returns resampled (r, s). + Returns resampled ``(r, s)``. """ warnings.warn( diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 63e10ba2..3a42990d 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -51,43 +51,46 @@ def _load_config(file_path): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): - """Get name, email, and orcid of the owner/user from various sources and + """Get name, email, and ORCID of the owner/user from various sources and return it as a metadata dictionary. - The function looks for the information in json format configuration files - with the name 'diffpyconfig.json'. These can be in the user's home - directory and in the current working directory. The information in the - config files are combined, with the local config overriding the - home- directory one. Values for owner_name, owner_email, and owner_orcid - may be passed in to the function and these override the values in the - config files. - - A template for the config file is below. Create a text file called ' - diffpyconfig.json' in your home directory and copy-paste the template - into it, editing it with your real information. - { - "owner_name": ">", - "owner_email": ">@email.com", - "owner_orcid": ">" # noqa: E501 - } + The function looks for information in JSON configuration files named + ``diffpyconfig.json``. These can be in the user's home directory and in + the current working directory. The information in the config files is + combined, with the local config overriding the home-directory one. + Values for ``owner_name``, ``owner_email``, and ``owner_orcid`` may be + passed in to the function, and these override the config files. + + A template for the config file is below. Create a text file called + ``diffpyconfig.json`` in your home directory and paste the template into + it, editing it with your real information:: + + { + "owner_name": "", + "owner_email": "@email.com", + "owner_orcid": "" # noqa: E501 + } + You may also store any other global-level information that you would like - associated with your diffraction data in this file + associated with your diffraction data in this file. Parameters ---------- - owner_name : str, optional, default is the value stored in the global or - local config file. The name of the user who will show as owner in the - metadata that is stored with the data - owner_email : str, optional, default is the value stored in the global or - local config file. The email of the user/owner - owner_orcid : str, optional, default is the value stored in the global or - local config file. The ORCID id of the user/owner + owner_name : str, ``optional`` + Default is the value stored in the global or local config file. + The name of the user stored in metadata. + owner_email : str, ``optional`` + Default is the value stored in the global or local config file. + The email address of the user/owner. + owner_orcid : str, ``optional`` + Default is the value stored in the global or local config file. + The ORCID ID of the user/owner. Returns ------- user_info : dict - The dictionary containing username, email and orcid of the user/owner - , and any other information stored in the global or local config files. + Dictionary containing username, email, ORCID, and any other + information stored in global or local config files. """ runtime_info = { "owner_name": owner_name, @@ -127,9 +130,9 @@ def check_and_build_global_config(skip_config_creation=False): Parameters ---------- - skip_config_creation : bool, optional, default is False. + skip_config_creation : bool, ``optional`` The boolean that will override the creation workflow even if no - config file exists. + config file exists. Default is False. Returns ------- @@ -190,6 +193,7 @@ def get_package_info(package_names, metadata=None): Package info stored in metadata as {'package_info': {'package_name': 'version_number'}}. + Parameters ---------- package_name : str or list The name of the package(s) to retrieve the version number for. @@ -244,11 +248,13 @@ def compute_mu_using_xraydb( The chemical formula of the material. energy : float The energy of the incident x-rays in keV. - sample_mass_density : float, optional, Default is None + sample_mass_density : float, ``optional`` The mass density of the packed powder/sample in g/cm*3. - packing_fraction : float, optional, Default is None + Default is None. + packing_fraction : float, ``optional`` The fraction of sample in the capillary (between 0 and 1). Specify either sample_mass_density or packing_fraction but not both. + Default is None. Returns ------- diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 56eebc68..ab1d009e 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -52,7 +52,7 @@ def q_to_tth(q, wavelength): Parameters ---------- - q : ndarray + q : ``ndarray`` The 1D array of :math:`q` values numpy.array([qs]). The units of q must be reciprocal of the units of wavelength. @@ -61,7 +61,7 @@ def q_to_tth(q, wavelength): Returns ------- - tth : ndarray + tth : ``ndarray`` The 1D array of :math:`2\theta` values in degrees numpy.array([tths]). """ _validate_inputs(q, wavelength) @@ -95,7 +95,7 @@ def tth_to_q(tth, wavelength): Parameters ---------- - tth : ndarray + tth : ``ndarray`` The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. @@ -104,7 +104,7 @@ def tth_to_q(tth, wavelength): Returns ------- - q : ndarray + q : ``ndarray`` The 1D array of :math:`q` values np.array([qs]). The units for the q-values are the inverse of the units of the provided wavelength. @@ -129,13 +129,13 @@ def q_to_d(q): Parameters ---------- - q : ndarray + q : ``ndarray`` The 1D array of :math:`q` values np.array([qs]). The units of q must be reciprocal of the units of wavelength. Returns ------- - d : ndarray + d : ``ndarray`` The 1D array of :math:`d` values np.array([ds]). """ if 0 in q: @@ -153,7 +153,7 @@ def tth_to_d(tth, wavelength): Parameters ---------- - tth : nsarray + tth : ``nsarray`` The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. @@ -162,7 +162,7 @@ def tth_to_d(tth, wavelength): Returns ------- - d : nsarray + d : ``nsarray`` The 1D array of :math:`d` values np.array([ds]). """ q = tth_to_q(tth, wavelength) @@ -182,12 +182,12 @@ def d_to_q(d): Parameters ---------- - d : nsarray + d : ``nsarray`` The 1D array of :math:`d` values np.array([ds]). Returns ------- - q : nsarray + q : ``nsarray`` The 1D array of :math:`q` values np.array([qs]). The units of q must be reciprocal of the units of wavelength. """ @@ -206,7 +206,7 @@ def d_to_tth(d, wavelength): Parameters ---------- - d : nsarray + d : ``nsarray`` The 1D array of :math:`d` values np.array([ds]). wavelength : float @@ -214,7 +214,7 @@ def d_to_tth(d, wavelength): Returns ------- - tth : nsarray + tth : ``nsarray`` The 1D array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. """ From 086e9bf16d7fadf39fc2a97f4d2188b694de7be7 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 20 Nov 2025 16:46:24 -0500 Subject: [PATCH 420/445] chore: add news item. --- news/html-build.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/html-build.rst diff --git a/news/html-build.rst b/news/html-build.rst new file mode 100644 index 00000000..cf04edf2 --- /dev/null +++ b/news/html-build.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: Fix format of docs to build html + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 2e8d887b1133620621d4b496f4d74fb45a0269d1 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 21 Nov 2025 16:50:41 -0500 Subject: [PATCH 421/445] deprecator decorator --- src/diffpy/utils/_deprecator.py | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/diffpy/utils/_deprecator.py diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py new file mode 100644 index 00000000..1334de17 --- /dev/null +++ b/src/diffpy/utils/_deprecator.py @@ -0,0 +1,78 @@ +import functools +import warnings + +# Deprecated decorator is available for Python 3.13+, once +# Support for earlier versions is dropped, this custom implementation can be removed. +try: + from warnings import deprecated as _builtin_deprecated +except ImportError: + _builtin_deprecated = None + +def deprecated(*, alt_name=None, message=None): + """ + Marks a function or class as deprecated. + + Emits a DeprecationWarning whenever the decorated function is called + or the decorated class is instantiated. + + Parameters + ---------- + alt_name : str, optional + Name of the recommended alternative. + message : str, optional + Custom deprecation message. If None, a default message is generated. + + Returns + ------- + decorator : function + Decorator that wraps the deprecated object. + + Examples + -------- + .. code-block:: python + + from diffpy._deprecations import deprecated + + # ------------------------------ + # Deprecated function + # ------------------------------ + @deprecated(alt_name="new_function") + def old_function(x, y): + return x + y + + def new_function(x, y): + return x + y + + # Usage + old_function(1, 2) # Emits DeprecationWarning + new_function(1, 2) # No warning + + # ------------------------------ + # Deprecated class + # ------------------------------ + @deprecated(alt_name="NewAtom") + class OldAtom: + def __init__(self, symbol): + self.symbol = symbol + + # Usage + a = OldAtom("C") # Emits DeprecationWarning + atom = NewAtom("C") # No warning + """ + if _builtin_deprecated: + return _builtin_deprecated + + def decorator(obj): + name = getattr(obj, "__name__", repr(obj)) + msg = message or (f"'{name}' is deprecated. Use '{alt_name}' instead." + if alt_name else f"'{name}' is deprecated.") + + if callable(obj): + @functools.wraps(obj) + def wrapper(*args, **kwargs): + warnings.warn(msg, DeprecationWarning, stacklevel=2) + return obj(*args, **kwargs) + return wrapper + else: + raise TypeError("deprecated decorator can only be applied to functions or classes") + return decorator From 614b234383a47d3307a7b91e53e8ffe062e6515c Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 21 Nov 2025 16:51:52 -0500 Subject: [PATCH 422/445] precommit fixes --- src/diffpy/utils/_deprecator.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 1334de17..099e371c 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -2,15 +2,16 @@ import warnings # Deprecated decorator is available for Python 3.13+, once -# Support for earlier versions is dropped, this custom implementation can be removed. +# Support for earlier versions is dropped, this custom +# implementation can be removed. try: from warnings import deprecated as _builtin_deprecated except ImportError: _builtin_deprecated = None + def deprecated(*, alt_name=None, message=None): - """ - Marks a function or class as deprecated. + """Marks a function or class as deprecated. Emits a DeprecationWarning whenever the decorated function is called or the decorated class is instantiated. @@ -64,15 +65,24 @@ def __init__(self, symbol): def decorator(obj): name = getattr(obj, "__name__", repr(obj)) - msg = message or (f"'{name}' is deprecated. Use '{alt_name}' instead." - if alt_name else f"'{name}' is deprecated.") + msg = message or ( + f"'{name}' is deprecated. Use '{alt_name}' instead." + if alt_name + else f"'{name}' is deprecated." + ) if callable(obj): + @functools.wraps(obj) def wrapper(*args, **kwargs): warnings.warn(msg, DeprecationWarning, stacklevel=2) return obj(*args, **kwargs) + return wrapper else: - raise TypeError("deprecated decorator can only be applied to functions or classes") + raise TypeError( + "deprecated decorator can only be applied to functions or " + "classes" + ) + return decorator From 4ec1cf22ee3202964d7ac4922b8868e91dd5fe42 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 21 Nov 2025 16:52:38 -0500 Subject: [PATCH 423/445] news --- news/deprecator.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/deprecator.rst diff --git a/news/deprecator.rst b/news/deprecator.rst new file mode 100644 index 00000000..dabbfdf1 --- /dev/null +++ b/news/deprecator.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add ``@deprecated`` decorator. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 0728a448cc9785e4ec456810be88d1eaeaeda7e8 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 24 Nov 2025 11:00:16 -0500 Subject: [PATCH 424/445] update deprecator to exactly match 3.13 deprecated decorator --- src/diffpy/utils/_deprecator.py | 85 +++++++++------------------------ 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 099e371c..55593c96 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -10,79 +10,40 @@ _builtin_deprecated = None -def deprecated(*, alt_name=None, message=None): - """Marks a function or class as deprecated. +def deprecated(message, *, category=DeprecationWarning, stacklevel=1): + """Compatibility wrapper for Python <3.13. - Emits a DeprecationWarning whenever the decorated function is called - or the decorated class is instantiated. - - Parameters - ---------- - alt_name : str, optional - Name of the recommended alternative. - message : str, optional - Custom deprecation message. If None, a default message is generated. - - Returns - ------- - decorator : function - Decorator that wraps the deprecated object. - - Examples - -------- - .. code-block:: python - - from diffpy._deprecations import deprecated - - # ------------------------------ - # Deprecated function - # ------------------------------ - @deprecated(alt_name="new_function") - def old_function(x, y): - return x + y - - def new_function(x, y): - return x + y - - # Usage - old_function(1, 2) # Emits DeprecationWarning - new_function(1, 2) # No warning - - # ------------------------------ - # Deprecated class - # ------------------------------ - @deprecated(alt_name="NewAtom") - class OldAtom: - def __init__(self, symbol): - self.symbol = symbol - - # Usage - a = OldAtom("C") # Emits DeprecationWarning - atom = NewAtom("C") # No warning + Matches the Python 3.13 warnings.deprecated API exactly. """ - if _builtin_deprecated: - return _builtin_deprecated + # If Python 3.13 implementation exists, delegate to it + if _builtin_deprecated is not None: + return _builtin_deprecated( + message, category=category, stacklevel=stacklevel + ) - def decorator(obj): - name = getattr(obj, "__name__", repr(obj)) - msg = message or ( - f"'{name}' is deprecated. Use '{alt_name}' instead." - if alt_name - else f"'{name}' is deprecated." + # Validate message type like Python 3.13 does + if not isinstance(message, str): + raise TypeError( + f"Expected an object of type str for 'message', not " + f"{type(message).__name__!r}" ) + def decorator(obj): + # Set __deprecated__ attribute (required by PEP 702) + setattr(obj, "__deprecated__", message) + + # Must support functions AND classes if callable(obj): @functools.wraps(obj) def wrapper(*args, **kwargs): - warnings.warn(msg, DeprecationWarning, stacklevel=2) + warnings.warn(message, category, stacklevel=stacklevel + 1) return obj(*args, **kwargs) return wrapper - else: - raise TypeError( - "deprecated decorator can only be applied to functions or " - "classes" - ) + + raise TypeError( + "deprecated decorator can only be applied to functions or classes" + ) return decorator From 1d936d77fc0ccb3acea09ac9047de94f0df75132 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 24 Nov 2025 11:00:53 -0500 Subject: [PATCH 425/445] comment --- src/diffpy/utils/_deprecator.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 55593c96..21dffa77 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -15,13 +15,10 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): Matches the Python 3.13 warnings.deprecated API exactly. """ - # If Python 3.13 implementation exists, delegate to it if _builtin_deprecated is not None: return _builtin_deprecated( message, category=category, stacklevel=stacklevel ) - - # Validate message type like Python 3.13 does if not isinstance(message, str): raise TypeError( f"Expected an object of type str for 'message', not " @@ -29,10 +26,7 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): ) def decorator(obj): - # Set __deprecated__ attribute (required by PEP 702) setattr(obj, "__deprecated__", message) - - # Must support functions AND classes if callable(obj): @functools.wraps(obj) From a3e4a108e7e26af380cc31acd62a21df2d4e4bdd Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Wed, 26 Nov 2025 11:00:51 -0500 Subject: [PATCH 426/445] fix: fix import to right file in examples --- docs/source/examples/parsers_example.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/examples/parsers_example.rst b/docs/source/examples/parsers_example.rst index 09220a3a..db97e0ed 100644 --- a/docs/source/examples/parsers_example.rst +++ b/docs/source/examples/parsers_example.rst @@ -69,7 +69,7 @@ returns an empty list. .. code-block:: python - from diffpy.utils.parsers.loaddata import serialize_data + from diffpy.utils.parsers.serialization import serialize_data file_data = serialize_data(' Date: Wed, 26 Nov 2025 11:06:49 -0500 Subject: [PATCH 427/445] chore: add news item --- news/docs-fix.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/docs-fix.rst diff --git a/news/docs-fix.rst b/news/docs-fix.rst new file mode 100644 index 00000000..2c81480c --- /dev/null +++ b/news/docs-fix.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: Fix import in examples + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From f5e4d439790691ee21cd2e99f36063e7f8333e8f Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Wed, 26 Nov 2025 16:27:15 -0500 Subject: [PATCH 428/445] fix: add real values into two entries --- docs/source/examples/diffraction_objects_example.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/examples/diffraction_objects_example.rst b/docs/source/examples/diffraction_objects_example.rst index 8794c719..6cc70a67 100644 --- a/docs/source/examples/diffraction_objects_example.rst +++ b/docs/source/examples/diffraction_objects_example.rst @@ -81,8 +81,9 @@ You could simply load them both as diffraction objects and plot them together on .. code-block:: python - calculated = DiffractionObject(xcalc, ycalc, "d") - measured = DiffractionObject(xmeas, ymeas, "tth", wavelength=0.717) + calculated = DiffractionObject(xcalc=[0.251436, 0.251542, 0.251647], ycalc=[-1.020020, -1.036460, -1.142070], "d") + measured = DiffractionObject(xmeas=[5.343089004093959198e-03, 5.343089004093959198e-03, 2.671544437772708711e-02], + ymeas=[3.533951950073242188e+01, 3.585629272460937500e+01, 3.611056518554687500e+01], "q", wavelength=0.717) plt.plot(calculated.on_q()[0], calculated.on_q()[1]) plt.plot(measured.on_q()[0], measured.on_q()[1]) plt.show() From e9c302e9f18d30c365d2d75a949d46f54ca5de92 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Wed, 26 Nov 2025 16:28:40 -0500 Subject: [PATCH 429/445] chore: add news item --- news/docs-add-data.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/docs-add-data.rst diff --git a/news/docs-add-data.rst b/news/docs-add-data.rst new file mode 100644 index 00000000..ad18e7a5 --- /dev/null +++ b/news/docs-add-data.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: Add real values into example + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From af8c0b1a165cb56077719315d7151643fd44f9cd Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 9 Dec 2025 14:46:08 -0500 Subject: [PATCH 430/445] news --- news/deprecated_update.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/deprecated_update.rst diff --git a/news/deprecated_update.rst b/news/deprecated_update.rst new file mode 100644 index 00000000..27fc85d7 --- /dev/null +++ b/news/deprecated_update.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news needed. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From d3c6e879c72fca57cbeed39ef3f09d3ac2ac225a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 9 Dec 2025 14:49:31 -0500 Subject: [PATCH 431/445] docstring --- src/diffpy/utils/_deprecator.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 21dffa77..a3c598de 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -11,10 +11,8 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): - """Compatibility wrapper for Python <3.13. - - Matches the Python 3.13 warnings.deprecated API exactly. - """ + """Deprecation decorator for functions and classes that is compatible with + Python versions prior to 3.13.""" if _builtin_deprecated is not None: return _builtin_deprecated( message, category=category, stacklevel=stacklevel From 3b7b13b50420cd25f3e30559fe204ea6d603125e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 9 Dec 2025 14:52:29 -0500 Subject: [PATCH 432/445] examples in docstring --- src/diffpy/utils/_deprecator.py | 44 ++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index a3c598de..91f3c0fb 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -12,7 +12,49 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): """Deprecation decorator for functions and classes that is compatible with - Python versions prior to 3.13.""" + Python versions prior to 3.13. + + Examples + -------- + Basic usage with a deprecated function: + + .. code-block:: python + + from diffpy._deprecations import deprecated + import warnings + + @deprecated("old_function is deprecated; use new_function instead") + def old_function(x, y): + return x + y + + def new_function(x, y): + return x + y + + old_function(1, 2) # Emits DeprecationWarning + new_function(1, 2) # No warning + + + Deprecating a class: + + .. code-block:: python + + from diffpy._deprecations import deprecated + import warnings + + warnings.simplefilter("always", DeprecationWarning) + + @deprecated("OldAtom is deprecated; use NewAtom instead") + class OldAtom: + def __init__(self, symbol): + self.symbol = symbol + + class NewAtom: + def __init__(self, symbol): + self.symbol = symbol + + a = OldAtom("C") # Emits DeprecationWarning + b = NewAtom("C") # No warning + """ if _builtin_deprecated is not None: return _builtin_deprecated( message, category=category, stacklevel=stacklevel From 804d7c3b1ee6f7de2e1e07b4b2ebc60b3d4b32b1 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Tue, 9 Dec 2025 16:28:30 -0500 Subject: [PATCH 433/445] fix: add instruction and corresponding files for the example --- .../examples/diffraction_objects_example.rst | 14 +- .../examples/example_data/CeO2_dspacing.xy | 2315 +++++++++++++ .../examples/example_data/CeO2_mean_q.chi | 3001 +++++++++++++++++ 3 files changed, 5325 insertions(+), 5 deletions(-) create mode 100644 docs/source/examples/example_data/CeO2_dspacing.xy create mode 100644 docs/source/examples/example_data/CeO2_mean_q.chi diff --git a/docs/source/examples/diffraction_objects_example.rst b/docs/source/examples/diffraction_objects_example.rst index 6cc70a67..14c44c01 100644 --- a/docs/source/examples/diffraction_objects_example.rst +++ b/docs/source/examples/diffraction_objects_example.rst @@ -75,15 +75,19 @@ i.e., This makes it very easy to compare a diffraction pattern that was measured or calculated on one ``xtype`` with one that was measured or calculated on another. E.g., suppose that you have a calculated powder pattern from a CIF file that was calculated on a d-spacing grid using -some software package, and -you want to know if a diffraction pattern you have measured on a Q-grid is the same material. +some software package, which you could find in the example data folder called `CeO2_dspacing.xy` and +you want to know if a diffraction pattern you have measured on a Q-grid, where the example data is also +in the example data folder called `CeO2_mean_q.chi`, is the same material. You could simply load them both as diffraction objects and plot them together on the same grid. +Here the `xcalc` is the first column in the `CeO2_dspacing.xy` and `ycalc` is the second column in the same +file. Similarly, `xmeas` corresponds to the first column in the `CeO2_mean_q.chi` and `ymeas` is the second column in +the same file. .. code-block:: python - calculated = DiffractionObject(xcalc=[0.251436, 0.251542, 0.251647], ycalc=[-1.020020, -1.036460, -1.142070], "d") - measured = DiffractionObject(xmeas=[5.343089004093959198e-03, 5.343089004093959198e-03, 2.671544437772708711e-02], - ymeas=[3.533951950073242188e+01, 3.585629272460937500e+01, 3.611056518554687500e+01], "q", wavelength=0.717) + calculated = DiffractionObject(xcalc, ycalc, "d") + measured = DiffractionObject(xmeas, + ymeas, "q", wavelength=0.1) plt.plot(calculated.on_q()[0], calculated.on_q()[1]) plt.plot(measured.on_q()[0], measured.on_q()[1]) plt.show() diff --git a/docs/source/examples/example_data/CeO2_dspacing.xy b/docs/source/examples/example_data/CeO2_dspacing.xy new file mode 100644 index 00000000..4785f90e --- /dev/null +++ b/docs/source/examples/example_data/CeO2_dspacing.xy @@ -0,0 +1,2315 @@ +7.218335 0.391564 +7.132923 0.370386 +7.049509 0.349966 +6.968024 0.329743 +6.888400 0.309479 +6.810576 0.289928 +6.734490 0.270571 +6.660086 0.251253 +6.587308 0.232565 +6.516103 0.214111 +6.446422 0.195715 +6.378214 0.177916 +6.311435 0.160339 +6.246021 0.142922 +6.181986 0.126044 +6.119251 0.109438 +6.057717 0.092990 +5.997466 0.077080 +5.938402 0.061451 +5.880489 0.045978 +5.823642 0.031012 +5.767936 0.016295 +5.713285 0.001721 +5.659610 -0.012304 +5.606983 -0.026099 +5.555326 -0.039828 +5.504565 -0.052906 +5.454769 -0.065756 +5.405867 -0.078608 +5.357834 -0.090725 +5.310602 -0.102664 +5.264239 -0.114745 +5.218679 -0.125908 +5.173858 -0.136968 +5.129843 -0.148309 +5.086570 -0.158575 +5.044021 -0.168832 +5.002138 -0.179372 +4.960984 -0.188845 +4.920502 -0.198336 +4.880637 -0.208117 +4.841450 -0.216812 +4.802887 -0.225712 +4.764898 -0.234709 +4.727541 -0.242872 +4.690765 -0.251246 +4.654556 -0.259696 +4.618868 -0.267483 +4.583757 -0.275518 +4.549176 -0.283485 +4.515080 -0.290860 +4.481523 -0.298452 +4.448462 -0.305873 +4.415884 -0.312760 +4.383750 -0.319821 +4.352110 -0.326670 +4.320924 -0.333017 +4.290152 -0.339568 +4.259844 -0.345951 +4.229962 -0.351786 +4.200468 -0.357837 +4.171409 -0.363754 +4.142750 -0.369075 +4.114483 -0.374698 +4.086571 -0.380270 +4.059063 -0.385138 +4.031922 -0.390302 +4.005116 -0.395460 +3.978689 -0.399810 +3.952609 -0.404509 +3.926868 -0.409280 +3.901436 -0.413232 +3.876356 -0.417557 +3.851596 -0.421917 +3.827127 -0.425455 +3.802990 -0.429475 +3.779155 -0.433467 +3.755595 -0.436673 +3.732349 -0.440473 +3.709389 -0.444102 +3.686710 -0.447042 +3.664285 -0.450705 +3.642152 -0.454059 +3.620285 -0.456761 +3.598658 -0.460185 +3.577309 -0.462992 +3.556211 -0.465360 +3.535341 -0.468228 +3.514734 -0.470431 +3.494366 -0.472010 +3.474233 -0.474049 +3.454311 -0.475013 +3.434636 -0.475180 +3.415183 -0.475268 +3.395931 -0.473697 +3.376913 -0.470006 +3.358107 -0.465152 +3.339509 -0.455154 +3.321098 -0.438147 +3.302907 -0.411786 +3.284913 -0.365345 +3.267098 -0.281368 +3.249492 -0.141094 +3.232075 0.132309 +3.214826 0.629427 +3.197778 1.434690 +3.180909 2.711830 +3.164217 4.415530 +3.147683 6.041780 +3.131338 7.012460 +3.115161 7.022290 +3.099135 6.011820 +3.083288 4.408110 +3.067602 2.705950 +3.052075 1.420620 +3.036690 0.593567 +3.021474 0.094493 +3.006409 -0.178329 +2.991480 -0.322551 +2.976713 -0.418517 +2.962090 -0.472643 +2.947597 -0.507672 +2.933258 -0.530855 +2.919058 -0.547876 +2.904995 -0.558779 +2.891054 -0.567202 +2.877259 -0.573437 +2.863595 -0.575618 +2.850047 -0.574917 +2.836640 -0.570439 +2.823358 -0.556533 +2.810187 -0.528418 +2.797151 -0.480731 +2.784236 -0.376463 +2.771439 -0.193399 +2.758747 0.116934 +2.746183 0.595629 +2.733733 1.208600 +2.721384 1.764630 +2.709157 2.076910 +2.697040 2.028750 +2.685030 1.634090 +2.673116 1.034660 +2.661318 0.436222 +2.649624 -0.004836 +2.638021 -0.286559 +2.626530 -0.462693 +2.615139 -0.559904 +2.603836 -0.615537 +2.592640 -0.651593 +2.581540 -0.674783 +2.570535 -0.690629 +2.559613 -0.703035 +2.548794 -0.713140 +2.538066 -0.720946 +2.527418 -0.728517 +2.516868 -0.735761 +2.506407 -0.741589 +2.496032 -0.747654 +2.485732 -0.754019 +2.475527 -0.759003 +2.465406 -0.764652 +2.455357 -0.770552 +2.445400 -0.775273 +2.435523 -0.780739 +2.425715 -0.786290 +2.415996 -0.790973 +2.406355 -0.796425 +2.396790 -0.801721 +2.387292 -0.806534 +2.377878 -0.811995 +2.368538 -0.817154 +2.359262 -0.821987 +2.350067 -0.827530 +2.340943 -0.832601 +2.331882 -0.837525 +2.322899 -0.843077 +2.313985 -0.848109 +2.305139 -0.852976 +2.296352 -0.858401 +2.287640 -0.863363 +2.278994 -0.868126 +2.270404 -0.873499 +2.261888 -0.878490 +2.253435 -0.883157 +2.245045 -0.888416 +2.236709 -0.893327 +2.228443 -0.897752 +2.220238 -0.902815 +2.212085 -0.907712 +2.204000 -0.912090 +2.195974 -0.917020 +2.187998 -0.921639 +2.180087 -0.925706 +2.172233 -0.930275 +2.164436 -0.934565 +2.156687 -0.938518 +2.149001 -0.943054 +2.141370 -0.947108 +2.133785 -0.950916 +2.126261 -0.955402 +2.118789 -0.959254 +2.111371 -0.963151 +2.103996 -0.967682 +2.096680 -0.971165 +2.089415 -0.974677 +2.082193 -0.979105 +2.075028 -0.981976 +2.067912 -0.985022 +2.060838 -0.988744 +2.053818 -0.990924 +2.046847 -0.993087 +2.039922 -0.995823 +2.033038 -0.996630 +2.026206 -0.996855 +2.019421 -0.996882 +2.012674 -0.994164 +2.005978 -0.989108 +1.999327 -0.981703 +1.992714 -0.967023 +1.986150 -0.943805 +1.979629 -0.906106 +1.973152 -0.840443 +1.966710 -0.727375 +1.960316 -0.531934 +1.953964 -0.158305 +1.947646 0.495551 +1.941376 1.615580 +1.935146 3.399880 +1.928955 5.730030 +1.922798 8.076660 +1.916686 9.710930 +1.910613 10.009000 +1.904573 8.762850 +1.898576 6.465530 +1.892617 4.017110 +1.886689 2.063250 +1.880805 0.743664 +1.874956 -0.055738 +1.869144 -0.481454 +1.863363 -0.727027 +1.857623 -0.873909 +1.851917 -0.958809 +1.846242 -1.013110 +1.840606 -1.049770 +1.835005 -1.074880 +1.829438 -1.092750 +1.823899 -1.107410 +1.818399 -1.118330 +1.812931 -1.127280 +1.807492 -1.135560 +1.802090 -1.142030 +1.796720 -1.147580 +1.791378 -1.153310 +1.786072 -1.157700 +1.780797 -1.161810 +1.775553 -1.166350 +1.770335 -1.169660 +1.765153 -1.172810 +1.760001 -1.176550 +1.754874 -1.179200 +1.749781 -1.181720 +1.744719 -1.184990 +1.739680 -1.187210 +1.734675 -1.188940 +1.729699 -1.191090 +1.724752 -1.191950 +1.719828 -1.191800 +1.714937 -1.191530 +1.710073 -1.189490 +1.705233 -1.185440 +1.700424 -1.179610 +1.695642 -1.169750 +1.690887 -1.154170 +1.686155 -1.130710 +1.681453 -1.091960 +1.676777 -1.028390 +1.672123 -0.924027 +1.667499 -0.735773 +1.662900 -0.399846 +1.658323 0.195974 +1.653774 1.268090 +1.649251 3.026370 +1.644753 5.422790 +1.640274 8.311170 +1.635824 10.768100 +1.631399 11.793900 +1.626993 10.982800 +1.622615 8.568240 +1.618260 5.766640 +1.613929 3.186140 +1.609616 1.341200 +1.605331 0.269659 +1.601069 -0.336168 +1.596825 -0.661840 +1.592607 -0.807221 +1.588412 -0.826728 +1.584235 -0.726604 +1.580083 -0.477472 +1.575954 -0.052714 +1.571846 0.495872 +1.567755 1.019980 +1.563689 1.346170 +1.559645 1.328280 +1.555617 0.965010 +1.551614 0.400290 +1.547632 -0.167374 +1.543670 -0.612469 +1.539725 -0.910709 +1.535803 -1.089710 +1.531901 -1.190970 +1.528016 -1.250180 +1.524153 -1.286660 +1.520311 -1.309300 +1.516483 -1.324670 +1.512679 -1.336500 +1.508894 -1.345480 +1.505128 -1.352560 +1.501376 -1.359120 +1.497647 -1.364510 +1.493937 -1.369260 +1.490241 -1.374180 +1.486567 -1.378300 +1.482912 -1.382340 +1.479270 -1.386650 +1.475650 -1.389990 +1.472048 -1.393620 +1.468463 -1.397470 +1.464892 -1.400070 +1.461342 -1.403180 +1.457809 -1.406900 +1.454290 -1.409090 +1.450791 -1.412050 +1.447308 -1.415730 +1.443843 -1.417830 +1.440391 -1.420720 +1.436958 -1.424210 +1.433542 -1.426040 +1.430139 -1.428670 +1.426755 -1.431550 +1.423387 -1.433260 +1.420032 -1.435580 +1.416695 -1.437490 +1.413375 -1.438580 +1.410070 -1.440150 +1.406777 -1.440620 +1.403502 -1.440570 +1.400243 -1.440670 +1.396996 -1.439020 +1.393767 -1.435740 +1.390553 -1.430770 +1.387354 -1.421030 +1.384166 -1.403940 +1.380996 -1.376270 +1.377840 -1.325890 +1.374696 -1.233610 +1.371569 -1.069430 +1.368456 -0.780739 +1.365355 -0.304776 +1.362270 0.362700 +1.359200 1.138740 +1.356143 1.834650 +1.353097 2.175630 +1.350067 2.011780 +1.347051 1.420970 +1.344046 0.634696 +1.341057 -0.095087 +1.338081 -0.651016 +1.335115 -1.004360 +1.332166 -1.206770 +1.329229 -1.321120 +1.326305 -1.385890 +1.323392 -1.423140 +1.320493 -1.446960 +1.317608 -1.462670 +1.314732 -1.471920 +1.311872 -1.479180 +1.309024 -1.484870 +1.306189 -1.487450 +1.303363 -1.489890 +1.300551 -1.492110 +1.297752 -1.492000 +1.294963 -1.492110 +1.292188 -1.491690 +1.289425 -1.489560 +1.286671 -1.487390 +1.283931 -1.483110 +1.281203 -1.476680 +1.278486 -1.468390 +1.275779 -1.454910 +1.273085 -1.434650 +1.270403 -1.404780 +1.267730 -1.355110 +1.265070 -1.271110 +1.262421 -1.129670 +1.259784 -0.877461 +1.257155 -0.411019 +1.254539 0.385198 +1.251935 1.675540 +1.249338 3.530830 +1.246755 5.648270 +1.244183 7.473220 +1.241618 8.390130 +1.239067 7.987870 +1.236526 6.434260 +1.233996 4.353390 +1.231473 2.422320 +1.228963 1.060420 +1.226464 0.323352 +1.223972 0.159026 +1.221492 0.527557 +1.219023 1.392570 +1.216561 2.635030 +1.214112 4.024160 +1.211672 5.072440 +1.209242 5.302750 +1.206820 4.627530 +1.204409 3.272480 +1.202008 1.795450 +1.199615 0.546544 +1.197233 -0.342175 +1.194860 -0.861614 +1.192497 -1.156180 +1.190141 -1.327480 +1.187797 -1.424240 +1.185462 -1.483280 +1.183134 -1.523100 +1.180817 -1.548790 +1.178509 -1.567380 +1.176208 -1.582180 +1.173918 -1.592490 +1.171637 -1.601710 +1.169365 -1.610670 +1.167100 -1.617240 +1.164845 -1.623560 +1.162599 -1.630260 +1.160360 -1.635140 +1.158131 -1.639940 +1.155911 -1.644940 +1.153699 -1.648340 +1.151494 -1.651480 +1.149299 -1.654450 +1.147113 -1.656210 +1.144933 -1.657680 +1.142763 -1.658040 +1.140601 -1.657010 +1.138446 -1.655270 +1.136301 -1.650960 +1.134163 -1.643040 +1.132034 -1.631380 +1.129911 -1.612350 +1.127797 -1.580380 +1.125692 -1.528840 +1.123592 -1.442120 +1.121503 -1.288320 +1.119421 -1.014350 +1.117344 -0.522385 +1.115278 0.345628 +1.113219 1.762510 +1.111167 3.804420 +1.109122 6.268800 +1.107085 8.557600 +1.105056 9.827060 +1.103033 9.565250 +1.101019 7.861330 +1.099012 5.415630 +1.097013 2.957880 +1.095019 1.090650 +1.093034 -0.107116 +1.091056 -0.813126 +1.089084 -1.201810 +1.087120 -1.417690 +1.085164 -1.547050 +1.083213 -1.626060 +1.081270 -1.674480 +1.079335 -1.707970 +1.077406 -1.730790 +1.075483 -1.746040 +1.073568 -1.757760 +1.071660 -1.764520 +1.069757 -1.768390 +1.067862 -1.768610 +1.065975 -1.762380 +1.064094 -1.748930 +1.062217 -1.725030 +1.060349 -1.681850 +1.058488 -1.604950 +1.056632 -1.469890 +1.054783 -1.227330 +1.052941 -0.779822 +1.051104 0.012156 +1.049275 1.302910 +1.047452 3.192750 +1.045636 5.489170 +1.043824 7.627500 +1.042020 8.902470 +1.040223 8.807070 +1.038430 7.338920 +1.036645 5.074930 +1.034865 2.796170 +1.033091 0.989055 +1.031324 -0.231767 +1.029563 -0.956643 +1.027808 -1.361260 +1.026057 -1.594130 +1.024314 -1.730070 +1.022577 -1.811800 +1.020844 -1.866610 +1.019119 -1.904180 +1.017400 -1.930170 +1.015686 -1.951160 +1.013976 -1.967190 +1.012274 -1.980500 +1.010578 -1.992790 +1.008885 -2.002140 +1.007200 -2.011170 +1.005520 -2.019610 +1.003845 -2.025400 +1.002176 -2.031930 +1.000514 -2.038800 +0.998856 -2.043860 +0.997203 -2.049370 +0.995556 -2.055190 +0.993915 -2.059230 +0.992278 -2.063760 +0.990648 -2.068300 +0.989023 -2.071570 +0.987404 -2.074940 +0.985788 -2.077210 +0.984179 -2.078900 +0.982575 -2.080810 +0.980975 -2.080720 +0.979382 -2.079640 +0.977794 -2.077600 +0.976209 -2.071980 +0.974631 -2.061830 +0.973059 -2.045310 +0.971491 -2.015870 +0.969927 -1.963900 +0.968369 -1.872130 +0.966816 -1.706160 +0.965267 -1.408060 +0.963725 -0.909593 +0.962187 -0.161827 +0.960654 0.797389 +0.959124 1.770790 +0.957601 2.465900 +0.956083 2.626200 +0.954568 2.174420 +0.953059 1.284200 +0.951555 0.258193 +0.950054 -0.610678 +0.948560 -1.216130 +0.947070 -1.601010 +0.945585 -1.816910 +0.944103 -1.932830 +0.942627 -1.999450 +0.941156 -2.035810 +0.939688 -2.054920 +0.938226 -2.064330 +0.936768 -2.064620 +0.935314 -2.059210 +0.933865 -2.046970 +0.932421 -2.024450 +0.930982 -1.988360 +0.929545 -1.930930 +0.928114 -1.835700 +0.926688 -1.672120 +0.925265 -1.387080 +0.923847 -0.874228 +0.922434 0.065748 +0.921025 1.602100 +0.919619 3.916420 +0.918218 6.892190 +0.916822 9.805050 +0.915429 11.763400 +0.914042 12.040600 +0.912658 10.599600 +0.911277 8.022060 +0.909902 5.370140 +0.908531 3.530140 +0.907165 2.768500 +0.905801 2.958040 +0.904442 3.700260 +0.903087 4.412830 +0.901736 4.587440 +0.900389 4.015880 +0.899047 2.828800 +0.897708 1.432140 +0.896372 0.179366 +0.895042 -0.759105 +0.893715 -1.358860 +0.892391 -1.706920 +0.891073 -1.909270 +0.889758 -2.027340 +0.888446 -2.097630 +0.887139 -2.144230 +0.885835 -2.175360 +0.884536 -2.197110 +0.883239 -2.213950 +0.881947 -2.226160 +0.880659 -2.235950 +0.879374 -2.244060 +0.878093 -2.249500 +0.876816 -2.254140 +0.875542 -2.256810 +0.874272 -2.256050 +0.873007 -2.252730 +0.871744 -2.245220 +0.870485 -2.230180 +0.869230 -2.205200 +0.867979 -2.163960 +0.866730 -2.090740 +0.865486 -1.960620 +0.864245 -1.728570 +0.863009 -1.312250 +0.861774 -0.578183 +0.860544 0.547392 +0.859318 2.137370 +0.858094 3.954730 +0.856874 5.452010 +0.855658 6.115170 +0.854445 5.675000 +0.853236 4.317370 +0.852030 2.483180 +0.850828 0.816455 +0.849628 -0.416977 +0.848432 -1.230220 +0.847240 -1.700220 +0.846050 -1.960710 +0.844865 -2.111440 +0.843683 -2.195130 +0.842504 -2.244400 +0.841328 -2.273520 +0.840155 -2.286730 +0.838986 -2.287700 +0.837819 -2.277180 +0.836657 -2.249780 +0.835498 -2.194560 +0.834341 -2.093750 +0.833188 -1.908350 +0.832038 -1.563170 +0.830892 -0.988176 +0.829747 -0.104998 +0.828607 1.078160 +0.827470 2.361610 +0.826335 3.412760 +0.825204 3.882800 +0.824076 3.634810 +0.822950 2.841470 +0.821829 1.902430 +0.820710 1.266960 +0.819595 1.150270 +0.818481 1.497000 +0.817372 2.036520 +0.816265 2.393340 +0.815161 2.285760 +0.814060 1.662360 +0.812963 0.708205 +0.811868 -0.269872 +0.810775 -1.074080 +0.809687 -1.641380 +0.808601 -1.989900 +0.807517 -2.180290 +0.806437 -2.293720 +0.805360 -2.358510 +0.804285 -2.398930 +0.803213 -2.426150 +0.802145 -2.442940 +0.801079 -2.455110 +0.800015 -2.465540 +0.798955 -2.470980 +0.797898 -2.475930 +0.796843 -2.479560 +0.795791 -2.479420 +0.794742 -2.479150 +0.793696 -2.477590 +0.792652 -2.473100 +0.791611 -2.463290 +0.790573 -2.444140 +0.789537 -2.409090 +0.788505 -2.345980 +0.787475 -2.233430 +0.786447 -2.034230 +0.785423 -1.720580 +0.784401 -1.287550 +0.783382 -0.782998 +0.782365 -0.334923 +0.781351 -0.095156 +0.780340 -0.152977 +0.779330 -0.473659 +0.778324 -0.951708 +0.777321 -1.438390 +0.776319 -1.824150 +0.775321 -2.082010 +0.774325 -2.236540 +0.773332 -2.317700 +0.772341 -2.354890 +0.771353 -2.368610 +0.770367 -2.364310 +0.769383 -2.344110 +0.768403 -2.305920 +0.767425 -2.240750 +0.766450 -2.128730 +0.765476 -1.935850 +0.764505 -1.588200 +0.763537 -0.974058 +0.762571 0.043572 +0.761607 1.615260 +0.760647 3.715820 +0.759687 5.998610 +0.758732 7.890970 +0.757778 8.800240 +0.756827 8.388930 +0.755877 6.922070 +0.754931 5.076470 +0.753987 3.554770 +0.753045 2.701210 +0.752105 2.431420 +0.751168 2.426570 +0.750234 2.253390 +0.749301 1.721280 +0.748371 0.878528 +0.747443 -0.070514 +0.746517 -0.871867 +0.745594 -1.471070 +0.744673 -1.853740 +0.743754 -2.068030 +0.742837 -2.189420 +0.741923 -2.260830 +0.741012 -2.298510 +0.740101 -2.321250 +0.739194 -2.334240 +0.738289 -2.337970 +0.737385 -2.337620 +0.736485 -2.331250 +0.735586 -2.319600 +0.734689 -2.302000 +0.733795 -2.273100 +0.732903 -2.226300 +0.732013 -2.164860 +0.731125 -2.056660 +0.730240 -1.852390 +0.729356 -1.473450 +0.728474 -0.789174 +0.727595 0.385379 +0.726718 2.144690 +0.725844 4.544120 +0.724970 7.184410 +0.724100 9.357850 +0.723231 10.359800 +0.722364 9.834470 +0.721499 7.949180 +0.720637 5.351160 +0.719776 2.830800 +0.718918 0.864216 +0.718062 -0.465378 +0.717208 -1.255600 +0.716355 -1.683670 +0.715505 -1.912110 +0.714657 -2.022570 +0.713810 -2.054990 +0.712966 -2.027070 +0.712124 -1.924460 +0.711284 -1.700990 +0.710445 -1.283350 +0.709609 -0.520504 +0.708775 0.792369 +0.707942 2.792180 +0.707112 5.477190 +0.706284 8.459850 +0.705457 10.930700 +0.704632 12.099000 +0.703810 11.539200 +0.702989 9.415280 +0.702170 6.435670 +0.701353 3.515200 +0.700538 1.233860 +0.699725 -0.311454 +0.698914 -1.243500 +0.698105 -1.755140 +0.697297 -2.050150 +0.696491 -2.223870 +0.695688 -2.327330 +0.694886 -2.397300 +0.694085 -2.446160 +0.693287 -2.478700 +0.692491 -2.506570 +0.691696 -2.525510 +0.690903 -2.538300 +0.690113 -2.553200 +0.689324 -2.563540 +0.688536 -2.573070 +0.687750 -2.584990 +0.686967 -2.589280 +0.686184 -2.594580 +0.685405 -2.598740 +0.684626 -2.597260 +0.683849 -2.595050 +0.683074 -2.583090 +0.682302 -2.553460 +0.681530 -2.501780 +0.680760 -2.402580 +0.679993 -2.237240 +0.679227 -2.003570 +0.678462 -1.709650 +0.677699 -1.426260 +0.676938 -1.239370 +0.676179 -1.210040 +0.675421 -1.355780 +0.674665 -1.619490 +0.673911 -1.916640 +0.673158 -2.175820 +0.672408 -2.361820 +0.671659 -2.477560 +0.670911 -2.541020 +0.670165 -2.566910 +0.669421 -2.571230 +0.668679 -2.557360 +0.667937 -2.519480 +0.667198 -2.450690 +0.666461 -2.321110 +0.665724 -2.089050 +0.664990 -1.701560 +0.664258 -1.093580 +0.663527 -0.268726 +0.662797 0.674892 +0.662069 1.570170 +0.661343 2.208460 +0.660617 2.496600 +0.659894 2.565580 +0.659173 2.672420 +0.658453 3.038140 +0.657734 3.660970 +0.657018 4.231170 +0.656303 4.398680 +0.655588 3.923050 +0.654876 2.838060 +0.654166 1.439570 +0.653456 0.102100 +0.652749 -0.941472 +0.652043 -1.644770 +0.651338 -2.062270 +0.650635 -2.299140 +0.649934 -2.429270 +0.649234 -2.503140 +0.648535 -2.540740 +0.647839 -2.557550 +0.647143 -2.566630 +0.646449 -2.560850 +0.645757 -2.547140 +0.645066 -2.518380 +0.644376 -2.462690 +0.643688 -2.370330 +0.643001 -2.197660 +0.642317 -1.888280 +0.641632 -1.351390 +0.640950 -0.438271 +0.640270 0.835431 +0.639590 2.413950 +0.638912 3.972790 +0.638236 5.020660 +0.637561 5.205300 +0.636887 4.481790 +0.636215 3.101430 +0.635545 1.477720 +0.634875 0.075114 +0.634207 -0.957736 +0.633541 -1.616330 +0.632876 -1.980570 +0.632212 -2.163100 +0.631550 -2.226880 +0.630889 -2.204630 +0.630229 -2.086250 +0.629571 -1.842620 +0.628914 -1.367690 +0.628256 -0.503795 +0.627603 0.801754 +0.626952 2.547170 +0.626302 4.452850 +0.625653 6.038610 +0.625000 6.886370 +0.624354 6.782980 +0.623709 5.855260 +0.623066 4.549840 +0.622418 3.339760 +0.621777 2.433040 +0.621138 1.771960 +0.620500 1.187340 +0.619857 0.522452 +0.619222 -0.213462 +0.618588 -0.926674 +0.617955 -1.533530 +0.617324 -1.968310 +0.616688 -2.247030 +0.616059 -2.412640 +0.615431 -2.506240 +0.614805 -2.560640 +0.614174 -2.590940 +0.613550 -2.609100 +0.612928 -2.618000 +0.612307 -2.616350 +0.611681 -2.605840 +0.611062 -2.576130 +0.610445 -2.520540 +0.609829 -2.418850 +0.609214 -2.229920 +0.608594 -1.909750 +0.607982 -1.384730 +0.607370 -0.624344 +0.606760 0.284606 +0.606146 1.231920 +0.605538 1.896150 +0.604932 2.063480 +0.604327 1.693470 +0.603717 0.915450 +0.603114 -0.051218 +0.602513 -0.928245 +0.601913 -1.573680 +0.601314 -1.986390 +0.600710 -2.199780 +0.600113 -2.277360 +0.599518 -2.248990 +0.598923 -2.117290 +0.598325 -1.819410 +0.597733 -1.266280 +0.597142 -0.332870 +0.596552 1.191810 +0.595964 3.203850 +0.595371 5.485900 +0.594785 7.601220 +0.594200 8.840130 +0.593616 9.021200 +0.593027 8.305810 +0.592446 7.219930 +0.591866 6.126080 +0.591286 5.346600 +0.590703 4.632130 +0.590126 3.772390 +0.589550 2.663790 +0.588975 1.355050 +0.588401 0.093469 +0.587823 -0.911609 +0.587252 -1.643330 +0.586682 -2.093770 +0.586113 -2.359830 +0.585539 -2.513030 +0.584972 -2.601730 +0.584406 -2.657660 +0.583841 -2.688380 +0.583272 -2.704900 +0.582710 -2.703370 +0.582148 -2.680620 +0.581588 -2.627430 +0.581029 -2.514710 +0.580465 -2.310110 +0.579908 -1.942890 +0.579352 -1.378620 +0.578797 -0.641363 +0.578237 0.213719 +0.577684 0.949522 +0.577133 1.347990 +0.576582 1.297260 +0.576027 0.795873 +0.575478 0.001515 +0.574930 -0.840506 +0.574384 -1.544660 +0.573838 -2.052530 +0.573289 -2.361690 +0.572745 -2.511360 +0.572203 -2.547500 +0.571661 -2.479130 +0.571116 -2.277790 +0.570576 -1.889320 +0.570038 -1.230470 +0.569501 -0.236128 +0.568959 1.001640 +0.568424 2.296300 +0.567889 3.326350 +0.567356 3.730250 +0.566824 3.361280 +0.566287 2.382080 +0.565757 1.090490 +0.565228 -0.221123 +0.564699 -1.245370 +0.564167 -1.937210 +0.563641 -2.374620 +0.563115 -2.608530 +0.562591 -2.745460 +0.562068 -2.820180 +0.561540 -2.865180 +0.561019 -2.893140 +0.560498 -2.907760 +0.559979 -2.918170 +0.559455 -2.919360 +0.558938 -2.915930 +0.558421 -2.909060 +0.557905 -2.890190 +0.557386 -2.863480 +0.556872 -2.811110 +0.556359 -2.720240 +0.555847 -2.555050 +0.555336 -2.275380 +0.554821 -1.837510 +0.554312 -1.191480 +0.553804 -0.415962 +0.553297 0.329634 +0.552786 0.902002 +0.552281 1.058130 +0.551776 0.760358 +0.551273 0.125162 +0.550765 -0.617280 +0.550264 -1.364610 +0.549763 -1.872020 +0.549263 -2.197320 +0.548764 -2.335700 +0.548261 -2.331220 +0.547764 -2.167020 +0.547268 -1.808530 +0.546773 -1.136310 +0.546274 -0.034987 +0.545780 1.489850 +0.545288 3.336300 +0.544796 5.199590 +0.544300 6.491620 +0.543810 6.995410 +0.543321 6.637020 +0.542833 5.632570 +0.542346 4.381170 +0.541855 3.222580 +0.541369 2.184240 +0.540885 1.284810 +0.540401 0.420936 +0.539913 -0.407044 +0.539431 -1.123040 +0.538950 -1.684100 +0.538470 -2.075350 +0.537985 -2.311720 +0.537507 -2.446560 +0.537029 -2.509600 +0.536552 -2.532080 +0.536076 -2.519180 +0.535596 -2.471910 +0.535122 -2.373990 +0.534648 -2.193760 +0.534175 -1.858790 +0.533699 -1.256640 +0.533228 -0.265271 +0.532758 1.236950 +0.532288 3.213720 +0.531820 5.331990 +0.531348 7.152100 +0.530881 8.133710 +0.530415 7.928210 +0.529949 6.620460 +0.529480 4.666260 +0.529017 2.558970 +0.528554 0.760343 +0.528092 -0.505518 +0.527626 -1.280430 +0.527166 -1.644170 +0.526706 -1.688510 +0.526248 -1.433570 +0.525790 -0.831591 +0.525328 0.193065 +0.524872 1.618970 +0.524416 3.281430 +0.523961 4.886310 +0.523503 6.018250 +0.523050 6.389920 +0.522597 6.020690 +0.522145 5.120270 +0.521690 3.994010 +0.521240 2.897000 +0.520791 1.899430 +0.520342 0.967846 +0.519895 0.099902 +0.519443 -0.717607 +0.518997 -1.404500 +0.518552 -1.924920 +0.518107 -2.286150 +0.517659 -2.506230 +0.517215 -2.640420 +0.516773 -2.720720 +0.516331 -2.768940 +0.515886 -2.805110 +0.515446 -2.827430 +0.515007 -2.846400 +0.514568 -2.859080 +0.514130 -2.869180 +0.513689 -2.879610 +0.513252 -2.882820 +0.512817 -2.888630 +0.512382 -2.893300 +0.511944 -2.891710 +0.511510 -2.895050 +0.511077 -2.887020 +0.510645 -2.875760 +0.510210 -2.862250 +0.509780 -2.835810 +0.509350 -2.804130 +0.508921 -2.753550 +0.508492 -2.672560 +0.508061 -2.542040 +0.507634 -2.305680 +0.507208 -1.905750 +0.506782 -1.262280 +0.506353 -0.349693 +0.505929 0.797531 +0.505506 2.025300 +0.505083 3.125500 +0.504661 3.969640 +0.504236 4.522060 +0.503816 4.841030 +0.503396 4.995400 +0.502977 4.912890 +0.502554 4.477400 +0.502137 3.647010 +0.501720 2.438280 +0.501303 1.093170 +0.500884 -0.148322 +0.500469 -1.135710 +0.500055 -1.801130 +0.499641 -2.216930 +0.499228 -2.442100 +0.498812 -2.557670 +0.498400 -2.606160 +0.497990 -2.593820 +0.497580 -2.527530 +0.497166 -2.369370 +0.496757 -2.059850 +0.496349 -1.535190 +0.495942 -0.707882 +0.495531 0.381660 +0.495125 1.614110 +0.494720 2.702940 +0.494315 3.335320 +0.493911 3.327810 +0.493503 2.679380 +0.493101 1.590640 +0.492698 0.389038 +0.492297 -0.689009 +0.491892 -1.462410 +0.491492 -1.930360 +0.491093 -2.120170 +0.490694 -2.067030 +0.490292 -1.774920 +0.489894 -1.229680 +0.489497 -0.442590 +0.489101 0.466116 +0.488705 1.319390 +0.488306 1.851510 +0.487912 1.912530 +0.487518 1.470310 +0.487125 0.666122 +0.486729 -0.276709 +0.486337 -1.141890 +0.485946 -1.810880 +0.485556 -2.259640 +0.485162 -2.531060 +0.484773 -2.679360 +0.484384 -2.762410 +0.483996 -2.805880 +0.483608 -2.828400 +0.483218 -2.841840 +0.482832 -2.842510 +0.482446 -2.837800 +0.482061 -2.822770 +0.481673 -2.792540 +0.481289 -2.744250 +0.480906 -2.658830 +0.480524 -2.521880 +0.480142 -2.322240 +0.479757 -2.055430 +0.479376 -1.764450 +0.478996 -1.500840 +0.478617 -1.334990 +0.478234 -1.312020 +0.477856 -1.421440 +0.477478 -1.618920 +0.477101 -1.829990 +0.476721 -1.984630 +0.476345 -2.024560 +0.475970 -1.893350 +0.475595 -1.529220 +0.475221 -0.810111 +0.474844 0.378581 +0.474471 2.102960 +0.474099 4.269870 +0.473727 6.542240 +0.473352 8.470690 +0.472982 9.602310 +0.472612 9.705800 +0.472242 8.892090 +0.471870 7.479180 +0.471502 5.826370 +0.471134 4.195180 +0.470767 2.703720 +0.470400 1.397470 +0.470031 0.262507 +0.469666 -0.645803 +0.469301 -1.326460 +0.468936 -1.785430 +0.468569 -2.060940 +0.468206 -2.216420 +0.467844 -2.282650 +0.467482 -2.282580 +0.467117 -2.215570 +0.466756 -2.041490 +0.466395 -1.717610 +0.466036 -1.180400 +0.465676 -0.389040 +0.465314 0.580476 +0.464956 1.599690 +0.464599 2.419680 +0.464242 2.809320 +0.463882 2.656020 +0.463526 2.003240 +0.463170 1.057930 +0.462816 0.076221 +0.462458 -0.739775 +0.462104 -1.269950 +0.461751 -1.478280 +0.461398 -1.353030 +0.461046 -0.893488 +0.460691 -0.072517 +0.460340 1.064860 +0.459990 2.367090 +0.459640 3.602100 +0.459287 4.513270 +0.458938 4.937040 +0.458590 4.864000 +0.458242 4.377570 +0.457891 3.644280 +0.457545 2.758090 +0.457198 1.814150 +0.456853 0.848905 +0.456507 -0.071874 +0.456159 -0.868480 +0.455815 -1.508080 +0.455472 -1.939340 +0.455128 -2.218470 +0.454783 -2.374790 +0.454441 -2.446240 +0.454099 -2.463830 +0.453758 -2.419040 +0.453417 -2.299990 +0.453074 -2.077760 +0.452735 -1.707370 +0.452395 -1.205110 +0.452057 -0.610868 +0.451716 -0.041384 +0.451378 0.338880 +0.451041 0.444697 +0.450705 0.220218 +0.450366 -0.244294 +0.450030 -0.821922 +0.449695 -1.357740 +0.449361 -1.748970 +0.449027 -1.939720 +0.448690 -1.898590 +0.448357 -1.614240 +0.448025 -1.072760 +0.447693 -0.284714 +0.447358 0.630872 +0.447027 1.533350 +0.446696 2.190630 +0.446366 2.475760 +0.446034 2.348340 +0.445704 1.885670 +0.445376 1.240450 +0.445048 0.523123 +0.444720 -0.164808 +0.444390 -0.793840 +0.444063 -1.336000 +0.443737 -1.768390 +0.443412 -2.098320 +0.443083 -2.311300 +0.442758 -2.433110 +0.442434 -2.480190 +0.442110 -2.453640 +0.441784 -2.356650 +0.441461 -2.136840 +0.441139 -1.732280 +0.440817 -1.063200 +0.440496 -0.061940 +0.440172 1.216230 +0.439851 2.578880 +0.439531 3.738230 +0.439212 4.356750 +0.438889 4.271310 +0.438571 3.498130 +0.438253 2.311580 +0.437935 0.993699 +0.437615 -0.111114 +0.437298 -0.838948 +0.436982 -1.115960 +0.436666 -0.896608 +0.436350 -0.209707 +0.436033 0.889054 +0.435718 2.252480 +0.435404 3.551350 +0.435090 4.450900 +0.434774 4.669080 +0.434462 4.148180 +0.434150 3.014020 +0.433838 1.600930 +0.433526 0.237992 +0.433213 -0.880384 +0.432902 -1.643570 +0.432592 -2.134970 +0.432283 -2.414920 +0.431971 -2.570330 +0.431662 -2.660550 +0.431354 -2.700430 +0.431046 -2.721710 +0.430736 -2.713350 +0.430429 -2.680030 +0.430122 -2.602130 +0.429816 -2.450120 +0.429511 -2.206440 +0.429203 -1.858720 +0.428898 -1.437030 +0.428594 -1.024590 +0.428290 -0.720181 +0.427984 -0.611111 +0.427681 -0.720680 +0.427378 -1.019450 +0.427076 -1.402660 +0.426771 -1.779340 +0.426470 -2.070190 +0.426169 -2.218750 +0.425869 -2.223970 +0.425569 -2.066600 +0.425267 -1.744480 +0.424967 -1.270970 +0.424669 -0.635456 +0.424370 0.124542 +0.424070 0.986634 +0.423772 1.892420 +0.423475 2.709870 +0.423179 3.288810 +0.422879 3.466730 +0.422584 3.157430 +0.422288 2.391810 +0.421993 1.364300 +0.421699 0.252772 +0.421402 -0.712392 +0.421108 -1.433140 +0.420815 -1.915970 +0.420522 -2.172480 +0.420227 -2.275380 +0.419934 -2.247970 +0.419643 -2.085410 +0.419351 -1.773260 +0.419058 -1.259480 +0.418767 -0.566734 +0.418477 0.218260 +0.418188 0.940855 +0.417898 1.414000 +0.417607 1.510180 +0.417318 1.214720 +0.417030 0.640559 +0.416743 -0.053192 +0.416452 -0.634455 +0.416166 -0.956743 +0.415879 -0.907795 +0.415593 -0.382874 +0.415307 0.609303 +0.415019 1.991590 +0.414734 3.584800 +0.414450 5.002200 +0.414166 5.933640 +0.413879 6.133630 +0.413596 5.585170 +0.413313 4.448950 +0.413031 3.067900 +0.412746 1.652830 +0.412464 0.435164 +0.412182 -0.516386 +0.411901 -1.231680 +0.411621 -1.720760 +0.411338 -2.062360 +0.411058 -2.273310 +0.410778 -2.391280 +0.410499 -2.446740 +0.410218 -2.432280 +0.409940 -2.360690 +0.409662 -2.206360 +0.409384 -1.957130 +0.409104 -1.630760 +0.408827 -1.256340 +0.408551 -0.928583 +0.408275 -0.714473 +0.407999 -0.675627 +0.407721 -0.802157 +0.407446 -1.045880 +0.407171 -1.299890 +0.406897 -1.451000 +0.406621 -1.401940 +0.406347 -1.025040 +0.406074 -0.248768 +0.405801 0.939839 +0.405526 2.485880 +0.405254 4.119860 +0.404983 5.579990 +0.404711 6.568730 +0.404440 6.918430 +0.404167 6.619930 +0.403897 5.798490 +0.403627 4.608330 +0.403358 3.272810 +0.403086 1.929500 +0.402817 0.682466 +0.402549 -0.350145 +0.402281 -1.164810 +0.402011 -1.726670 +0.401743 -2.077300 +0.401476 -2.262950 +0.401210 -2.294530 +0.400943 -2.200630 +0.400675 -1.942250 +0.400409 -1.512670 +0.400144 -0.929292 +0.399879 -0.247496 +0.399612 0.354555 +0.399348 0.761287 +0.399084 0.837322 +0.398821 0.572189 +0.398558 0.032599 +0.398293 -0.613099 +0.398030 -1.212570 +0.397768 -1.638590 +0.397506 -1.802630 +0.397243 -1.722090 +0.396982 -1.400540 +0.396721 -0.946250 +0.396461 -0.477878 +0.396198 -0.127203 +0.395938 -0.020933 +0.395679 -0.186647 +0.395420 -0.586836 +0.395161 -1.114610 +0.394901 -1.659440 +0.394643 -2.120150 +0.394385 -2.457190 +0.394128 -2.685350 +0.393868 -2.810680 +0.393612 -2.885590 +0.393355 -2.915280 +0.393100 -2.932080 +0.392841 -2.929180 +0.392586 -2.907680 +0.392331 -2.873770 +0.392077 -2.801330 +0.391822 -2.712850 +0.391566 -2.592430 +0.391312 -2.475120 +0.391059 -2.374280 +0.390806 -2.309940 +0.390551 -2.298260 +0.390299 -2.304310 +0.390047 -2.317720 +0.389795 -2.269370 +0.389541 -2.116460 +0.389290 -1.762280 +0.389040 -1.153350 +0.388789 -0.272432 +0.388539 0.855005 +0.388287 2.025000 +0.388038 3.097400 +0.387789 3.810120 +0.387540 4.077540 +0.387289 3.871280 +0.387041 3.267260 +0.386793 2.406480 +0.386546 1.437980 +0.386296 0.471584 +0.386049 -0.375531 +0.385803 -1.075440 +0.385557 -1.584010 +0.385311 -1.911940 +0.385063 -2.065230 +0.384818 -2.054810 +0.384573 -1.847330 +0.384328 -1.433180 +0.384081 -0.743040 +0.383837 0.160443 +0.383594 1.212010 +0.383350 2.201400 +0.383107 2.893200 +0.382862 3.137140 +0.382619 2.846320 +0.382377 2.145790 +0.382135 1.253730 +0.381892 0.412312 +0.381650 -0.138802 +0.381409 -0.275535 +0.381169 0.045115 +0.380926 0.749947 +0.380686 1.709110 +0.380446 2.654820 +0.380207 3.434280 +0.379968 3.819820 +0.379727 3.775410 +0.379488 3.324330 +0.379250 2.569000 +0.379012 1.662610 +0.378772 0.733320 +0.378535 -0.126438 +0.378298 -0.844998 +0.378061 -1.406570 +0.377822 -1.796430 +0.377586 -2.046830 +0.377350 -2.172000 +0.377115 -2.200680 +0.376880 -2.137790 +0.376642 -1.992230 +0.376408 -1.762750 +0.376173 -1.498730 +0.375939 -1.233760 +0.375703 -1.033730 +0.375470 -0.946908 +0.375237 -0.985990 +0.375004 -1.130390 +0.374769 -1.300550 +0.374536 -1.426130 +0.374304 -1.416620 +0.374073 -1.205230 +0.373841 -0.747801 +0.373608 -0.076518 +0.373377 0.756432 +0.373146 1.620160 +0.372916 2.368200 +0.372684 2.905990 +0.372454 3.120140 +0.372224 3.007040 +0.371995 2.578540 +0.371764 1.918160 +0.371535 1.127610 +0.371307 0.307854 +0.371079 -0.427737 +0.370851 -1.016580 +0.370622 -1.415260 +0.370394 -1.609700 +0.370167 -1.599750 +0.369941 -1.356850 +0.369712 -0.849970 +0.369486 -0.084191 +0.369260 0.891005 +0.369035 1.928660 +0.368809 2.811940 +0.368582 3.339160 +0.368358 3.364410 +0.368133 2.914590 +0.367909 2.118180 +0.367683 1.221520 +0.367459 0.480592 +0.367236 0.070824 +0.367013 0.080320 +0.366788 0.473917 +0.366565 1.097300 +0.366343 1.741140 +0.366121 2.190730 +0.365899 2.273680 +0.365676 1.960500 +0.365454 1.310950 +0.365234 0.487427 +0.365013 -0.340282 +0.364790 -1.050550 +0.364570 -1.572940 +0.364350 -1.912100 +0.364131 -2.106500 +0.363909 -2.197410 +0.363690 -2.205530 +0.363471 -2.140630 +0.363253 -1.987030 +0.363035 -1.731800 +0.362815 -1.360410 +0.362597 -0.901955 +0.362379 -0.410840 +0.362162 -0.000367 +0.361943 0.251308 +0.361726 0.284680 +0.361510 0.109164 +0.361294 -0.181939 +0.361076 -0.458273 +0.360860 -0.566522 +0.360645 -0.387050 +0.360429 0.154349 +0.360214 0.995083 +0.359998 2.026560 +0.359783 3.015110 +0.359569 3.749320 +0.359355 4.073170 +0.359140 3.908640 +0.358926 3.330900 +0.358713 2.454920 +0.358500 1.459590 +0.358286 0.486612 +0.358073 -0.371931 +0.357861 -1.061010 +0.357649 -1.565150 +0.357438 -1.899100 +0.357224 -2.096760 +0.357013 -2.175310 +0.356803 -2.162950 +0.356592 -2.052100 +0.356380 -1.876250 +0.356169 -1.650990 +0.355960 -1.445840 +0.355750 -1.289280 +0.355541 -1.238750 +0.355330 -1.294820 +0.355121 -1.425820 +0.354912 -1.578820 +0.354704 -1.674340 +0.354494 -1.664760 +0.354286 -1.481630 +0.354078 -1.135120 +0.353871 -0.624111 +0.353661 -0.051632 +0.353455 0.536468 +0.353248 1.024950 +0.353041 1.333880 +0.352835 1.421240 +0.352627 1.236720 +0.352422 0.848033 +0.352216 0.281790 +0.352011 -0.331397 +0.351804 -0.939046 +0.351599 -1.457830 +0.351395 -1.856810 +0.351191 -2.118740 +0.350985 -2.289800 +0.350781 -2.381080 +0.350577 -2.431020 +0.350374 -2.449030 +0.350171 -2.448790 +0.349966 -2.444520 +0.349763 -2.419160 +0.349561 -2.384590 +0.349359 -2.312060 +0.349155 -2.205630 +0.348953 -2.013660 +0.348752 -1.698250 +0.348551 -1.203940 +0.348348 -0.516518 +0.348147 0.363596 +0.347947 1.335730 +0.347746 2.276350 +0.347546 3.038400 +0.347345 3.476010 +0.347145 3.547080 +0.346946 3.229240 +0.346747 2.620220 +0.346546 1.815740 +0.346347 0.957054 +0.346149 0.163618 +0.345950 -0.490064 +0.345750 -0.962861 +0.345553 -1.223110 +0.345355 -1.257970 +0.345158 -1.048670 +0.344961 -0.603521 +0.344762 0.061608 +0.344565 0.853785 +0.344369 1.639850 +0.344173 2.258350 +0.343975 2.568880 +0.343779 2.518810 +0.343584 2.161540 +0.343388 1.665890 +0.343193 1.246310 +0.342997 1.109010 +0.342802 1.379990 +0.342608 2.044640 +0.342414 2.933970 +0.342218 3.806510 +0.342024 4.377010 +0.341830 4.466440 +0.341637 4.006870 +0.341442 3.104300 +0.341249 1.964670 +0.341057 0.820650 +0.340864 -0.172302 +0.340672 -0.919245 +0.340478 -1.428060 +0.340286 -1.734070 +0.340095 -1.904310 +0.339904 -1.995140 +0.339711 -2.035150 +0.339520 -2.045350 +0.339329 -2.021120 +0.339139 -1.986370 +0.338946 -1.939050 +0.338756 -1.899960 +0.338567 -1.859000 +0.338377 -1.830550 +0.338187 -1.777840 +0.337996 -1.677930 +0.337807 -1.464630 +0.337619 -1.101140 +0.337430 -0.537606 +0.337240 0.203112 +0.337052 1.085020 +0.336864 1.983440 +0.336676 2.772800 +0.336487 3.309480 +0.336300 3.504840 +0.336112 3.341440 +0.335926 2.831850 +0.335739 2.096860 +0.335551 1.240560 +0.335364 0.409713 +0.335178 -0.311638 +0.334992 -0.833173 +0.334805 -1.133660 +0.334620 -1.181340 +0.334434 -0.978296 +0.334249 -0.534621 +0.334063 0.085489 +0.333878 0.776903 +0.333694 1.382370 +0.333509 1.767800 +0.333325 1.837630 +0.333140 1.582800 +0.332956 1.080730 +0.332773 0.468127 +0.332590 -0.084811 +0.332405 -0.473991 +0.332222 -0.640277 +0.332040 -0.617750 +0.331857 -0.468851 +0.331675 -0.297231 +0.331491 -0.190269 +0.331310 -0.198557 +0.331128 -0.333111 +0.330947 -0.579871 +0.330764 -0.892361 +0.330583 -1.222460 +0.330402 -1.520190 +0.330221 -1.760000 +0.330039 -1.922480 +0.329859 -1.998250 +0.329679 -1.974530 +0.329499 -1.847610 +0.329319 -1.619420 +0.329138 -1.311060 +0.328959 -0.963634 +0.328780 -0.642515 +0.328601 -0.418867 +0.328421 -0.331877 +0.328243 -0.365678 +0.328064 -0.466802 +0.327886 -0.536899 +0.327707 -0.465413 +0.327529 -0.164357 +0.327352 0.395196 +0.327174 1.154580 +0.326997 2.006120 +0.326819 2.789420 +0.326642 3.349850 +0.326465 3.568230 +0.326289 3.396070 +0.326111 2.885620 +0.325935 2.120170 +0.325760 1.235750 +0.325584 0.357582 +0.325407 -0.426838 +0.325232 -1.025120 +0.325057 -1.440490 +0.324882 -1.634140 +0.324707 -1.661870 +0.324531 -1.517670 +0.324357 -1.279590 +0.324183 -1.001290 +0.324009 -0.765534 +0.323834 -0.624881 +0.323660 -0.608819 +0.323487 -0.714087 +0.323314 -0.887268 +0.323139 -1.037530 +0.322967 -1.100790 +0.322794 -1.024890 +0.322622 -0.829471 +0.322449 -0.564767 +0.322276 -0.326945 +0.322104 -0.207127 +0.321932 -0.256530 +0.321761 -0.468238 +0.321588 -0.805796 +0.321417 -1.195260 +0.321246 -1.570460 +0.321075 -1.871980 +0.320905 -2.086750 +0.320733 -2.212720 +0.320563 -2.255010 +0.320393 -2.214600 +0.320223 -2.103490 +0.320051 -1.944990 +0.319882 -1.736520 +0.319713 -1.492530 +0.319544 -1.262970 +0.319373 -1.098140 +0.319204 -1.019250 +0.319036 -1.013030 +0.318867 -1.037640 +0.318699 -1.024230 +0.318529 -0.908590 +0.318362 -0.637004 +0.318194 -0.216806 +0.318026 0.308162 +0.317858 0.831916 +0.317690 1.258290 +0.317523 1.489660 +0.317357 1.480320 +0.317188 1.234720 +0.317022 0.814220 +0.316856 0.296692 +0.316690 -0.228982 +0.316524 -0.686256 +0.316356 -1.010720 +0.316191 -1.166150 +0.316025 -1.113260 +0.315860 -0.846222 +0.315694 -0.349779 +0.315529 0.292168 +0.315364 1.020830 +0.315199 1.674860 +0.315033 2.149520 +0.314869 2.324310 +0.314705 2.219410 +0.314541 1.951120 +0.314378 1.710450 +0.314213 1.650730 +0.314049 1.908270 +0.313886 2.456810 +0.313723 3.205120 +0.313559 3.914160 +0.313396 4.404710 +0.313234 4.508250 +0.313071 4.194080 +0.312908 3.502270 +0.312746 2.574590 +0.312584 1.561360 +0.312422 0.612125 +0.312261 -0.176248 +0.312098 -0.781210 +0.311937 -1.194750 +0.311776 -1.438540 +0.311615 -1.541110 +0.311453 -1.539010 +0.311292 -1.475290 +0.311132 -1.384960 +0.310972 -1.305270 +0.310810 -1.253200 +0.310650 -1.231550 +0.310491 -1.227270 +0.310331 -1.207850 +0.310172 -1.128220 +0.310011 -0.936959 +0.309852 -0.612456 +0.309693 -0.164270 +0.309535 0.351013 +0.309375 0.867758 +0.309216 1.280970 +0.309058 1.519570 +0.308900 1.532090 +0.308742 1.306400 +0.308583 0.893757 +0.308426 0.363664 +0.308268 -0.193327 +0.308111 -0.697989 +0.307952 -1.090160 +0.307796 -1.332310 +0.307639 -1.417920 +0.307482 -1.350070 +0.307324 -1.190430 +0.307168 -0.976276 +0.307012 -0.785287 +0.306856 -0.638051 +0.306700 -0.574551 +0.306543 -0.561808 +0.306388 -0.556794 +0.306232 -0.465682 +0.306077 -0.245573 +0.305921 0.136170 +0.305766 0.610585 +0.305611 1.091950 +0.305457 1.425200 +0.305301 1.528420 +0.305147 1.360570 +0.304993 0.942743 +0.304839 0.363583 +0.304685 -0.265326 +0.304530 -0.843475 +0.304377 -1.305160 +0.304223 -1.632660 +0.304070 -1.834940 +0.303916 -1.928720 +0.303763 -1.931570 +0.303610 -1.872750 +0.303458 -1.776320 +0.303304 -1.657940 +0.303152 -1.544170 +0.303000 -1.456760 +0.302848 -1.398620 +0.302696 -1.343860 +0.302543 -1.255980 +0.302392 -1.082720 +0.302240 -0.796620 +0.302089 -0.376994 +0.301937 0.150104 +0.301786 0.739920 +0.301635 1.277740 +0.301485 1.685230 +0.301333 1.863030 +0.301183 1.811290 +0.301033 1.507620 +0.300883 1.033790 +0.300733 0.447449 +0.300582 -0.141520 +0.300433 -0.658952 +0.300283 -1.036180 +0.300134 -1.246930 +0.299984 -1.307230 +0.299835 -1.230390 +0.299686 -1.078980 +0.299537 -0.896737 +0.299389 -0.742503 +0.299239 -0.649337 +0.299091 -0.638359 +0.298943 -0.674424 +0.298795 -0.719851 +0.298646 -0.701020 +0.298499 -0.592329 +0.298351 -0.369715 +0.298204 -0.062452 +0.298055 0.277385 +0.297908 0.566574 +0.297762 0.753032 +0.297615 0.780844 +0.297468 0.648839 +0.297321 0.369498 +0.297174 0.001355 +0.297028 -0.393235 +0.296882 -0.751070 +0.296735 -1.030380 +0.296589 -1.186570 +0.296444 -1.213240 +0.296298 -1.137670 +0.296152 -0.996025 +0.296007 -0.807705 +0.295862 -0.623152 +0.295717 -0.482340 +0.295572 -0.398692 +0.295426 -0.329169 +0.295282 -0.242116 +0.295138 -0.055315 +0.294994 0.252086 +0.294848 0.707907 +0.294704 1.203490 +0.294561 1.707560 +0.294417 2.054710 +0.294272 2.208160 +0.294129 2.076040 +0.293986 1.749500 +0.293843 1.246340 +0.293700 0.697966 +0.293556 0.170336 +0.293413 -0.248395 +0.293271 -0.490819 +0.293129 -0.538455 +0.292985 -0.386080 +0.292843 -0.069112 +0.292701 0.368198 +0.292560 0.831510 +0.292417 1.236490 +0.292275 1.486560 +0.292134 1.555580 +0.291993 1.452060 +0.291852 1.260130 +0.291709 1.069030 +0.291569 0.956478 +0.291428 0.950570 +0.291287 1.025410 +0.291146 1.113740 +0.291005 1.134780 +0.290865 1.022540 +0.290725 0.774893 +0.290586 0.410285 +0.290444 -0.005209 +0.290305 -0.420923 +0.290165 -0.771826 +0.290026 -1.031430 +0.289886 -1.186390 +0.289747 -1.263190 +0.289608 -1.266670 +0.289469 -1.222800 +0.289329 -1.133490 +0.289191 -1.028400 +0.289052 -0.931153 +0.288914 -0.855828 +0.288776 -0.788041 +0.288637 -0.689466 +0.288499 -0.539620 +0.288361 -0.284233 +0.288223 0.087515 +0.288085 0.570364 +0.287947 1.124000 +0.287810 1.689870 +0.287673 2.172880 +0.287535 2.476510 +0.287398 2.545890 +0.287262 2.381240 +0.287125 2.005480 +0.286989 1.494910 +0.286851 0.954414 +0.286715 0.498674 +0.286579 0.192041 +0.286443 0.083057 +0.286306 0.176171 +0.286170 0.416096 +0.286035 0.745182 +0.285900 1.070530 +0.285763 1.299890 +0.285628 1.381480 +0.285493 1.309460 +0.285358 1.155620 +0.285223 0.981969 +0.285087 0.865786 +0.284953 0.865037 +0.284819 0.955754 +0.284684 1.095010 +0.284549 1.201000 +0.284415 1.209710 +0.284281 1.080090 +0.284148 0.818488 +0.284013 0.453608 +0.283879 0.046044 +0.283746 -0.361982 +0.283613 -0.716786 +0.283480 -1.001910 +0.283345 -1.215790 +0.283213 -1.361260 +0.283080 -1.452950 +0.282947 -1.503310 +0.282814 -1.518520 +0.282681 -1.489540 +0.282549 -1.404170 +0.282417 -1.234770 +0.282285 -0.958253 +0.282152 -0.549208 +0.282020 -0.012801 +0.281889 0.619442 +0.281757 1.269960 +0.281625 1.838390 +0.281493 2.212030 +0.281362 2.331190 +0.281231 2.178200 +0.281099 1.789230 +0.280968 1.241860 +0.280838 0.636035 +0.280707 0.088510 +0.280577 -0.352187 +0.280446 -0.631277 +0.280315 -0.740295 +0.280185 -0.676766 +0.280056 -0.495711 +0.279924 -0.235233 +0.279795 0.000829 +0.279665 0.152551 +0.279536 0.205051 +0.279405 0.142098 +0.279276 0.015605 +0.279147 -0.144039 +0.279018 -0.274831 +0.278890 -0.365013 +0.278760 -0.457882 +0.278631 -0.716285 +0.278503 -0.990560 +0.278374 -0.975798 +0.278245 -0.991576 +0.278117 -1.148440 +0.277989 -1.341860 +0.277861 -1.533450 +0.277732 -1.698110 +0.277604 -1.812080 +0.277477 -1.870870 +0.277349 -1.870180 +0.277222 -1.829050 +0.277094 -1.761790 +0.276967 -1.701670 +0.276840 -1.617620 +0.276713 -1.536070 +0.276585 -1.448250 +0.276459 -1.340110 +0.276332 -1.179020 +0.276206 -0.939642 +0.276078 -0.648901 +0.275952 -0.302726 +0.275826 0.037513 +0.275700 0.334390 +0.275575 0.496829 +0.275448 0.506565 +0.275322 0.378490 +0.275197 0.129260 +0.275072 -0.189910 +0.274945 -0.503486 +0.274820 -0.757848 +0.274695 -0.919310 +0.274570 -0.952724 +0.274446 -0.845720 +0.274320 -0.615887 +0.274195 -0.330519 +0.274071 -0.048172 +0.273947 0.189911 +0.273821 0.360597 +0.273697 0.468483 +0.273573 0.535321 +0.273449 0.636785 +0.273325 0.852649 +0.273201 1.185300 +0.273077 1.553760 +0.272954 1.882600 +0.272831 2.092880 +0.272707 2.122950 +0.272583 1.930220 +0.272461 1.560950 +0.272338 1.043770 +0.272214 0.480503 +0.272091 -0.068598 +0.271969 -0.544338 +0.271846 -0.889780 +0.271723 -1.111450 +0.271601 -1.214410 +0.271479 -1.258050 +0.271357 -1.250820 +0.271235 -1.216430 +0.271112 -1.186390 +0.270990 -1.159960 +0.270869 -1.121440 +0.270748 -1.039930 +0.270625 -0.902885 +0.270504 -0.679071 +0.270383 -0.370141 +0.270262 0.002275 +0.270140 0.365134 +0.270019 0.668894 +0.269899 0.851777 +0.269778 0.872481 +0.269658 0.730296 +0.269536 0.441438 +0.269416 0.084427 +0.269296 -0.291436 +0.269176 -0.597159 +0.269055 -0.814538 +0.268935 -0.945395 +0.268815 -0.977971 +0.268696 -0.968796 +0.268575 -0.904540 +0.268456 -0.845719 +0.268337 -0.771346 +0.268218 -0.678019 +0.268099 -0.543423 +0.267979 -0.353686 +0.267860 -0.148139 +0.267741 0.081377 +0.267622 0.259615 +0.267503 0.348687 +0.267384 0.310676 +0.267266 0.144393 +0.267148 -0.122534 +0.267030 -0.422795 +0.266911 -0.693419 +0.266793 -0.917406 +0.266675 -1.048430 +0.266557 -1.088900 +0.266439 -1.025640 +0.266321 -0.895473 +0.266204 -0.734740 +0.266087 -0.541843 +0.265968 -0.362913 +0.265851 -0.163725 +0.265734 -0.000693 +0.265618 0.194210 +0.265501 0.491866 +0.265383 0.864630 +0.265267 1.304330 +0.265150 1.741000 +0.265034 2.125620 +0.264917 2.363350 +0.264800 2.385520 +0.264684 2.185610 +0.264569 1.799840 +0.264452 1.310920 +0.264336 0.795538 +0.264220 0.319048 +0.264105 -0.073417 +0.263989 -0.328656 +0.263873 -0.441400 +0.263758 -0.440798 +0.263643 -0.379376 +0.263528 -0.313040 +0.263412 -0.253590 +0.263297 -0.241552 +0.263182 -0.263178 +0.263068 -0.297699 +0.262952 -0.332083 +0.262838 -0.309669 +0.262723 -0.257524 +0.262609 -0.173748 +0.262495 -0.070388 +0.262380 0.001438 +0.262266 0.027817 +0.262152 -0.012307 +0.262038 -0.121826 +0.261924 -0.272660 +0.261810 -0.434129 +0.261697 -0.581416 +0.261584 -0.674850 +0.261469 -0.720905 +0.261356 -0.731022 +0.261243 -0.707790 +0.261130 -0.649549 +0.261017 -0.576394 +0.260904 -0.511706 +0.260791 -0.419017 +0.260678 -0.274080 +0.260566 -0.099784 +0.260453 0.108958 +0.260340 0.358585 +0.260228 0.599138 +0.260116 0.813986 +0.260004 0.965053 +0.259891 1.033340 +0.259780 1.002440 +0.259668 0.883573 +0.259556 0.712921 +0.259444 0.546273 +0.259332 0.432851 +0.259221 0.415245 +0.259110 0.510877 +0.258998 0.707588 +0.258887 0.960425 +0.258776 1.219180 +0.258665 1.426050 +0.258555 1.532460 +0.258443 1.545890 +0.258332 1.503110 +0.258222 1.417160 +0.258112 1.300690 +0.258000 1.203930 +0.257890 1.099840 +0.257780 0.981915 +0.257670 0.851193 +0.257559 0.659772 +0.257450 0.448868 +0.257340 0.213842 +0.257230 -0.038387 +0.257121 -0.248771 +0.257010 -0.413391 +0.256901 -0.542009 +0.256792 -0.629132 +0.256683 -0.671440 +0.256573 -0.689430 +0.256464 -0.748888 +0.256355 -0.827158 +0.256246 -0.806314 +0.256137 -0.706977 +0.256028 -0.547498 +0.255920 -0.322463 +0.255811 -0.001369 +0.255703 0.364733 +0.255594 0.719000 +0.255486 1.003050 +0.255378 1.163900 +0.255270 1.173300 +0.255161 1.029950 +0.255053 0.796077 +0.254946 0.499562 +0.254838 0.190767 +0.254730 -0.052653 +0.254622 -0.217734 +0.254515 -0.285743 +0.254408 -0.256614 +0.254301 -0.167850 +0.254193 -0.070276 +0.254086 0.017324 +0.253979 0.110071 +0.253872 0.207980 +0.253765 0.322238 +0.253658 0.469009 +0.253552 0.628295 +0.253445 0.801130 +0.253339 0.952694 +0.253232 1.037630 +0.253126 1.004320 +0.253020 0.812639 +0.252914 0.503949 +0.252807 0.129866 +0.252701 -0.265739 +0.252595 -0.625930 +0.252490 -0.927273 +0.252383 -1.137990 +0.252278 -1.265580 +0.252173 -1.320580 +0.252067 -1.321520 +0.251962 -1.305900 +0.251856 -1.265590 +0.251751 -1.205560 +0.251647 -1.142070 +0.251542 -1.036460 +0.251436 -1.020020 diff --git a/docs/source/examples/example_data/CeO2_mean_q.chi b/docs/source/examples/example_data/CeO2_mean_q.chi new file mode 100644 index 00000000..29e330fb --- /dev/null +++ b/docs/source/examples/example_data/CeO2_mean_q.chi @@ -0,0 +1,3001 @@ +# chi_Q chi_I +5.343089004093959198e-03 3.533951950073242188e+01 +1.602926688373333686e-02 3.585629272460937500e+01 +2.671544437772708711e-02 3.611056518554687500e+01 +3.740162122897814495e-02 3.579096984863281250e+01 +4.808779718038944107e-02 3.584022903442382812e+01 +5.877397197486396163e-02 3.651346588134765625e+01 +6.946014535530466161e-02 3.680017471313476562e+01 +8.014631706461461391e-02 3.759931564331054688e+01 +9.083248684569683595e-02 3.781277465820312500e+01 +1.015186544414544700e-01 3.773645782470703125e+01 +1.122048195947906862e-01 3.811039733886718750e+01 +1.228909820486087101e-01 3.783448028564453125e+01 +1.335771415458117950e-01 3.763015747070312500e+01 +1.442632978293033053e-01 3.769110488891601562e+01 +1.549494506419867301e-01 3.813339996337890625e+01 +1.656355997267655866e-01 3.870289230346679688e+01 +1.763217448265434195e-01 4.036896514892578125e+01 +1.870078856842239956e-01 4.107411956787109375e+01 +1.976940220427111095e-01 4.212387084960937500e+01 +2.083801536449086389e-01 4.362857055664062500e+01 +2.190662802337206561e-01 4.639533233642578125e+01 +2.297524015520513163e-01 4.990507507324218750e+01 +2.404385173428048861e-01 5.699381637573242188e+01 +2.511246273488857983e-01 6.507775878906250000e+01 +2.618107313131984859e-01 7.904644012451171875e+01 +2.724968289786478537e-01 1.002316131591796875e+02 +2.831829200881385566e-01 1.291399841308593750e+02 +2.938690043845757494e-01 1.560103759765625000e+02 +3.045550816108644754e-01 2.098122558593750000e+02 +3.152411515099101669e-01 2.517750549316406250e+02 +3.259272138246183670e-01 3.066466369628906250e+02 +3.366132682978946189e-01 3.714101562500000000e+02 +3.472993146726448543e-01 4.163765258789062500e+02 +3.579853526917753381e-01 4.569295959472656250e+02 +3.686713820981921685e-01 5.115658569335937500e+02 +3.793574026348018324e-01 5.416533813476562500e+02 +3.900434140445111497e-01 5.803795166015625000e+02 +4.007294160702269403e-01 6.207738647460937500e+02 +4.114154084548564128e-01 6.476390380859375000e+02 +4.221013909413068865e-01 6.818058471679687500e+02 +4.327873632724860697e-01 7.151784057617187500e+02 +4.434733251913017815e-01 7.240357055664062500e+02 +4.541592764406621185e-01 7.490963745117187500e+02 +4.648452167634754550e-01 7.661317749023437500e+02 +4.755311459026504428e-01 7.677396240234375000e+02 +4.862170636010959557e-01 7.839503173828125000e+02 +4.969029696017212561e-01 7.960452270507812500e+02 +5.075888636474356064e-01 7.922737426757812500e+02 +5.182747454811489352e-01 8.061403808593750000e+02 +5.289606148457712820e-01 8.148671875000000000e+02 +5.396464714842129640e-01 8.096653442382812500e+02 +5.503323151393845203e-01 8.216730957031250000e+02 +5.610181455541970452e-01 8.275275878906250000e+02 +5.717039624715616331e-01 8.222787475585937500e+02 +5.823897656343903773e-01 8.312442016601562500e+02 +5.930755547855944831e-01 8.332574462890625000e+02 +6.037613296680867103e-01 8.294469604492187500e+02 +6.144470900247794853e-01 8.359780883789062500e+02 +6.251328355985859009e-01 8.359211425781250000e+02 +6.358185661324191607e-01 8.319819335937500000e+02 +6.465042813691931345e-01 8.373393554687500000e+02 +6.571899810518215812e-01 8.358358764648437500e+02 +6.678756649232191478e-01 8.327212524414062500e+02 +6.785613327263005923e-01 8.370446777343750000e+02 +6.892469842039808947e-01 8.334761352539062500e+02 +6.999326190991759233e-01 8.317766113281250000e+02 +7.106182371548015464e-01 8.352678833007812500e+02 +7.213038381137744093e-01 8.300540771484375000e+02 +7.319894217190106023e-01 8.293782348632812500e+02 +7.426749877134279920e-01 8.322225341796875000e+02 +7.533605358399440011e-01 8.265166015625000000e+02 +7.640460658414769402e-01 8.266762695312500000e+02 +7.747315774609447869e-01 8.292753295898437500e+02 +7.854170704412669624e-01 8.224727172851562500e+02 +7.961025445253626653e-01 8.236756591796875000e+02 +8.067879994561518719e-01 8.256440429687500000e+02 +8.174734349765547803e-01 8.183037109375000000e+02 +8.281588508294920326e-01 8.201004638671875000e+02 +8.388442467578850481e-01 8.213957519531250000e+02 +8.495296225046558014e-01 8.133092651367187500e+02 +8.602149778127260449e-01 8.153364257812500000e+02 +8.709003124250187522e-01 8.155524902343750000e+02 +8.815856260844568970e-01 8.079816284179687500e+02 +8.922709185339644522e-01 8.104735717773437500e+02 +9.029561895164656127e-01 8.104391479492187500e+02 +9.136414387748850174e-01 8.043567504882812500e+02 +9.243266660521479716e-01 8.066055908203125000e+02 +9.350118710911802244e-01 8.063153076171875000e+02 +9.456970536349083023e-01 8.013012084960937500e+02 +9.563822134262587316e-01 8.036494140625000000e+02 +9.670673502081590378e-01 8.023903808593750000e+02 +9.777524637235373017e-01 7.982675170898437500e+02 +9.884375537153220481e-01 8.003577880859375000e+02 +9.991226199264423569e-01 7.986368408203125000e+02 +1.009807662099827752e+00 7.954224853515625000e+02 +1.020492679978408423e+00 7.975910644531250000e+02 +1.031177673305115450e+00 7.954527587890625000e+02 +1.041862641822880020e+00 7.933936157226562500e+02 +1.052547585274634434e+00 7.958038940429687500e+02 +1.063232503403310991e+00 7.935961303710937500e+02 +1.073917395951843101e+00 7.923787841796875000e+02 +1.084602262663164840e+00 7.948920288085937500e+02 +1.095287103280210061e+00 7.917245483398437500e+02 +1.105971917545914174e+00 7.915021362304687500e+02 +1.116656705203213029e+00 7.940468750000000000e+02 +1.127341465995042924e+00 7.903366088867187500e+02 +1.138026199664340821e+00 7.908435668945312500e+02 +1.148710905954044570e+00 7.935567626953125000e+02 +1.159395584607092022e+00 7.891414794921875000e+02 +1.170080235366422583e+00 7.907683105468750000e+02 +1.180764857974975657e+00 7.931762084960937500e+02 +1.191449452175691981e+00 7.883082885742187500e+02 +1.202134017711511849e+00 7.908236694335937500e+02 +1.212818554325377773e+00 7.932772827148437500e+02 +1.223503061760232047e+00 7.883348388671875000e+02 +1.234187539759016738e+00 7.916812744140625000e+02 +1.244871988064677470e+00 7.934884033203125000e+02 +1.255556406420156978e+00 7.894774780273437500e+02 +1.266240794568401773e+00 7.928322753906250000e+02 +1.276925152252357032e+00 7.940992431640625000e+02 +1.287609479214969710e+00 7.908642578125000000e+02 +1.298293775199187650e+00 7.944114990234375000e+02 +1.308978039947958694e+00 7.947002563476562500e+02 +1.319662273204231795e+00 7.920859985351562500e+02 +1.330346474710956794e+00 7.950556030273437500e+02 +1.341030644211084200e+00 7.937648925781250000e+02 +1.351714781447564517e+00 7.909371948242187500e+02 +1.362398886163350920e+00 7.925008544921875000e+02 +1.373082958101395024e+00 7.895103759765625000e+02 +1.383766997004651556e+00 7.864347534179687500e+02 +1.394451002616073909e+00 7.874502563476562500e+02 +1.405134974678617921e+00 7.833190917968750000e+02 +1.415818912935239648e+00 7.810859375000000000e+02 +1.426502817128895373e+00 7.815460815429687500e+02 +1.437186687002542929e+00 7.771553955078125000e+02 +1.447870522299140816e+00 7.757736206054687500e+02 +1.458554322761648425e+00 7.764435424804687500e+02 +1.469238088133025588e+00 7.715352172851562500e+02 +1.479921818156233249e+00 7.710051879882812500e+02 +1.490605512574233682e+00 7.716523437500000000e+02 +1.501289171129989164e+00 7.666708374023437500e+02 +1.511972793566463524e+00 7.672438354492187500e+02 +1.522656379626620815e+00 7.676915283203125000e+02 +1.533339929053426198e+00 7.624107055664062500e+02 +1.544023441589845946e+00 7.637574462890625000e+02 +1.554706916978847664e+00 7.639237670898437500e+02 +1.565390354963398734e+00 7.592135620117187500e+02 +1.576073755286467870e+00 7.614262695312500000e+02 +1.586757117691025343e+00 7.616248779296875000e+02 +1.597440441920041421e+00 7.579816284179687500e+02 +1.608123727716487927e+00 7.604234619140625000e+02 +1.618806974823337130e+00 7.603114624023437500e+02 +1.629490182983562407e+00 7.578605957031250000e+02 +1.640173351940138691e+00 7.607775268554687500e+02 +1.650856481436040246e+00 7.603922119140625000e+02 +1.661539571214244448e+00 7.589763793945312500e+02 +1.672222621017727340e+00 7.619127807617187500e+02 +1.682905630589468737e+00 7.613222656250000000e+02 +1.693588599672446016e+00 7.608853759765625000e+02 +1.704271528009640546e+00 7.637795410156250000e+02 +1.714954415344032590e+00 7.627802124023437500e+02 +1.725637261418604851e+00 7.630223999023437500e+02 +1.736320065976340032e+00 7.661149291992187500e+02 +1.747002828760222393e+00 7.651450805664062500e+02 +1.757685549513237078e+00 7.670209960937500000e+02 +1.768368227978369900e+00 7.707496337890625000e+02 +1.779050863898608448e+00 7.708610229492187500e+02 +1.789733457016940532e+00 7.747752685546875000e+02 +1.800416007076355074e+00 7.806038208007812500e+02 +1.811098513819842548e+00 7.827476196289062500e+02 +1.821780976990394318e+00 7.910405273437500000e+02 +1.832463396331001970e+00 8.009752197265625000e+02 +1.843145771584659975e+00 8.090477905273437500e+02 +1.853828102494362140e+00 8.260147094726562500e+02 +1.864510388803103602e+00 8.487339477539062500e+02 +1.875192630253881720e+00 8.721124267578125000e+02 +1.885874826589693631e+00 9.210285034179687500e+02 +1.896556977553537804e+00 9.886934204101562500e+02 +1.907239082888414483e+00 1.089866821289062500e+03 +1.917921142337324358e+00 1.274013549804687500e+03 +1.928603155643269451e+00 1.597372314453125000e+03 +1.939285122549253337e+00 2.112072021484375000e+03 +1.949967042798279149e+00 3.247780761718750000e+03 +1.960648916133353126e+00 5.071626464843750000e+03 +1.971330742297481731e+00 8.052325683593750000e+03 +1.982012521033672092e+00 1.265926464843750000e+04 +1.992694252084933115e+00 1.765331445312500000e+04 +2.003375935194275037e+00 2.099921289062500000e+04 +2.014057570104708983e+00 2.176243554687500000e+04 +2.024739156559246300e+00 1.933409375000000000e+04 +2.035420694300901445e+00 1.498183398437500000e+04 +2.046102183072687986e+00 1.006079785156250000e+04 +2.056783622617622598e+00 6.320898925781250000e+03 +2.067465012678721958e+00 3.946736816406250000e+03 +2.078146352999004076e+00 2.533163085937500000e+03 +2.088827643321487404e+00 1.776117919921875000e+03 +2.099508883389193503e+00 1.392275634765625000e+03 +2.110190072945143491e+00 1.142712646484375000e+03 +2.120871211732361150e+00 1.007680053710937500e+03 +2.131552299493869818e+00 9.251745605468750000e+02 +2.142233335972695052e+00 8.740640869140625000e+02 +2.152914320911864188e+00 8.385794067382812500e+02 +2.163595254054404116e+00 8.200810546875000000e+02 +2.174276135143343502e+00 8.076011352539062500e+02 +2.184956963921714568e+00 8.000727539062500000e+02 +2.195637740132547311e+00 8.041757202148437500e+02 +2.206318463518874839e+00 8.147222900390625000e+02 +2.216999133823731594e+00 8.339907836914062500e+02 +2.227679750790153346e+00 8.796899414062500000e+02 +2.238360314161175868e+00 9.606395263671875000e+02 +2.249040823679838486e+00 1.089637573242187500e+03 +2.259721279089179635e+00 1.379220092773437500e+03 +2.270401680132239086e+00 1.858270019531250000e+03 +2.281082026552060604e+00 2.670435546875000000e+03 +2.291762318091686179e+00 3.884877685546875000e+03 +2.302442554494160909e+00 5.333493164062500000e+03 +2.313122735502530336e+00 6.402265136718750000e+03 +2.323802860859840891e+00 6.774020019531250000e+03 +2.334482930309142557e+00 6.233157226562500000e+03 +2.345162943593484428e+00 5.034031738281250000e+03 +2.355842900455918265e+00 3.590260253906250000e+03 +2.366522800639495383e+00 2.433910888671875000e+03 +2.377202643887271538e+00 1.724281616210937500e+03 +2.387882429942300710e+00 1.268792602539062500e+03 +2.398562158547639989e+00 1.025280395507812500e+03 +2.409241829446347349e+00 8.989053344726562500e+02 +2.419921442381482546e+00 8.213082275390625000e+02 +2.430600997096106219e+00 7.753579101562500000e+02 +2.441280493333280788e+00 7.489624633789062500e+02 +2.451959930836070001e+00 7.311651611328125000e+02 +2.462639309347538497e+00 7.174228515625000000e+02 +2.473318628610753578e+00 7.104360961914062500e+02 +2.483997888368782103e+00 7.040582885742187500e+02 +2.494677088364694040e+00 6.973093261718750000e+02 +2.505356228341561131e+00 6.948115234375000000e+02 +2.516035308042452900e+00 6.918329467773437500e+02 +2.526714327210445532e+00 6.873784179687500000e+02 +2.537393285588613434e+00 6.868483886718750000e+02 +2.548072182920033235e+00 6.847408447265625000e+02 +2.558751018947782896e+00 6.817982177734375000e+02 +2.569429793414942154e+00 6.817931518554687500e+02 +2.580108506064591634e+00 6.801025390625000000e+02 +2.590787156639814182e+00 6.780816040039062500e+02 +2.601465744883694420e+00 6.783452758789062500e+02 +2.612144270539316082e+00 6.766311035156250000e+02 +2.622822733349768676e+00 6.754150390625000000e+02 +2.633501133058138599e+00 6.755415649414062500e+02 +2.644179469407516248e+00 6.738220825195312500e+02 +2.654857742140994681e+00 6.731515502929687500e+02 +2.665535951001665627e+00 6.733184814453125000e+02 +2.676214095732624365e+00 6.713435668945312500e+02 +2.686892176076966177e+00 6.712967529296875000e+02 +2.697570191777789450e+00 6.711826782226562500e+02 +2.708248142578193907e+00 6.692199096679687500e+02 +2.718926028221279712e+00 6.695454711914062500e+02 +2.729603848450149250e+00 6.693747558593750000e+02 +2.740281603007907574e+00 6.675477294921875000e+02 +2.750959291637658399e+00 6.682468872070312500e+02 +2.761636914082509886e+00 6.680734252929687500e+02 +2.772314470085571525e+00 6.663165893554687500e+02 +2.782991959389953252e+00 6.672682495117187500e+02 +2.793669381738765889e+00 6.671719360351562500e+02 +2.804346736875123813e+00 6.656593627929687500e+02 +2.815024024542143177e+00 6.670789184570312500e+02 +2.825701244482939689e+00 6.671594238281250000e+02 +2.836378396440631278e+00 6.659552001953125000e+02 +2.847055480158338980e+00 6.674028930664062500e+02 +2.857732495379184723e+00 6.672885131835937500e+02 +2.868409441846290875e+00 6.668595581054687500e+02 +2.879086319302782915e+00 6.686034545898437500e+02 +2.889763127491787653e+00 6.688637084960937500e+02 +2.900439866156434121e+00 6.692627563476562500e+02 +2.911116535039850017e+00 6.709934082031250000e+02 +2.921793133885169258e+00 6.711219482421875000e+02 +2.932469662435524871e+00 6.720301513671875000e+02 +2.943146120434051660e+00 6.738054199218750000e+02 +2.953822507623885318e+00 6.739323730468750000e+02 +2.964498823748165979e+00 6.752171020507812500e+02 +2.975175068550033330e+00 6.766104125976562500e+02 +2.985851241772628839e+00 6.766295776367187500e+02 +2.996527343159096191e+00 6.784760742187500000e+02 +3.007203372452581291e+00 6.802971191406250000e+02 +3.017879329396231380e+00 6.804688720703125000e+02 +3.028555213733193696e+00 6.831763305664062500e+02 +3.039231025206619918e+00 6.854785156250000000e+02 +3.049906763559663059e+00 6.863349609375000000e+02 +3.060582428535476129e+00 6.899279174804687500e+02 +3.071258019877214807e+00 6.930741577148437500e+02 +3.081933537328037431e+00 6.949030761718750000e+02 +3.092608980631103233e+00 7.004067993164062500e+02 +3.103284349529574104e+00 7.059667358398437500e+02 +3.113959643766610608e+00 7.113367309570312500e+02 +3.124634863085379965e+00 7.218494873046875000e+02 +3.135310007229048068e+00 7.345906372070312500e+02 +3.145985075940783027e+00 7.502580566406250000e+02 +3.156660068963755172e+00 7.784248657226562500e+02 +3.167334986041136613e+00 8.166553344726562500e+02 +3.178009826916100344e+00 8.776434326171875000e+02 +3.188684591331823359e+00 9.821945800781250000e+02 +3.199359279031483094e+00 1.157652221679687500e+03 +3.210033889758257875e+00 1.460894653320312500e+03 +3.220708423255329578e+00 2.066882080078125000e+03 +3.231382879265880526e+00 3.062988281250000000e+03 +3.242057257533096593e+00 4.821234375000000000e+03 +3.252731557800164097e+00 7.448486328125000000e+03 +3.263405779810272467e+00 1.045888378906250000e+04 +3.274079923306611128e+00 1.291577148437500000e+04 +3.284753988032373062e+00 1.393179785156250000e+04 +3.295427973730752580e+00 1.294199121093750000e+04 +3.306101880144946215e+00 1.031859179687500000e+04 +3.316775707018151387e+00 7.246994140625000000e+03 +3.327449454093569514e+00 4.726026367187500000e+03 +3.338123121114401126e+00 3.020931884765625000e+03 +3.348796707823850305e+00 1.983898315429687500e+03 +3.359470213965123797e+00 1.448760131835937500e+03 +3.370143639281428793e+00 1.148870117187500000e+03 +3.380816983515976037e+00 9.732468261718750000e+02 +3.391490246411975384e+00 8.756717529296875000e+02 +3.402163427712641131e+00 8.162207031250000000e+02 +3.412836527161189348e+00 7.778659057617187500e+02 +3.423509544500837887e+00 7.530654296875000000e+02 +3.434182479474805483e+00 7.370991210937500000e+02 +3.444855331826313982e+00 7.246820678710937500e+02 +3.455528101298587451e+00 7.163761596679687500e+02 +3.466200787634849512e+00 7.105205688476562500e+02 +3.476873390578330003e+00 7.051766357421875000e+02 +3.487545909872256988e+00 7.019453735351562500e+02 +3.498218345259863415e+00 6.997832031250000000e+02 +3.508890696484380456e+00 6.971002807617187500e+02 +3.519562963289045943e+00 6.962108764648437500e+02 +3.530235145417096820e+00 6.954377441406250000e+02 +3.540907242611772254e+00 6.938466186523437500e+02 +3.551579254616314074e+00 6.941666870117187500e+02 +3.562251181173966330e+00 6.942121582031250000e+02 +3.572923022027975293e+00 6.932670898437500000e+02 +3.583594776921586789e+00 6.943505859375000000e+02 +3.594266445598052861e+00 6.948228759765625000e+02 +3.604938027800624223e+00 6.941513061523437500e+02 +3.615609523272556469e+00 6.957863159179687500e+02 +3.626280931757103865e+00 6.969299316406250000e+02 +3.636952252997526003e+00 6.973308105468750000e+02 +3.647623486737082477e+00 7.005484008789062500e+02 +3.658294632719036432e+00 7.034169311523437500e+02 +3.668965690686651460e+00 7.063652343750000000e+02 +3.679636660383195146e+00 7.126078491210937500e+02 +3.690307541551935966e+00 7.194368896484375000e+02 +3.700978333936145503e+00 7.287960205078125000e+02 +3.711649037279095342e+00 7.435733642578125000e+02 +3.722319651324061951e+00 7.636727905273437500e+02 +3.732990175814322242e+00 7.937940673828125000e+02 +3.743660610493156238e+00 8.452849731445312500e+02 +3.754330955103844847e+00 9.227620849609375000e+02 +3.765001209389672088e+00 1.061691040039062500e+03 +3.775671373093924643e+00 1.314595703125000000e+03 +3.786341445959889640e+00 1.757694213867187500e+03 +3.797011427730858202e+00 2.574094238281250000e+03 +3.807681318150122785e+00 4.017432128906250000e+03 +3.818351116960978953e+00 6.023881347656250000e+03 +3.829020823906721382e+00 8.577230468750000000e+03 +3.839690438730651856e+00 1.083893750000000000e+04 +3.850359961176069934e+00 1.184232226562500000e+04 +3.861029390986280507e+00 1.119395996093750000e+04 +3.871698727904588910e+00 9.078638671875000000e+03 +3.882367971674303142e+00 6.630632324218750000e+03 +3.893037122038733422e+00 4.399854980468750000e+03 +3.903706178741192634e+00 2.819866210937500000e+03 +3.914375141524995882e+00 1.919382324218750000e+03 +3.925044010133460048e+00 1.423726684570312500e+03 +3.935712784309904233e+00 1.165497436523437500e+03 +3.946381463797649758e+00 1.057260131835937500e+03 +3.957050048340021053e+00 1.052460205078125000e+03 +3.967718537680343882e+00 1.147743530273437500e+03 +3.978386931561947559e+00 1.370183227539062500e+03 +3.989055229728161844e+00 1.739488769531250000e+03 +3.999723431922320493e+00 2.195901855468750000e+03 +4.010391537887759483e+00 2.597444335937500000e+03 +4.021059547367814346e+00 2.798768798828125000e+03 +4.031727460105828165e+00 2.689036132812500000e+03 +4.042395275845140468e+00 2.313292236328125000e+03 +4.053062994329097890e+00 1.826465209960937500e+03 +4.063730615301046178e+00 1.398169311523437500e+03 +4.074398138504335520e+00 1.096313720703125000e+03 +4.085065563682316991e+00 9.104357299804687500e+02 +4.095732890578346108e+00 8.073331298828125000e+02 +4.106400118935776611e+00 7.513488159179687500e+02 +4.117067248497971121e+00 7.178878173828125000e+02 +4.127734279008286933e+00 6.982321777343750000e+02 +4.138401210210090220e+00 6.867126464843750000e+02 +4.149068041846746269e+00 6.784762573242187500e+02 +4.159734773661622143e+00 6.721734619140625000e+02 +4.170401405398091121e+00 6.685241699218750000e+02 +4.181067936799523821e+00 6.649102172851562500e+02 +4.191734367609297962e+00 6.620700683593750000e+02 +4.202400697570790378e+00 6.604096069335937500e+02 +4.213066926427382342e+00 6.581131591796875000e+02 +4.223733053922455127e+00 6.565463256835937500e+02 +4.234399079799395338e+00 6.553806152343750000e+02 +4.245065003801590464e+00 6.535380249023437500e+02 +4.255730825672430662e+00 6.526988525390625000e+02 +4.266396545155308750e+00 6.517832641601562500e+02 +4.277062161993619327e+00 6.503088989257812500e+02 +4.287727675930761428e+00 6.500792846679687500e+02 +4.298393086710133204e+00 6.495330810546875000e+02 +4.309058394075138132e+00 6.482205200195312500e+02 +4.319723597769181467e+00 6.483043212890625000e+02 +4.330388697535670239e+00 6.478114624023437500e+02 +4.341053693118014145e+00 6.466324462890625000e+02 +4.351718584259626432e+00 6.467595825195312500e+02 +4.362383370703923902e+00 6.463022460937500000e+02 +4.373048052194319801e+00 6.452699584960937500e+02 +4.383712628474238038e+00 6.456644897460937500e+02 +4.394377099287099853e+00 6.453466796875000000e+02 +4.405041464376330040e+00 6.447771606445312500e+02 +4.415705723485356948e+00 6.453111572265625000e+02 +4.426369876357609812e+00 6.451341552734375000e+02 +4.437033922736523195e+00 6.453332519531250000e+02 +4.447697862365529886e+00 6.462383422851562500e+02 +4.458361694988071555e+00 6.464781494140625000e+02 +4.469025420347585431e+00 6.477945556640625000e+02 +4.479689038187515848e+00 6.493012084960937500e+02 +4.490352548251308917e+00 6.504808959960937500e+02 +4.501015950282412525e+00 6.534041748046875000e+02 +4.511679244024275448e+00 6.571067504882812500e+02 +4.522342429220354454e+00 6.618771362304687500e+02 +4.533005505614104536e+00 6.707628784179687500e+02 +4.543668472948984238e+00 6.845731811523437500e+02 +4.554331330968454772e+00 7.058615112304687500e+02 +4.564994079415980899e+00 7.461163940429687500e+02 +4.575656718035028270e+00 8.178013305664062500e+02 +4.586319246569065200e+00 9.445160522460937500e+02 +4.596981664761566222e+00 1.167491333007812500e+03 +4.607643972356003204e+00 1.522064819335937500e+03 +4.618306169095856006e+00 1.969571655273437500e+03 +4.628968254724601827e+00 2.438584716796875000e+03 +4.639630228985725857e+00 2.747313964843750000e+03 +4.650292091622709734e+00 2.747208007812500000e+03 +4.660953842379045753e+00 2.451345947265625000e+03 +4.671615480998220882e+00 1.985130737304687500e+03 +4.682277007223730969e+00 1.531981689453125000e+03 +4.692938420799070087e+00 1.176854003906250000e+03 +4.703599721467737638e+00 9.504230346679687500e+02 +4.714260908973236575e+00 8.230952758789062500e+02 +4.724921983059068964e+00 7.529544067382812500e+02 +4.735582943468741313e+00 7.143572998046875000e+02 +4.746243789945766345e+00 6.934056396484375000e+02 +4.756904522233654120e+00 6.807571411132812500e+02 +4.767565140075919139e+00 6.727809448242187500e+02 +4.778225643216081231e+00 6.689298706054687500e+02 +4.788886031397658449e+00 6.662372436523437500e+02 +4.799546304364177729e+00 6.643004150390625000e+02 +4.810206461859161564e+00 6.643281860351562500e+02 +4.820866503626141331e+00 6.643848266601562500e+02 +4.831526429408648404e+00 6.644357910156250000e+02 +4.842186238950215049e+00 6.659909667968750000e+02 +4.852845931994381523e+00 6.672853393554687500e+02 +4.863505508284688972e+00 6.688287963867187500e+02 +4.874164967564673212e+00 6.714851684570312500e+02 +4.884824309577888712e+00 6.739413452148437500e+02 +4.895483534067879283e+00 6.777067260742187500e+02 +4.906142640778197617e+00 6.826550292968750000e+02 +4.916801629452397293e+00 6.884257202148437500e+02 +4.927460499834035446e+00 6.974104003906250000e+02 +4.938119251666672760e+00 7.100382080078125000e+02 +4.948777884693871698e+00 7.279213256835937500e+02 +4.959436398659196499e+00 7.578704833984375000e+02 +4.970094793306215841e+00 8.077319946289062500e+02 +4.980753068378505510e+00 8.910679321289062500e+02 +4.991411223619633297e+00 1.042447265625000000e+03 +5.002069258773180316e+00 1.325277587890625000e+03 +5.012727173582724127e+00 1.795562622070312500e+03 +5.023384967791850286e+00 2.554148925781250000e+03 +5.034042641144143460e+00 3.586247314453125000e+03 +5.044700193383190978e+00 4.618225585937500000e+03 +5.055357624252586390e+00 5.353215332031250000e+03 +5.066014933495925021e+00 5.464512207031250000e+03 +5.076672120856799530e+00 4.878016113281250000e+03 +5.087329186078815901e+00 3.879750000000000000e+03 +5.097986128905573011e+00 2.837323974609375000e+03 +5.108642949080681284e+00 2.059670410156250000e+03 +5.119299646347748478e+00 1.602059204101562500e+03 +5.129956220450385018e+00 1.435255004882812500e+03 +5.140612671132207545e+00 1.527768066406250000e+03 +5.151268998136834476e+00 1.873064819335937500e+03 +5.161925201207885117e+00 2.415413818359375000e+03 +5.172581280088985878e+00 3.078907714843750000e+03 +5.183237234523763171e+00 3.636994140625000000e+03 +5.193893064255843406e+00 3.818624267578125000e+03 +5.204548769028866317e+00 3.560608154296875000e+03 +5.215204348586462757e+00 2.943248535156250000e+03 +5.225859802672274235e+00 2.250387207031250000e+03 +5.236515131029942260e+00 1.661774169921875000e+03 +5.247170333403111897e+00 1.243349975585937500e+03 +5.257825409535429984e+00 1.002456909179687500e+03 +5.268480359170552241e+00 8.686416625976562500e+02 +5.279135182052125508e+00 7.924193725585937500e+02 +5.289789877923813499e+00 7.505543823242187500e+02 +5.300444446529275488e+00 7.257702026367187500e+02 +5.311098887612170749e+00 7.097236328125000000e+02 +5.321753200916169213e+00 7.001299438476562500e+02 +5.332407386184940812e+00 6.936835327148437500e+02 +5.343061443162157254e+00 6.887816772460937500e+02 +5.353715371591493799e+00 6.860388793945312500e+02 +5.364369171216630150e+00 6.836042480468750000e+02 +5.375022841781246008e+00 6.811933593750000000e+02 +5.385676383029030845e+00 6.801546630859375000e+02 +5.396329794703667915e+00 6.789706420898437500e+02 +5.406983076548852019e+00 6.775343627929687500e+02 +5.417636228308274404e+00 6.773240966796875000e+02 +5.428289249725635202e+00 6.767597045898437500e+02 +5.438942140544633652e+00 6.760964965820312500e+02 +5.449594900508974327e+00 6.765576782226562500e+02 +5.460247529362363572e+00 6.766649780273437500e+02 +5.470900026848512177e+00 6.769923095703125000e+02 +5.481552392711131816e+00 6.780775756835937500e+02 +5.492204626693940384e+00 6.788614501953125000e+02 +5.502856728540657549e+00 6.805922241210937500e+02 +5.513508697995003871e+00 6.826817626953125000e+02 +5.524160534800707900e+00 6.849552001953125000e+02 +5.534812238701497300e+00 6.890444335937500000e+02 +5.545463809441105951e+00 6.943037109375000000e+02 +5.556115246763266846e+00 7.013156738281250000e+02 +5.566766550411720971e+00 7.126730346679687500e+02 +5.577417720130208423e+00 7.303010864257812500e+02 +5.588068755662476406e+00 7.575957031250000000e+02 +5.598719656752271234e+00 8.053261718750000000e+02 +5.609370423143347217e+00 8.899309082031250000e+02 +5.620021054579456887e+00 1.040861694335937500e+03 +5.630671550804358105e+00 1.316172363281250000e+03 +5.641321911561814062e+00 1.788488403320312500e+03 +5.651972136595588836e+00 2.511168945312500000e+03 +5.662622225649450947e+00 3.439645751953125000e+03 +5.673272178467168914e+00 4.367238769531250000e+03 +5.683921994792519250e+00 4.940112304687500000e+03 +5.694571674369280245e+00 4.922060546875000000e+03 +5.705221216941231077e+00 4.314236816406250000e+03 +5.715870622252158029e+00 3.394366210937500000e+03 +5.726519890045847383e+00 2.452543701171875000e+03 +5.737169020066088976e+00 1.738268798828125000e+03 +5.747818012056680637e+00 1.285203613281250000e+03 +5.758466865761416642e+00 1.021999877929687500e+03 +5.769115580924100151e+00 8.796880493164062500e+02 +5.779764157288533433e+00 8.020812988281250000e+02 +5.790412594598525864e+00 7.566673583984375000e+02 +5.801060892597885932e+00 7.299285888671875000e+02 +5.811709051030431006e+00 7.145324707031250000e+02 +5.822357069639976679e+00 7.043264770507812500e+02 +5.833004948170342985e+00 6.978503417968750000e+02 +5.843652686365357951e+00 6.941828613281250000e+02 +5.854300283968845164e+00 6.915758056640625000e+02 +5.864947740724638869e+00 6.908864746093750000e+02 +5.875595056376570646e+00 6.912194824218750000e+02 +5.886242230668481845e+00 6.927738647460937500e+02 +5.896889263344211152e+00 6.968969726562500000e+02 +5.907536154147603469e+00 7.035602416992187500e+02 +5.918182902822508140e+00 7.141380004882812500e+02 +5.928829509112776286e+00 7.325109863281250000e+02 +5.939475972762263467e+00 7.641732177734375000e+02 +5.950122293514825245e+00 8.188717041015625000e+02 +5.960768471114326061e+00 9.175960693359375000e+02 +5.971414505304631248e+00 1.101096801757812500e+03 +5.982060395829607913e+00 1.421695190429687500e+03 +5.992706142433131156e+00 1.932250366210937500e+03 +6.003351744859072525e+00 2.651426513671875000e+03 +6.013997202851315116e+00 3.441502197265625000e+03 +6.024642516153739358e+00 4.055655761718750000e+03 +6.035287684510231010e+00 4.256279785156250000e+03 +6.045932707664681161e+00 3.941619384765625000e+03 +6.056577585360981786e+00 3.235073730468750000e+03 +6.067222317343029303e+00 2.433556640625000000e+03 +6.077866903354722794e+00 1.765935668945312500e+03 +6.088511343139969334e+00 1.302019409179687500e+03 +6.099155636442673334e+00 1.025054077148437500e+03 +6.109799783006745422e+00 8.746006469726562500e+02 +6.120443782576100666e+00 7.901195678710937500e+02 +6.131087634894655913e+00 7.420778198242187500e+02 +6.141731339706333337e+00 7.149016113281250000e+02 +6.152374896755056888e+00 6.974500732421875000e+02 +6.163018305784754070e+00 6.859393310546875000e+02 +6.173661566539361267e+00 6.788694458007812500e+02 +6.184304678762805985e+00 6.733462524414062500e+02 +6.194947642199034377e+00 6.695398559570312500e+02 +6.205590456591986381e+00 6.667891845703125000e+02 +6.216233121685607266e+00 6.642049560546875000e+02 +6.226875637223849402e+00 6.626867675781250000e+02 +6.237518002950664275e+00 6.612979125976562500e+02 +6.248160218610008698e+00 6.599806518554687500e+02 +6.258802283945843037e+00 6.596062622070312500e+02 +6.269444198702132987e+00 6.589934082031250000e+02 +6.280085962622845130e+00 6.581966552734375000e+02 +6.290727575451953157e+00 6.580597534179687500e+02 +6.301369036933428092e+00 6.577460937500000000e+02 +6.312010346811252504e+00 6.572639770507812500e+02 +6.322651504829406299e+00 6.574758300781250000e+02 +6.333292510731877378e+00 6.574190673828125000e+02 +6.343933364262655417e+00 6.573334960937500000e+02 +6.354574065165731867e+00 6.577716674804687500e+02 +6.365214613185104398e+00 6.580551147460937500e+02 +6.375855008064776008e+00 6.587269897460937500e+02 +6.386495249548747921e+00 6.595447387695312500e+02 +6.397135337381029352e+00 6.601424560546875000e+02 +6.407775271305633957e+00 6.616143188476562500e+02 +6.418415051066572730e+00 6.631992187500000000e+02 +6.429054676407869984e+00 6.650293579101562500e+02 +6.439694147073545594e+00 6.682530517578125000e+02 +6.450333462807628315e+00 6.727244873046875000e+02 +6.460972623354146904e+00 6.791828002929687500e+02 +6.471611628457133669e+00 6.902700195312500000e+02 +6.482250477860629800e+00 7.087609863281250000e+02 +6.492889171308677376e+00 7.408073120117187500e+02 +6.503527708545317587e+00 7.988739624023437500e+02 +6.514166089314603170e+00 9.023049316406250000e+02 +6.524804313360585084e+00 1.070028564453125000e+03 +6.535442380427322284e+00 1.310592407226562500e+03 +6.546080290258871059e+00 1.592997314453125000e+03 +6.556718042599300134e+00 1.833729614257812500e+03 +6.567355637192675566e+00 1.951127563476562500e+03 +6.577993073783067857e+00 1.886588134765625000e+03 +6.588630352114553723e+00 1.672712768554687500e+03 +6.599267471931211659e+00 1.386576049804687500e+03 +6.609904432977128153e+00 1.127488647460937500e+03 +6.620541234996386137e+00 9.456999511718750000e+02 +6.631177877733078319e+00 8.284180908203125000e+02 +6.641814360931300065e+00 7.630810546875000000e+02 +6.652450684335147635e+00 7.295563964843750000e+02 +6.663086847688726166e+00 7.107470703125000000e+02 +6.673722850736139911e+00 7.009564819335937500e+02 +6.684358693221499337e+00 6.963928833007812500e+02 +6.694994374888920241e+00 6.943372802734375000e+02 +6.705629895482515757e+00 6.949149780273437500e+02 +6.716265254746412339e+00 6.969520874023437500e+02 +6.726900452424732002e+00 7.005670776367187500e+02 +6.737535488261608307e+00 7.068223266601562500e+02 +6.748170362001171263e+00 7.163702392578125000e+02 +6.758805073387557982e+00 7.310372924804687500e+02 +6.769439622164912684e+00 7.550841064453125000e+02 +6.780074008077376924e+00 7.961584472656250000e+02 +6.790708230869102024e+00 8.677875976562500000e+02 +6.801342290284239311e+00 9.958537597656250000e+02 +6.811976186066947214e+00 1.231215209960937500e+03 +6.822609917961384163e+00 1.616306884765625000e+03 +6.833243485711717469e+00 2.196250488281250000e+03 +6.843876889062113555e+00 2.935390869140625000e+03 +6.854510127756747728e+00 3.636816406250000000e+03 +6.865143201539792628e+00 4.071528076171875000e+03 +6.875776110155433329e+00 4.063956787109375000e+03 +6.886408853347850467e+00 3.634684326171875000e+03 +6.897041430861236222e+00 2.957371337890625000e+03 +6.907673842439779222e+00 2.320462646484375000e+03 +6.918306087827677864e+00 1.929380126953125000e+03 +6.928938166769134988e+00 1.809908691406250000e+03 +6.939570079008348991e+00 1.909286743164062500e+03 +6.950201824289534258e+00 2.110392578125000000e+03 +6.960833402356898958e+00 2.258080566406250000e+03 +6.971464812954664581e+00 2.240816894531250000e+03 +6.982096055827046399e+00 2.038132080078125000e+03 +6.992727130718272122e+00 1.711284545898437500e+03 +7.003358037372569456e+00 1.381904418945312500e+03 +7.013988775534173215e+00 1.114066406250000000e+03 +7.024619344947314659e+00 9.326279907226562500e+02 +7.035249745356240147e+00 8.282878417968750000e+02 +7.045879976505193376e+00 7.696282958984375000e+02 +7.056510038138419816e+00 7.354207153320312500e+02 +7.067139930000176484e+00 7.167085571289062500e+02 +7.077769651834718623e+00 7.048060302734375000e+02 +7.088399203386307690e+00 6.969727783203125000e+02 +7.099028584399209585e+00 6.922392578125000000e+02 +7.109657794617693760e+00 6.885267944335937500e+02 +7.120286833786032332e+00 6.860540771484375000e+02 +7.130915701648505411e+00 6.844313354492187500e+02 +7.141544397949390444e+00 6.828943481445312500e+02 +7.152172922432977309e+00 6.822315673828125000e+02 +7.162801274843554999e+00 6.817125244140625000e+02 +7.173429454925416060e+00 6.814032592773437500e+02 +7.184057462422860141e+00 6.820169677734375000e+02 +7.194685297080190445e+00 6.831192626953125000e+02 +7.205312958641711063e+00 6.848960571289062500e+02 +7.215940446851735857e+00 6.883131713867187500e+02 +7.226567761454576910e+00 6.936546630859375000e+02 +7.237194902194556079e+00 7.019376831054687500e+02 +7.247821868815994328e+00 7.165010986328125000e+02 +7.258448661063219731e+00 7.421382446289062500e+02 +7.269075278680563912e+00 7.879049682617187500e+02 +7.279701721412362936e+00 8.706286621093750000e+02 +7.290327989002958198e+00 1.020032287597656250e+03 +7.300954081196691092e+00 1.251599487304687500e+03 +7.311579997737913672e+00 1.582026367187500000e+03 +7.322205738370975325e+00 1.959066162109375000e+03 +7.332831302840235210e+00 2.266651855468750000e+03 +7.343456690890052485e+00 2.396548339843750000e+03 +7.354081902264796078e+00 2.292095703125000000e+03 +7.364706936708833140e+00 1.997203857421875000e+03 +7.375331793966537042e+00 1.609820312500000000e+03 +7.385956473782285592e+00 1.270382324218750000e+03 +7.396580975900465482e+00 1.028067504882812500e+03 +7.407205300065458076e+00 8.734135131835937500e+02 +7.417829446021659834e+00 7.883736572265625000e+02 +7.428453413513460113e+00 7.424445190429687500e+02 +7.439077202285265145e+00 7.159290161132812500e+02 +7.449700812081473167e+00 7.020507202148437500e+02 +7.460324242646494852e+00 6.938139038085937500e+02 +7.470947493724743538e+00 6.892227172851562500e+02 +7.481570565060634337e+00 6.878087158203125000e+02 +7.492193456398588580e+00 6.886027832031250000e+02 +7.502816167483032039e+00 6.916846313476562500e+02 +7.513438698058396703e+00 6.986668090820312500e+02 +7.524061047869113672e+00 7.119370117187500000e+02 +7.534683216659623817e+00 7.356111450195312500e+02 +7.545305204174368896e+00 7.795932617187500000e+02 +7.555927010157796886e+00 8.618198852539062500e+02 +7.566548634354358427e+00 9.933261108398437500e+02 +7.577170076508507712e+00 1.192349487304687500e+03 +7.587791336364708705e+00 1.440743041992187500e+03 +7.598412413667427145e+00 1.679812500000000000e+03 +7.609033308161126996e+00 1.836830810546875000e+03 +7.619654019590285543e+00 1.847573730468750000e+03 +7.630274547699380072e+00 1.727088500976562500e+03 +7.640894892232893199e+00 1.533870483398437500e+03 +7.651515052935311090e+00 1.372615356445312500e+03 +7.662135029551125243e+00 1.308448486328125000e+03 +7.672754821824830707e+00 1.347620971679687500e+03 +7.683374429500929637e+00 1.449842773437500000e+03 +7.693993852323922411e+00 1.540186279296875000e+03 +7.704613090038321843e+00 1.550533813476562500e+03 +7.715232142388641634e+00 1.456975830078125000e+03 +7.725851009119395485e+00 1.284302490234375000e+03 +7.736469689975109532e+00 1.096495727539062500e+03 +7.747088184700309910e+00 9.383493041992187500e+02 +7.757706493039527196e+00 8.253958129882812500e+02 +7.768324614737297296e+00 7.555357666015625000e+02 +7.778942549538160556e+00 7.184893188476562500e+02 +7.789560297186662652e+00 6.966138305664062500e+02 +7.800177857427352812e+00 6.844909667968750000e+02 +7.810795230004780265e+00 6.772030639648437500e+02 +7.821412414663510226e+00 6.723433837890625000e+02 +7.832029411148103470e+00 6.695527954101562500e+02 +7.842646219203122548e+00 6.676491088867187500e+02 +7.853262838573145110e+00 6.659823608398437500e+02 +7.863879269002743477e+00 6.653209838867187500e+02 +7.874495510236503293e+00 6.647116699218750000e+02 +7.885111562019006648e+00 6.643076171875000000e+02 +7.895727424094843627e+00 6.646254272460937500e+02 +7.906343096208609644e+00 6.649099731445312500e+02 +7.916958578104905442e+00 6.654145507812500000e+02 +7.927573869528329986e+00 6.664851074218750000e+02 +7.938188970223497343e+00 6.684416503906250000e+02 +7.948803879935016248e+00 6.721162109375000000e+02 +7.959418598407507872e+00 6.786392211914062500e+02 +7.970033125385590722e+00 6.901878051757812500e+02 +7.980647460613894850e+00 7.107658691406250000e+02 +7.991261603837049421e+00 7.471265258789062500e+02 +8.001875554799692480e+00 8.035787963867187500e+02 +8.012489313246460299e+00 8.801359863281250000e+02 +8.023102878922003356e+00 9.661654663085937500e+02 +8.033716251570968581e+00 1.036288574218750000e+03 +8.044329430938010006e+00 1.064299438476562500e+03 +8.054942416767790547e+00 1.038845092773437500e+03 +8.065555208804969567e+00 9.729641113281250000e+02 +8.076167806794217086e+00 8.872814331054687500e+02 +8.086780210480208453e+00 8.094811401367187500e+02 +8.097392419607617242e+00 7.538461914062500000e+02 +8.108004433921129461e+00 7.195546875000000000e+02 +8.118616253165432894e+00 7.001906738281250000e+02 +8.129227877085215326e+00 6.913977661132812500e+02 +8.139839305425176974e+00 6.876367797851562500e+02 +8.150450537930018058e+00 6.868375244140625000e+02 +8.161061574344444125e+00 6.890332031250000000e+02 +8.171672414413166052e+00 6.934417724609375000e+02 +8.182283057880901822e+00 7.013698730468750000e+02 +8.192893504492367640e+00 7.144827880859375000e+02 +8.203503753992292147e+00 7.375480346679687500e+02 +8.214113806125400430e+00 7.769415283203125000e+02 +8.224723660636430012e+00 8.514718017578125000e+02 +8.235333317270120190e+00 9.778217773437500000e+02 +8.245942775771213817e+00 1.185203857421875000e+03 +8.256552035884460850e+00 1.488963134765625000e+03 +8.267161097354613020e+00 1.850345336914062500e+03 +8.277769959926430943e+00 2.180961181640625000e+03 +8.288378623344673457e+00 2.381870361328125000e+03 +8.298987087354113612e+00 2.369458007812500000e+03 +8.309595351699520904e+00 2.164498779296875000e+03 +8.320203416125675489e+00 1.869843750000000000e+03 +8.330811280377352190e+00 1.609964111328125000e+03 +8.341418944199348928e+00 1.455990356445312500e+03 +8.352026407336449410e+00 1.401854736328125000e+03 +8.362633669533453329e+00 1.402648559570312500e+03 +8.373240730535160381e+00 1.383694335937500000e+03 +8.383847590086380919e+00 1.309516357421875000e+03 +8.394454247931923518e+00 1.184627319335937500e+03 +8.405060703816603862e+00 1.039467407226562500e+03 +8.415666957485242961e+00 9.166905517578125000e+02 +8.426273008682667154e+00 8.248919677734375000e+02 +8.436878857153708111e+00 7.665571289062500000e+02 +8.447484502643199278e+00 7.343247070312500000e+02 +8.458089944895982981e+00 7.162971801757812500e+02 +8.468695183656903325e+00 7.058026123046875000e+02 +8.479300218670809741e+00 7.003860473632812500e+02 +8.489905049682560545e+00 6.971550903320312500e+02 +8.500509676437012274e+00 6.953563232421875000e+02 +8.511114098679032125e+00 6.949784545898437500e+02 +8.521718316153490846e+00 6.951216430664062500e+02 +8.532322328605257411e+00 6.961920166015625000e+02 +8.542926135779218555e+00 6.980186157226562500e+02 +8.553529737420257462e+00 7.006634521484375000e+02 +8.564133133273260867e+00 7.050765991210937500e+02 +8.574736323083124390e+00 7.121492919921875000e+02 +8.585339306594748976e+00 7.210169067382812500e+02 +8.595942083553033797e+00 7.375151367187500000e+02 +8.606544653702897563e+00 7.690372314453125000e+02 +8.617147016789250102e+00 8.278894042968750000e+02 +8.627749172557006574e+00 9.343248291015625000e+02 +8.638351120751096346e+00 1.116160156250000000e+03 +8.648952861116448787e+00 1.381629272460937500e+03 +8.659554393397998595e+00 1.737533081054687500e+03 +8.670155717340682244e+00 2.101072753906250000e+03 +8.680756832689445091e+00 2.363639160156250000e+03 +8.691357739189237819e+00 2.430061035156250000e+03 +8.701958436585016443e+00 2.274398437500000000e+03 +8.712558924621735201e+00 1.949995971679687500e+03 +8.723159203044362542e+00 1.572384765625000000e+03 +8.733759271597866913e+00 1.250865356445312500e+03 +8.744359130027223870e+00 1.022978027343750000e+03 +8.754958778077412518e+00 8.802243041992187500e+02 +8.765558215493417293e+00 8.037642211914062500e+02 +8.776157442020226185e+00 7.630902099609375000e+02 +8.786756457402837839e+00 7.420597534179687500e+02 +8.797355261386249126e+00 7.337076416015625000e+02 +8.807953853715467574e+00 7.338025512695312500e+02 +8.818552234135500711e+00 7.416336059570312500e+02 +8.829150402391364949e+00 7.622483520507812500e+02 +8.839748358228082026e+00 8.032894897460937500e+02 +8.850346101390677234e+00 8.773948364257812500e+02 +8.860943631624175865e+00 1.015917968750000000e+03 +8.871540948673620974e+00 1.239467895507812500e+03 +8.882138052284050289e+00 1.560921752929687500e+03 +8.892734942200508641e+00 1.953891113281250000e+03 +8.903331618168044415e+00 2.313953125000000000e+03 +8.913928079931723758e+00 2.525069580078125000e+03 +8.924524327236600385e+00 2.509851074218750000e+03 +8.935120359827740444e+00 2.270142822265625000e+03 +8.945716177450217188e+00 1.889432006835937500e+03 +8.956311779849107424e+00 1.497576538085937500e+03 +8.966907166769495063e+00 1.186223632812500000e+03 +8.977502337956464018e+00 9.739269409179687500e+02 +8.988097293155110634e+00 8.456494140625000000e+02 +8.998692032110529482e+00 7.764365844726562500e+02 +9.009286554567824012e+00 7.370728149414062500e+02 +9.019880860272101231e+00 7.141510009765625000e+02 +9.030474948968478799e+00 7.008740844726562500e+02 +9.041068820402069051e+00 6.920217895507812500e+02 +9.051662474318002083e+00 6.859138793945312500e+02 +9.062255910461402664e+00 6.820156250000000000e+02 +9.072849128577406219e+00 6.786572875976562500e+02 +9.083442128411146399e+00 6.765030517578125000e+02 +9.094034909707779946e+00 6.751370849609375000e+02 +9.104627472212447614e+00 6.734834594726562500e+02 +9.115219815670306147e+00 6.724407958984375000e+02 +9.125811939826519392e+00 6.714660644531250000e+02 +9.136403844426247645e+00 6.701627807617187500e+02 +9.146995529214665410e+00 6.699064941406250000e+02 +9.157586993936950748e+00 6.694194946289062500e+02 +9.168178238338281716e+00 6.691209106445312500e+02 +9.178769262163847031e+00 6.695682373046875000e+02 +9.189360065158837187e+00 6.699863281250000000e+02 +9.199950647068453335e+00 6.717796630859375000e+02 +9.210541007637893074e+00 6.758046264648437500e+02 +9.221131146612368212e+00 6.824872436523437500e+02 +9.231721063737092337e+00 6.954828491210937500e+02 +9.242310758757284361e+00 7.166214599609375000e+02 +9.252900231418166754e+00 7.457329711914062500e+02 +9.263489481464969089e+00 7.815885009765625000e+02 +9.274078508642926266e+00 8.134724731445312500e+02 +9.284667312697280295e+00 8.312718505859375000e+02 +9.295255893373276734e+00 8.287663574218750000e+02 +9.305844250416166474e+00 8.055675048828125000e+02 +9.316432383571202180e+00 7.717019653320312500e+02 +9.327020292583648953e+00 7.373230590820312500e+02 +9.337607977198775444e+00 7.100646972656250000e+02 +9.348195437161850307e+00 6.923247070312500000e+02 +9.358782672218152854e+00 6.820676879882812500e+02 +9.369369682112969500e+00 6.770531616210937500e+02 +9.379956466591584885e+00 6.758416748046875000e+02 +9.390543025399294308e+00 6.761682739257812500e+02 +9.401129358281396620e+00 6.789955444335937500e+02 +9.411715464983201329e+00 6.847637329101562500e+02 +9.422301345250016169e+00 6.944530639648437500e+02 +9.432886998827155978e+00 7.136517333984375000e+02 +9.443472425459940922e+00 7.462779541015625000e+02 +9.454057624893701828e+00 8.002896728515625000e+02 +9.464642596873771296e+00 8.823135986328125000e+02 +9.475227341145481930e+00 9.835158691406250000e+02 +9.485811857454180540e+00 1.089523559570312500e+03 +9.496396145545215717e+00 1.175358276367187500e+03 +9.506980205163939601e+00 1.219432373046875000e+03 +9.517564036055718546e+00 1.228174194335937500e+03 +9.528147637965910022e+00 1.230171020507812500e+03 +9.538731010639885710e+00 1.254951782226562500e+03 +9.549314153823027951e+00 1.318453369140625000e+03 +9.559897067260713754e+00 1.388763671875000000e+03 +9.570479750698330790e+00 1.425546752929687500e+03 +9.581062203881272055e+00 1.393830078125000000e+03 +9.591644426554937652e+00 1.288382934570312500e+03 +9.602226418464729463e+00 1.138149902343750000e+03 +9.612808179356060023e+00 9.878187255859375000e+02 +9.623389708974336543e+00 8.681583862304687500e+02 +9.633971007064991099e+00 7.866500244140625000e+02 +9.644552073373441559e+00 7.385062866210937500e+02 +9.655132907645123552e+00 7.114185791015625000e+02 +9.665713509625476263e+00 6.968461303710937500e+02 +9.676293879059931768e+00 6.886304931640625000e+02 +9.686874015693950568e+00 6.845813598632812500e+02 +9.697453919272982503e+00 6.829522094726562500e+02 +9.708033589542489850e+00 6.820095825195312500e+02 +9.718613026247933107e+00 6.827423095703125000e+02 +9.729192229134785208e+00 6.842188110351562500e+02 +9.739771197948524417e+00 6.871852416992187500e+02 +9.750349932434632549e+00 6.929224853515625000e+02 +9.760928432338593197e+00 7.022583007812500000e+02 +9.771506697405907715e+00 7.198040161132812500e+02 +9.782084727382072131e+00 7.513469848632812500e+02 +9.792662522012586024e+00 8.057764892578125000e+02 +9.803240081042963183e+00 8.986049804687500000e+02 +9.813817404218724505e+00 1.027781738281250000e+03 +9.824394491285389108e+00 1.187187744140625000e+03 +9.834971341988479665e+00 1.342151000976562500e+03 +9.845547956073536611e+00 1.441205566406250000e+03 +9.856124333286091499e+00 1.449668090820312500e+03 +9.866700473371693647e+00 1.366206787109375000e+03 +9.877276376075895925e+00 1.220043945312500000e+03 +9.887852041144247650e+00 1.055586669921875000e+03 +9.898427468322315903e+00 9.206362915039062500e+02 +9.909002657355660659e+00 8.248157348632812500e+02 +9.919577607989864987e+00 7.666193847656250000e+02 +9.930152319970504848e+00 7.362219238281250000e+02 +9.940726793043159759e+00 7.217100830078125000e+02 +9.951301026953426998e+00 7.179713134765625000e+02 +9.961875021446894962e+00 7.220485839843750000e+02 +9.972448776269173365e+00 7.360645751953125000e+02 +9.983022291165864814e+00 7.629471435546875000e+02 +9.993595565882586129e+00 8.158376464843750000e+02 +1.000416860016495413e+01 9.112163696289062500e+02 +1.001474139375859806e+01 1.049245849609375000e+03 +1.002531394640914009e+01 1.229015991210937500e+03 +1.003588625786222366e+01 1.410225952148437500e+03 +1.004645832786349047e+01 1.542261108398437500e+03 +1.005703015615858753e+01 1.589919311523437500e+03 +1.006760174249316897e+01 1.541504394531250000e+03 +1.007817308661289424e+01 1.424957031250000000e+03 +1.008874418826343167e+01 1.294504882812500000e+03 +1.009931504719044959e+01 1.190503662109375000e+03 +1.010988566313962700e+01 1.118144165039062500e+03 +1.012045603585664288e+01 1.063238403320312500e+03 +1.013102616508719578e+01 1.007510009765625000e+03 +1.014159605057697178e+01 9.387431030273437500e+02 +1.015216569207167652e+01 8.699727783203125000e+02 +1.016273508931701208e+01 8.061417846679687500e+02 +1.017330424205869654e+01 7.582930908203125000e+02 +1.018387315004244620e+01 7.272119140625000000e+02 +1.019444181301398089e+01 7.084058837890625000e+02 +1.020501023071903823e+01 6.981911621093750000e+02 +1.021557840290334873e+01 6.922360839843750000e+02 +1.022614632931265710e+01 6.890814819335937500e+02 +1.023671400969270984e+01 6.873847656250000000e+02 +1.024728144378926586e+01 6.862614746093750000e+02 +1.025784863134807168e+01 6.864813842773437500e+02 +1.026841557211490752e+01 6.870771484375000000e+02 +1.027898226583553942e+01 6.892537231445312500e+02 +1.028954871225574408e+01 6.935203247070312500e+02 +1.030011491112130528e+01 7.006565551757812500e+02 +1.031068086217801394e+01 7.148612060546875000e+02 +1.032124656517166450e+01 7.390040283203125000e+02 +1.033181201984805853e+01 7.797720947265625000e+02 +1.034237722595299935e+01 8.436943969726562500e+02 +1.035294218323230986e+01 9.214270629882812500e+02 +1.036350689143180404e+01 1.008942199707031250e+03 +1.037407135029730121e+01 1.075177734375000000e+03 +1.038463555957464379e+01 1.098153808593750000e+03 +1.039519951900965999e+01 1.071623168945312500e+03 +1.040576322834819933e+01 1.006277893066406250e+03 +1.041632668733611133e+01 9.207026367187500000e+02 +1.042688989571924552e+01 8.414843750000000000e+02 +1.043745285324346384e+01 7.829299316406250000e+02 +1.044801555965464068e+01 7.452891845703125000e+02 +1.045857801469864512e+01 7.257985229492187500e+02 +1.046914021812135864e+01 7.184252319335937500e+02 +1.047970216966866097e+01 7.203538818359375000e+02 +1.049026386908644959e+01 7.308907470703125000e+02 +1.050082531612062375e+01 7.553340454101562500e+02 +1.051138651051708273e+01 8.013146362304687500e+02 +1.052194745202173465e+01 8.793386840820312500e+02 +1.053250814038050187e+01 1.007551391601562500e+03 +1.054306857533930142e+01 1.176235473632812500e+03 +1.055362875664406452e+01 1.366962280273437500e+03 +1.056418868404072597e+01 1.542199707031250000e+03 +1.057474835727522056e+01 1.640827758789062500e+03 +1.058530777609350082e+01 1.649079956054687500e+03 +1.059586694024151932e+01 1.582589355468750000e+03 +1.060642584946522859e+01 1.488293212890625000e+03 +1.061698450351059719e+01 1.396133666992187500e+03 +1.062754290212359898e+01 1.333205322265625000e+03 +1.063810104505020604e+01 1.273243774414062500e+03 +1.064865893203640823e+01 1.199566772460937500e+03 +1.065921656282818475e+01 1.104590209960937500e+03 +1.066977393717154143e+01 9.943576049804687500e+02 +1.068033105481247524e+01 8.922419433593750000e+02 +1.069088791549699557e+01 8.144992065429687500e+02 +1.070144451897111537e+01 7.593032226562500000e+02 +1.071200086498085291e+01 7.276634521484375000e+02 +1.072255695327224068e+01 7.090423583984375000e+02 +1.073311278359130583e+01 6.987014770507812500e+02 +1.074366835568409151e+01 6.927329101562500000e+02 +1.075422366929663731e+01 6.889016113281250000e+02 +1.076477872417500237e+01 6.871348266601562500e+02 +1.077533352006523870e+01 6.861631469726562500e+02 +1.078588805671341078e+01 6.869276733398437500e+02 +1.079644233386558660e+01 6.893882446289062500e+02 +1.080699635126784663e+01 6.945742797851562500e+02 +1.081755010866627487e+01 7.054983520507812500e+02 +1.082810360580695530e+01 7.244885864257812500e+02 +1.083865684243598260e+01 7.589120483398437500e+02 +1.084920981829946207e+01 8.088952026367187500e+02 +1.085976253314350082e+01 8.710745239257812500e+02 +1.087031498671421126e+01 9.402892456054687500e+02 +1.088086717875771292e+01 9.885995483398437500e+02 +1.089141910902013066e+01 1.005946044921875000e+03 +1.090197077724760355e+01 9.846653442382812500e+02 +1.091252218318626532e+01 9.307870483398437500e+02 +1.092307332658226393e+01 8.623673706054687500e+02 +1.093362420718174910e+01 8.001635131835937500e+02 +1.094417482473087766e+01 7.533066406250000000e+02 +1.095472517897581888e+01 7.220283203125000000e+02 +1.096527526966274024e+01 7.062800292968750000e+02 +1.097582509653781635e+01 6.996358032226562500e+02 +1.098637465934723600e+01 7.014745483398437500e+02 +1.099692395783718979e+01 7.108397827148437500e+02 +1.100747299175386473e+01 7.335140991210937500e+02 +1.101802176084346918e+01 7.715427246093750000e+02 +1.102857026485221148e+01 8.368585815429687500e+02 +1.103911850352630886e+01 9.245899047851562500e+02 +1.104966647661198031e+01 1.024052612304687500e+03 +1.106021418385545374e+01 1.114989013671875000e+03 +1.107076162500296412e+01 1.165868286132812500e+03 +1.108130879980075534e+01 1.156306518554687500e+03 +1.109185570799506770e+01 1.092670166015625000e+03 +1.110240234933216108e+01 9.999187011718750000e+02 +1.111294872355829000e+01 8.966014404296875000e+02 +1.112349483041972675e+01 8.127677001953125000e+02 +1.113404066966274009e+01 7.561912231445312500e+02 +1.114458624103360940e+01 7.191755981445312500e+02 +1.115513154427862297e+01 7.000423583984375000e+02 +1.116567657914407263e+01 6.887508544921875000e+02 +1.117622134537625378e+01 6.828145141601562500e+02 +1.118676584272147778e+01 6.793381347656250000e+02 +1.119731007092604713e+01 6.771367187500000000e+02 +1.120785402973628742e+01 6.761496582031250000e+02 +1.121839771889852067e+01 6.753288574218750000e+02 +1.122894113815908135e+01 6.752622070312500000e+02 +1.123948428726429682e+01 6.755961303710937500e+02 +1.125002716596052110e+01 6.760122070312500000e+02 +1.126056977399410286e+01 6.774196166992187500e+02 +1.127111211111139788e+01 6.791550903320312500e+02 +1.128165417705876727e+01 6.826375122070312500e+02 +1.129219597158258459e+01 6.885833740234375000e+02 +1.130273749442922515e+01 6.994708251953125000e+02 +1.131327874534507316e+01 7.182205810546875000e+02 +1.132381972407651638e+01 7.477691650390625000e+02 +1.133436043036995322e+01 7.923176269531250000e+02 +1.134490086397178388e+01 8.465413818359375000e+02 +1.135544102462842275e+01 8.988520507812500000e+02 +1.136598091208628070e+01 9.393861694335937500e+02 +1.137652052609177744e+01 9.505449218750000000e+02 +1.138705986639135226e+01 9.296549682617187500e+02 +1.139759893273143376e+01 8.852344360351562500e+02 +1.140813772485846300e+01 8.335649414062500000e+02 +1.141867624251889168e+01 7.817878417968750000e+02 +1.142921448545917507e+01 7.470804443359375000e+02 +1.143975245342577729e+01 7.250403442382812500e+02 +1.145029014616516250e+01 7.160763549804687500e+02 +1.146082756342381082e+01 7.168729858398437500e+02 +1.147136470494819527e+01 7.287165527343750000e+02 +1.148190157048481552e+01 7.540387573242187500e+02 +1.149243815978016237e+01 8.013792724609375000e+02 +1.150297447258073547e+01 8.785340576171875000e+02 +1.151351050863304515e+01 9.841313476562500000e+02 +1.152404626768360529e+01 1.110267944335937500e+03 +1.153458174947894044e+01 1.234328735351562500e+03 +1.154511695376557867e+01 1.313542480468750000e+03 +1.155565188029005164e+01 1.337609375000000000e+03 +1.156618652879890341e+01 1.302816528320312500e+03 +1.157672089903868340e+01 1.227626098632812500e+03 +1.158725499075594989e+01 1.141229003906250000e+03 +1.159778880369725584e+01 1.065448974609375000e+03 +1.160832233760917731e+01 9.974991455078125000e+02 +1.161885559223828679e+01 9.396141357421875000e+02 +1.162938856733116921e+01 8.822892456054687500e+02 +1.163992126263440952e+01 8.282180786132812500e+02 +1.165045367789460329e+01 7.832050781250000000e+02 +1.166098581285835500e+01 7.490753173828125000e+02 +1.167151766727227624e+01 7.264988403320312500e+02 +1.168204924088297680e+01 7.137516479492187500e+02 +1.169258053343708070e+01 7.065291137695312500e+02 +1.170311154468122261e+01 7.037494506835937500e+02 +1.171364227436203187e+01 7.028441772460937500e+02 +1.172417272222615381e+01 7.044617919921875000e+02 +1.173470288802023909e+01 7.081874389648437500e+02 +1.174523277149094369e+01 7.157470703125000000e+02 +1.175576237238492894e+01 7.293864746093750000e+02 +1.176629169044886680e+01 7.550786743164062500e+02 +1.177682072542943104e+01 8.008106079101562500e+02 +1.178734947707330960e+01 8.743061523437500000e+02 +1.179787794512719401e+01 9.832484741210937500e+02 +1.180840612933777400e+01 1.118622802734375000e+03 +1.181893402945175886e+01 1.250297119140625000e+03 +1.182946164521585786e+01 1.351081420898437500e+03 +1.183998897637679271e+01 1.381074584960937500e+03 +1.185051602268128157e+01 1.331694580078125000e+03 +1.186104278387606215e+01 1.220269042968750000e+03 +1.187156925970786681e+01 1.086376953125000000e+03 +1.188209544992344391e+01 9.549729003906250000e+02 +1.189262135426954536e+01 8.594440307617187500e+02 +1.190314697249293374e+01 7.960217895507812500e+02 +1.191367230434036628e+01 7.627114868164062500e+02 +1.192419734955862154e+01 7.510479736328125000e+02 +1.193472210789447985e+01 7.576512451171875000e+02 +1.194524657909472509e+01 7.833159179687500000e+02 +1.195577076290614826e+01 8.332525024414062500e+02 +1.196629465907555456e+01 9.112811889648437500e+02 +1.197681826734975274e+01 1.009433410644531250e+03 +1.198734158747555156e+01 1.114133911132812500e+03 +1.199786461919977221e+01 1.200647705078125000e+03 +1.200838736226924475e+01 1.241906005859375000e+03 +1.201890981643080636e+01 1.233878417968750000e+03 +1.202943198143129955e+01 1.186364746093750000e+03 +1.203995385701756504e+01 1.117898193359375000e+03 +1.205047544293646489e+01 1.048176391601562500e+03 +1.206099673893486113e+01 9.862639160156250000e+02 +1.207151774475962469e+01 9.279526977539062500e+02 +1.208203846015763006e+01 8.751108398437500000e+02 +1.209255888487576236e+01 8.236754760742187500e+02 +1.210307901866091207e+01 7.792252807617187500e+02 +1.211359886125997853e+01 7.451602783203125000e+02 +1.212411841241986288e+01 7.207249755859375000e+02 +1.213463767188747688e+01 7.059654541015625000e+02 +1.214515663940974655e+01 6.970002441406250000e+02 +1.215567531473358720e+01 6.915884399414062500e+02 +1.216619369760594083e+01 6.885905151367187500e+02 +1.217671178777374230e+01 6.861752319335937500e+02 +1.218722958498393538e+01 6.848314208984375000e+02 +1.219774708898348337e+01 6.836103515625000000e+02 +1.220826429951934244e+01 6.828072509765625000e+02 +1.221878121633847769e+01 6.822663574218750000e+02 +1.222929783918786661e+01 6.815256958007812500e+02 +1.223981416781449205e+01 6.813870239257812500e+02 +1.225033020196534395e+01 6.810576171875000000e+02 +1.226084594138742112e+01 6.806791381835937500e+02 +1.227136138582771707e+01 6.808665771484375000e+02 +1.228187653503325194e+01 6.805249023437500000e+02 +1.229239138875104231e+01 6.809461669921875000e+02 +1.230290594672810833e+01 6.815899047851562500e+02 +1.231342020871148613e+01 6.822404785156250000e+02 +1.232393417444821004e+01 6.837416381835937500e+02 +1.233444784368533398e+01 6.854254760742187500e+02 +1.234496121616990294e+01 6.881037597656250000e+02 +1.235547429164898325e+01 6.924296875000000000e+02 +1.236598706986963769e+01 6.992177734375000000e+02 +1.237649955057894857e+01 7.117470703125000000e+02 +1.238701173352398932e+01 7.331104736328125000e+02 +1.239752361845185291e+01 7.680556640625000000e+02 +1.240803520510963232e+01 8.181621704101562500e+02 +1.241854649324443649e+01 8.816943359375000000e+02 +1.242905748260337440e+01 9.500315551757812500e+02 +1.243956817293356387e+01 1.011162292480468750e+03 +1.245007856398212098e+01 1.057757202148437500e+03 +1.246058865549619021e+01 1.087816284179687500e+03 +1.247109844722290539e+01 1.104677490234375000e+03 +1.248160793890941456e+01 1.112337280273437500e+03 +1.249211713030287108e+01 1.106899291992187500e+03 +1.250262602115043364e+01 1.081947387695312500e+03 +1.251313461119927339e+01 1.035264404296875000e+03 +1.252364290019656501e+01 9.678991699218750000e+02 +1.253415088788949383e+01 8.936472167968750000e+02 +1.254465857402524342e+01 8.258160400390625000e+02 +1.255516595835101334e+01 7.725118408203125000e+02 +1.256567304061401202e+01 7.372329101562500000e+02 +1.257617982056144967e+01 7.154588012695312500e+02 +1.258668629794054006e+01 7.039624633789062500e+02 +1.259719247249851826e+01 6.981387939453125000e+02 +1.260769834398261224e+01 6.957836914062500000e+02 +1.261820391214006243e+01 6.967379150390625000e+02 +1.262870917671811810e+01 7.005480957031250000e+02 +1.263921413746403566e+01 7.095493774414062500e+02 +1.264971879412507683e+01 7.270390625000000000e+02 +1.266022314644851399e+01 7.564002685546875000e+02 +1.267072719418162130e+01 8.025034179687500000e+02 +1.268123093707168536e+01 8.619790649414062500e+02 +1.269173437486599809e+01 9.278380737304687500e+02 +1.270223750731186030e+01 9.832341918945312500e+02 +1.271274033415657634e+01 1.011440490722656250e+03 +1.272324285514745945e+01 1.004211853027343750e+03 +1.273374507003183354e+01 9.633917846679687500e+02 +1.274424697855702782e+01 9.018200073242187500e+02 +1.275474858047037507e+01 8.380989379882812500e+02 +1.276524987551922408e+01 7.833153686523437500e+02 +1.277575086345092359e+01 7.466546630859375000e+02 +1.278625154401282948e+01 7.256193847656250000e+02 +1.279675191695231007e+01 7.189322509765625000e+02 +1.280725198201673720e+01 7.244823608398437500e+02 +1.281775173895349340e+01 7.424971313476562500e+02 +1.282825118750996651e+01 7.738120117187500000e+02 +1.283875032743355149e+01 8.172592163085937500e+02 +1.284924915847164506e+01 8.647427978515625000e+02 +1.285974768037166704e+01 9.066199340820312500e+02 +1.287024589288103193e+01 9.275772094726562500e+02 +1.288074379574716311e+01 9.228789672851562500e+02 +1.289124138871749459e+01 8.922898559570312500e+02 +1.290173867153946397e+01 8.463707275390625000e+02 +1.291223564396052303e+01 7.971900634765625000e+02 +1.292273230572812359e+01 7.554675903320312500e+02 +1.293322865658972987e+01 7.251651000976562500e+02 +1.294372469629280786e+01 7.061458740234375000e+02 +1.295422042458484313e+01 6.951181030273437500e+02 +1.296471584121331233e+01 6.895092773437500000e+02 +1.297521094592571167e+01 6.861082763671875000e+02 +1.298570573846953735e+01 6.845974731445312500e+02 +1.299620021859230512e+01 6.836950073242187500e+02 +1.300669438604152539e+01 6.831593627929687500e+02 +1.301718824056471391e+01 6.834429931640625000e+02 +1.302768178190940773e+01 6.836553344726562500e+02 +1.303817500982314748e+01 6.847479248046875000e+02 +1.304866792405347198e+01 6.865251464843750000e+02 +1.305916052434793606e+01 6.893231811523437500e+02 +1.306965281045410343e+01 6.945164794921875000e+02 +1.308014478211953069e+01 7.023309326171875000e+02 +1.309063643909180641e+01 7.135306396484375000e+02 +1.310112778111850496e+01 7.279290161132812500e+02 +1.311161880794722201e+01 7.418795776367187500e+02 +1.312210951932555147e+01 7.536883544921875000e+02 +1.313259991500109969e+01 7.584838867187500000e+02 +1.314308999472148187e+01 7.562736816406250000e+02 +1.315357975823431858e+01 7.480876464843750000e+02 +1.316406920528723568e+01 7.371174926757812500e+02 +1.317455833562787149e+01 7.274690551757812500e+02 +1.318504714900387143e+01 7.219248046875000000e+02 +1.319553564516288091e+01 7.233309326171875000e+02 +1.320602382385256313e+01 7.343006591796875000e+02 +1.321651168482058480e+01 7.580899047851562500e+02 +1.322699922781461801e+01 8.031383666992187500e+02 +1.323748645258235079e+01 8.717847290039062500e+02 +1.324797335887146055e+01 9.678251953125000000e+02 +1.325845994642965309e+01 1.077533325195312500e+03 +1.326894621500463600e+01 1.181720092773437500e+03 +1.327943216434411511e+01 1.256138793945312500e+03 +1.328991779419581221e+01 1.282376098632812500e+03 +1.330040310430745443e+01 1.258684692382812500e+03 +1.331088809442678134e+01 1.199624633789062500e+03 +1.332137276430153250e+01 1.121562255859375000e+03 +1.333185711367946169e+01 1.042793945312500000e+03 +1.334234114230832269e+01 9.680359497070312500e+02 +1.335282484993589058e+01 9.039510498046875000e+02 +1.336330823630992981e+01 8.464333496093750000e+02 +1.337379130117822790e+01 7.996467285156250000e+02 +1.338427404428857059e+01 7.637827758789062500e+02 +1.339475646538875964e+01 7.385233154296875000e+02 +1.340523856422660032e+01 7.233422851562500000e+02 +1.341572034054990326e+01 7.144063110351562500e+02 +1.342620179410648618e+01 7.100392456054687500e+02 +1.343668292464417924e+01 7.091323852539062500e+02 +1.344716373191082326e+01 7.106524658203125000e+02 +1.345764421565425728e+01 7.163797607421875000e+02 +1.346812437562233455e+01 7.282827148437500000e+02 +1.347860421156291011e+01 7.482400512695312500e+02 +1.348908372322385851e+01 7.809824829101562500e+02 +1.349956291035305256e+01 8.228463745117187500e+02 +1.351004177269837392e+01 8.704761352539062500e+02 +1.352052031000771315e+01 9.128406372070312500e+02 +1.353099852202896791e+01 9.378024291992187500e+02 +1.354147640851004475e+01 9.383411254882812500e+02 +1.355195396919886264e+01 9.138033447265625000e+02 +1.356243120384333523e+01 8.724307250976562500e+02 +1.357290811219140103e+01 8.265563964843750000e+02 +1.358338469399099502e+01 7.862529296875000000e+02 +1.359386094899005570e+01 7.583912963867187500e+02 +1.360433687693654647e+01 7.447263793945312500e+02 +1.361481247757842183e+01 7.462110595703125000e+02 +1.362528775066365583e+01 7.620617675781250000e+02 +1.363576269594022072e+01 7.940957641601562500e+02 +1.364623731315610833e+01 8.415816040039062500e+02 +1.365671160205929979e+01 8.986943359375000000e+02 +1.366718556239780646e+01 9.557219238281250000e+02 +1.367765919391963436e+01 1.000548217773437500e+03 +1.368813249637280016e+01 1.024041381835937500e+03 +1.369860546950532765e+01 1.025089477539062500e+03 +1.370907811306524415e+01 1.006011413574218750e+03 +1.371955042680060011e+01 9.753089599609375000e+02 +1.373002241045943528e+01 9.369115600585937500e+02 +1.374049406378980898e+01 8.958378295898437500e+02 +1.375096538653978762e+01 8.534520874023437500e+02 +1.376143637845743761e+01 8.122576293945312500e+02 +1.377190703929084492e+01 7.764322509765625000e+02 +1.378237736878809372e+01 7.468507690429687500e+02 +1.379284736669728240e+01 7.269019775390625000e+02 +1.380331703276651290e+01 7.138211059570312500e+02 +1.381378636674390137e+01 7.063560180664062500e+02 +1.382425536837756042e+01 7.029116210937500000e+02 +1.383472403741562040e+01 7.017208251953125000e+02 +1.384519237360622235e+01 7.031297607421875000e+02 +1.385566037669750550e+01 7.075427246093750000e+02 +1.386612804643762686e+01 7.159042968750000000e+02 +1.387659538257473457e+01 7.306563110351562500e+02 +1.388706238485700872e+01 7.511306762695312500e+02 +1.389752905303262409e+01 7.761248168945312500e+02 +1.390799538684975722e+01 8.011322631835937500e+02 +1.391846138605660776e+01 8.185717163085937500e+02 +1.392892705040137180e+01 8.248841552734375000e+02 +1.393939237963225963e+01 8.167781372070312500e+02 +1.394985737349748511e+01 7.979476928710937500e+02 +1.396032203174527453e+01 7.735673217773437500e+02 +1.397078635412385950e+01 7.504269409179687500e+02 +1.398125034038148584e+01 7.331237792968750000e+02 +1.399171399026639051e+01 7.240398559570312500e+02 +1.400217730352683887e+01 7.245930786132812500e+02 +1.401264027991109451e+01 7.351683959960937500e+02 +1.402310291916742813e+01 7.563333740234375000e+02 +1.403356522104412463e+01 7.881397094726562500e+02 +1.404402718528947247e+01 8.255968017578125000e+02 +1.405448881165175834e+01 8.633485107421875000e+02 +1.406495009987930267e+01 8.913906860351562500e+02 +1.407541104972041346e+01 9.042471923828125000e+02 +1.408587166092341114e+01 8.999338378906250000e+02 +1.409633193323662326e+01 8.814013671875000000e+02 +1.410679186640839156e+01 8.551547241210937500e+02 +1.411725146018706134e+01 8.257432250976562500e+02 +1.412771071432098680e+01 7.975484619140625000e+02 +1.413816962855852921e+01 7.717686767578125000e+02 +1.414862820264806054e+01 7.495309448242187500e+02 +1.415908643633795982e+01 7.318565063476562500e+02 +1.416954432937661146e+01 7.183331909179687500e+02 +1.418000188151241403e+01 7.096323242187500000e+02 +1.419045909249376614e+01 7.046708374023437500e+02 +1.420091596206908768e+01 7.027170410156250000e+02 +1.421137248998679325e+01 7.037540893554687500e+02 +1.422182867599530809e+01 7.074931030273437500e+02 +1.423228451984306986e+01 7.160107421875000000e+02 +1.424274002127852690e+01 7.317193603515625000e+02 +1.425319518005013109e+01 7.577529907226562500e+02 +1.426364999590634142e+01 7.969297485351562500e+02 +1.427410446859562398e+01 8.470828857421875000e+02 +1.428455859786646265e+01 9.006730346679687500e+02 +1.429501238346734304e+01 9.463171997070312500e+02 +1.430546582514675436e+01 9.706340942382812500e+02 +1.431591892265320176e+01 9.672256469726562500e+02 +1.432637167573519399e+01 9.367326660156250000e+02 +1.433682408414124865e+01 8.901213989257812500e+02 +1.434727614761989756e+01 8.384776000976562500e+02 +1.435772786591967609e+01 7.953200073242187500e+02 +1.436817923878912140e+01 7.669782714843750000e+02 +1.437863026597679550e+01 7.561812133789062500e+02 +1.438908094723124798e+01 7.646918945312500000e+02 +1.439953128230105683e+01 7.912402954101562500e+02 +1.440998127093479120e+01 8.335914306640625000e+02 +1.442043091288103973e+01 8.859393310546875000e+02 +1.443088020788839998e+01 9.354515991210937500e+02 +1.444132915570547304e+01 9.692942504882812500e+02 +1.445177775608085824e+01 9.767083740234375000e+02 +1.446222600876318864e+01 9.558031616210937500e+02 +1.447267391350108667e+01 9.116588134765625000e+02 +1.448312147004318362e+01 8.573433837890625000e+02 +1.449356867813812855e+01 8.054794311523437500e+02 +1.450401553753457229e+01 7.632726440429687500e+02 +1.451446204798117279e+01 7.348009033203125000e+02 +1.452490820922660397e+01 7.166107177734375000e+02 +1.453535402101953977e+01 7.064051513671875000e+02 +1.454579948310866300e+01 7.008240356445312500e+02 +1.455624459524267955e+01 6.975839233398437500e+02 +1.456668935717027757e+01 6.962541503906250000e+02 +1.457713376864017718e+01 6.955492553710937500e+02 +1.458757782940109315e+01 6.959952392578125000e+02 +1.459802153920175805e+01 6.973740234375000000e+02 +1.460846489779090973e+01 7.004251708984375000e+02 +1.461890790491728431e+01 7.062944946289062500e+02 +1.462935056032964098e+01 7.155499877929687500e+02 +1.463979286377674249e+01 7.286762084960937500e+02 +1.465023481500735869e+01 7.444194335937500000e+02 +1.466067641377026476e+01 7.595737304687500000e+02 +1.467111765981425364e+01 7.704754638671875000e+02 +1.468155855288811118e+01 7.739241943359375000e+02 +1.469199909274065163e+01 7.693114624023437500e+02 +1.470243927912068393e+01 7.578452148437500000e+02 +1.471287911177703123e+01 7.436201782226562500e+02 +1.472331859045852021e+01 7.299137573242187500e+02 +1.473375771491398822e+01 7.195794677734375000e+02 +1.474419648489228685e+01 7.146263427734375000e+02 +1.475463490014226942e+01 7.148676147460937500e+02 +1.476507296041279282e+01 7.210720825195312500e+02 +1.477551066545274239e+01 7.332048950195312500e+02 +1.478594801501098921e+01 7.507239990234375000e+02 +1.479638500883642571e+01 7.740746459960937500e+02 +1.480682164667794787e+01 8.016788330078125000e+02 +1.481725792828446586e+01 8.328090820312500000e+02 +1.482769385340489521e+01 8.651865844726562500e+02 +1.483812942178815675e+01 8.938651123046875000e+02 +1.484856463318318021e+01 9.134532470703125000e+02 +1.485899948733891129e+01 9.181367797851562500e+02 +1.486943398400429750e+01 9.052359008789062500e+02 +1.487986812292830052e+01 8.762755126953125000e+02 +1.489030190385988206e+01 8.388368530273437500e+02 +1.490073532654801802e+01 7.991285400390625000e+02 +1.491116839074169320e+01 7.655210571289062500e+02 +1.492160109618990305e+01 7.409478759765625000e+02 +1.493203344264164478e+01 7.247570190429687500e+02 +1.494246542984592985e+01 7.165631103515625000e+02 +1.495289705755177856e+01 7.134932861328125000e+02 +1.496332832550821124e+01 7.149490356445312500e+02 +1.497375923346427129e+01 7.211434936523437500e+02 +1.498418978116899858e+01 7.325695800781250000e+02 +1.499461996837144895e+01 7.511939086914062500e+02 +1.500504979482068357e+01 7.758444824218750000e+02 +1.501547926026577429e+01 8.033033447265625000e+02 +1.502590836445579470e+01 8.279018554687500000e+02 +1.503633710713983618e+01 8.431052856445312500e+02 +1.504676548806699543e+01 8.448269653320312500e+02 +1.505719350698637804e+01 8.330290527343750000e+02 +1.506762116364710025e+01 8.121721191406250000e+02 +1.507804845779827652e+01 7.878616943359375000e+02 +1.508847538918904618e+01 7.683822021484375000e+02 +1.509890195756854681e+01 7.583217163085937500e+02 +1.510932816268592660e+01 7.613522949218750000e+02 +1.511975400429034444e+01 7.809867553710937500e+02 +1.513017948213096275e+01 8.163377075195312500e+02 +1.514060459595696351e+01 8.645189208984375000e+02 +1.515102934551752512e+01 9.189617309570312500e+02 +1.516145373056184020e+01 9.657611083984375000e+02 +1.517187775083911205e+01 9.948972167968750000e+02 +1.518230140609855461e+01 9.983812866210937500e+02 +1.519272469608937648e+01 9.767369995117187500e+02 +1.520314762056081470e+01 9.360689086914062500e+02 +1.521357017926209920e+01 8.886837768554687500e+02 +1.522399237194248300e+01 8.410164184570312500e+02 +1.523441419835121202e+01 8.009737548828125000e+02 +1.524483565823755704e+01 7.701291503906250000e+02 +1.525525675135078174e+01 7.470883789062500000e+02 +1.526567747744017112e+01 7.316399536132812500e+02 +1.527609783625501372e+01 7.208004150390625000e+02 +1.528651782754461053e+01 7.143040161132812500e+02 +1.529693745105826430e+01 7.107585449218750000e+02 +1.530735670654529024e+01 7.091669311523437500e+02 +1.531777559375501951e+01 7.099414062500000000e+02 +1.532819411243677976e+01 7.125107421875000000e+02 +1.533861226233991637e+01 7.179098510742187500e+02 +1.534903004321378006e+01 7.264246826171875000e+02 +1.535944745480773044e+01 7.373665161132812500e+02 +1.536986449687113598e+01 7.497380371093750000e+02 +1.538028116915337762e+01 7.601744384765625000e+02 +1.539069747140383804e+01 7.666937866210937500e+02 +1.540111340337191947e+01 7.672745971679687500e+02 +1.541152896480702239e+01 7.625722045898437500e+02 +1.542194415545855612e+01 7.542581787109375000e+02 +1.543235897507594778e+01 7.460095214843750000e+02 +1.544277342340863335e+01 7.414826049804687500e+02 +1.545318750020604703e+01 7.436962280273437500e+02 +1.546360120521763726e+01 7.568658447265625000e+02 +1.547401453819286488e+01 7.829712524414062500e+02 +1.548442749888120140e+01 8.222162475585937500e+02 +1.549484008703211657e+01 8.725429077148437500e+02 +1.550525230239509966e+01 9.246077270507812500e+02 +1.551566414471963817e+01 9.701860351562500000e+02 +1.552607561375524625e+01 9.997198486328125000e+02 +1.553648670925142561e+01 1.008458435058593750e+03 +1.554689743095770282e+01 9.966508178710937500e+02 +1.555730777862360625e+01 9.687661743164062500e+02 +1.556771775199867314e+01 9.297874145507812500e+02 +1.557812735083245848e+01 8.869936523437500000e+02 +1.558853657487451194e+01 8.444711914062500000e+02 +1.559894542387440275e+01 8.053651733398437500e+02 +1.560935389758170544e+01 7.734443359375000000e+02 +1.561976199574600344e+01 7.484133300781250000e+02 +1.563016971811689260e+01 7.314566650390625000e+02 +1.564057706444397411e+01 7.210183715820312500e+02 +1.565098403447685271e+01 7.156175537109375000e+02 +1.566139062796515979e+01 7.150162963867187500e+02 +1.567179684465852141e+01 7.182182006835937500e+02 +1.568220268430657072e+01 7.266137084960937500e+02 +1.569260814665895865e+01 7.402609252929687500e+02 +1.570301323146534145e+01 7.585636596679687500e+02 +1.571341793847538604e+01 7.797080078125000000e+02 +1.572382226743876821e+01 7.979246826171875000e+02 +1.573422621810516908e+01 8.098950195312500000e+02 +1.574462979022428044e+01 8.114157714843750000e+02 +1.575503298354581005e+01 8.025253906250000000e+02 +1.576543579781946214e+01 7.854091796875000000e+02 +1.577583823279496222e+01 7.654338989257812500e+02 +1.578624028822203584e+01 7.472041015625000000e+02 +1.579664196385042629e+01 7.344868774414062500e+02 +1.580704325942988042e+01 7.299106445312500000e+02 +1.581744417471014685e+01 7.326730346679687500e+02 +1.582784470944100086e+01 7.427111206054687500e+02 +1.583824486337221771e+01 7.565376586914062500e+02 +1.584864463625357800e+01 7.705859985351562500e+02 +1.585904402783487299e+01 7.808970947265625000e+02 +1.586944303786590993e+01 7.836870117187500000e+02 +1.587984166609650138e+01 7.783323364257812500e+02 +1.589023991227646526e+01 7.660123291015625000e+02 +1.590063777615563012e+01 7.500524291992187500e+02 +1.591103525748384051e+01 7.337304687500000000e+02 +1.592143235601094275e+01 7.200535888671875000e+02 +1.593182907148679028e+01 7.101273193359375000e+02 +1.594222540366125962e+01 7.034216308593750000e+02 +1.595262135228421840e+01 6.997868652343750000e+02 +1.596301691710555382e+01 6.976027221679687500e+02 +1.597341209787516725e+01 6.967744750976562500e+02 +1.598380689434295654e+01 6.963051757812500000e+02 +1.599420130625883374e+01 6.964200439453125000e+02 +1.600459533337272333e+01 6.970819702148437500e+02 +1.601498897543455868e+01 6.980866699218750000e+02 +1.602538223219427849e+01 7.002362060546875000e+02 +1.603577510340183565e+01 7.028278808593750000e+02 +1.604616758880718308e+01 7.063532714843750000e+02 +1.605655968816029855e+01 7.097729492187500000e+02 +1.606695140121115628e+01 7.127152709960937500e+02 +1.607734272770974471e+01 7.146091918945312500e+02 +1.608773366740605582e+01 7.149381713867187500e+02 +1.609812422005009935e+01 7.147639160156250000e+02 +1.610851438539189218e+01 7.143027343750000000e+02 +1.611890416318145824e+01 7.155864868164062500e+02 +1.612929355316882862e+01 7.197643432617187500e+02 +1.613968255510404504e+01 7.296390380859375000e+02 +1.615007116873717052e+01 7.467438354492187500e+02 +1.616045939381825747e+01 7.715459594726562500e+02 +1.617084723009737957e+01 8.035701904296875000e+02 +1.618123467732461407e+01 8.368737182617187500e+02 +1.619162173525005599e+01 8.677196655273437500e+02 +1.620200840362380745e+01 8.884152832031250000e+02 +1.621239468219597057e+01 8.965267333984375000e+02 +1.622278057071666169e+01 8.911193237304687500e+02 +1.623316606893601843e+01 8.742457885742187500e+02 +1.624355117660417136e+01 8.499126586914062500e+02 +1.625393589347126522e+01 8.223900146484375000e+02 +1.626432021928745897e+01 7.947615356445312500e+02 +1.627470415380291513e+01 7.704838256835937500e+02 +1.628508769676781043e+01 7.502926635742187500e+02 +1.629547084793233225e+01 7.355236816406250000e+02 +1.630585360704666442e+01 7.258424072265625000e+02 +1.631623597386101565e+01 7.210970458984375000e+02 +1.632661794812559819e+01 7.207870483398437500e+02 +1.633699952959063850e+01 7.259490356445312500e+02 +1.634738071800635595e+01 7.365177612304687500e+02 +1.635776151312300186e+01 7.548806152343750000e+02 +1.636814191469082047e+01 7.791937866210937500e+02 +1.637852192246007732e+01 8.081868286132812500e+02 +1.638890153618103085e+01 8.362327880859375000e+02 +1.639928075560396792e+01 8.565739746093750000e+02 +1.640965958047917539e+01 8.650573120117187500e+02 +1.642003801055695078e+01 8.584819946289062500e+02 +1.643041604558759872e+01 8.399564819335937500e+02 +1.644079368532143448e+01 8.153006591796875000e+02 +1.645117092950879112e+01 7.910407104492187500e+02 +1.646154777789999812e+01 7.741557006835937500e+02 +1.647192423024540275e+01 7.682584228515625000e+02 +1.648230028629535937e+01 7.750477905273437500e+02 +1.649267594580022944e+01 7.925957031250000000e+02 +1.650305120851038865e+01 8.183408813476562500e+02 +1.651342607417621977e+01 8.442299194335937500e+02 +1.652380054254811981e+01 8.668970947265625000e+02 +1.653417461337648220e+01 8.791641235351562500e+02 +1.654454828641172881e+01 8.798620605468750000e+02 +1.655492156140427440e+01 8.692528686523437500e+02 +1.656529443810455149e+01 8.497137451171875000e+02 +1.657566691626299971e+01 8.254815063476562500e+02 +1.658603899563006934e+01 8.001793212890625000e+02 +1.659641067595622488e+01 7.763156738281250000e+02 +1.660678195699193083e+01 7.561918945312500000e+02 +1.661715283848766589e+01 7.401016235351562500e+02 +1.662752332019391943e+01 7.288314819335937500e+02 +1.663789340186119148e+01 7.213060302734375000e+02 +1.664826308323999271e+01 7.173514404296875000e+02 +1.665863236408083381e+01 7.159221191406250000e+02 +1.666900124413424322e+01 7.170377197265625000e+02 +1.667936972315075650e+01 7.201383666992187500e+02 +1.668973780088092695e+01 7.258416748046875000e+02 +1.670010547707530790e+01 7.325659179687500000e+02 +1.671047275148445976e+01 7.397742919921875000e+02 +1.672083962385896427e+01 7.457292480468750000e+02 +1.673120609394939962e+01 7.488215942382812500e+02 +1.674157216150636884e+01 7.487069702148437500e+02 +1.675193782628046790e+01 7.451619873046875000e+02 +1.676230308802231761e+01 7.405700683593750000e+02 +1.677266794648254233e+01 7.363184814453125000e+02 +1.678303240141177000e+01 7.350848388671875000e+02 +1.679339645256064273e+01 7.383744506835937500e+02 +1.680376009967982043e+01 7.482165527343750000e+02 +1.681412334251996299e+01 7.637478027343750000e+02 +1.682448618083174452e+01 7.841622924804687500e+02 +1.683484861436583913e+01 8.070129394531250000e+02 +1.684521064287294578e+01 8.272522583007812500e+02 +1.685557226610376702e+01 8.436844482421875000e+02 +1.686593348380900892e+01 8.518140258789062500e+02 +1.687629429573939888e+01 8.518258056640625000e+02 +1.688665470164566429e+01 8.432887573242187500e+02 +1.689701470127853966e+01 8.278353271484375000e+02 +1.690737429438878436e+01 8.084746704101562500e+02 +1.691773348072715777e+01 7.871266479492187500e+02 +1.692809226004442635e+01 7.673193359375000000e+02 +1.693845063209137081e+01 7.507359619140625000e+02 +1.694880859661878247e+01 7.385547485351562500e+02 +1.695916615337745625e+01 7.315543823242187500e+02 +1.696952330211820481e+01 7.294022216796875000e+02 +1.697988004259184791e+01 7.326395263671875000e+02 +1.699023637454921598e+01 7.421691284179687500e+02 +1.700059229774114300e+01 7.585292358398437500e+02 +1.701094781191847716e+01 7.806693725585937500e+02 +1.702130291683207730e+01 8.071004028320312500e+02 +1.703165761223281649e+01 8.315775756835937500e+02 +1.704201189787156778e+01 8.498919067382812500e+02 +1.705236577349922200e+01 8.563206787109375000e+02 +1.706271923886667707e+01 8.498665161132812500e+02 +1.707307229372483803e+01 8.331644897460937500e+02 +1.708342493782462057e+01 8.099622192382812500e+02 +1.709377717091695814e+01 7.886770629882812500e+02 +1.710412899275278065e+01 7.732526245117187500e+02 +1.711448040308304286e+01 7.681939086914062500e+02 +1.712483140165869244e+01 7.732213134765625000e+02 +1.713518198823070549e+01 7.868538208007812500e+02 +1.714553216255005808e+01 8.032000732421875000e+02 +1.715588192436772985e+01 8.179378662109375000e+02 +1.716623127343472532e+01 8.252298583984375000e+02 +1.717658020950204545e+01 8.224810180664062500e+02 +1.718692873232070895e+01 8.106398925781250000e+02 +1.719727684164174875e+01 7.915667114257812500e+02 +1.720762453721618712e+01 7.707756347656250000e+02 +1.721797181879507832e+01 7.512111206054687500e+02 +1.722831868612948725e+01 7.357273559570312500e+02 +1.723866513897046460e+01 7.250925903320312500e+02 +1.724901117706909659e+01 7.185461425781250000e+02 +1.725935680017646234e+01 7.150523071289062500e+02 +1.726970200804366584e+01 7.136519775390625000e+02 +1.728004680042181107e+01 7.141416625976562500e+02 +1.729039117706201267e+01 7.162445678710937500e+02 +1.730073513771539595e+01 7.207252197265625000e+02 +1.731107868213310397e+01 7.275888671875000000e+02 +1.732142181006627268e+01 7.373516235351562500e+02 +1.733176452126606293e+01 7.486511230468750000e+02 +1.734210681548364263e+01 7.602707519531250000e+02 +1.735244869247019039e+01 7.688552856445312500e+02 +1.736279015197688835e+01 7.733700561523437500e+02 +1.737313119375492931e+01 7.723006591796875000e+02 +1.738347181755553450e+01 7.666949462890625000e+02 +1.739381202312990027e+01 7.592193603515625000e+02 +1.740415181022926561e+01 7.530209350585937500e+02 +1.741449117860486950e+01 7.517874755859375000e+02 +1.742483012800794739e+01 7.578535156250000000e+02 +1.743516865818976669e+01 7.726395263671875000e+02 +1.744550676890159124e+01 7.936083984375000000e+02 +1.745584445989469558e+01 8.182658691406250000e+02 +1.746618173092036841e+01 8.403273315429687500e+02 +1.747651858172990558e+01 8.554758300781250000e+02 +1.748685501207462067e+01 8.604420776367187500e+02 +1.749719102170582374e+01 8.539533691406250000e+02 +1.750752661037484259e+01 8.386215209960937500e+02 +1.751786177783302278e+01 8.170733032226562500e+02 +1.752819652383169924e+01 7.937507934570312500e+02 +1.753853084812224239e+01 7.715438842773437500e+02 +1.754886475045601912e+01 7.523465576171875000e+02 +1.755919823058439633e+01 7.372171630859375000e+02 +1.756953128825876931e+01 7.263425292968750000e+02 +1.757986392323054403e+01 7.192658081054687500e+02 +1.759019613525111936e+01 7.151580810546875000e+02 +1.760052792407191902e+01 7.136967773437500000e+02 +1.761085928944436674e+01 7.141885986328125000e+02 +1.762119023111990757e+01 7.169241333007812500e+02 +1.763152074884999010e+01 7.209940795898437500e+02 +1.764185084238607004e+01 7.261282958984375000e+02 +1.765218051147962086e+01 7.306380615234375000e+02 +1.766250975588212313e+01 7.340248413085937500e+02 +1.767283857534506453e+01 7.349460449218750000e+02 +1.768316696961995049e+01 7.335153198242187500e+02 +1.769349493845828647e+01 7.304680786132812500e+02 +1.770382248161159211e+01 7.270038452148437500e+02 +1.771414959883140838e+01 7.248871459960937500e+02 +1.772447628986926560e+01 7.251074829101562500e+02 +1.773480255447672249e+01 7.291762084960937500e+02 +1.774512839240533779e+01 7.367758789062500000e+02 +1.775545380340668444e+01 7.479902343750000000e+02 +1.776577878723234605e+01 7.605355224609375000e+02 +1.777610334363391331e+01 7.734619750976562500e+02 +1.778642747236298760e+01 7.842545166015625000e+02 +1.779675117317119515e+01 7.911639404296875000e+02 +1.780707444581014087e+01 7.933287963867187500e+02 +1.781739729003147588e+01 7.894792480468750000e+02 +1.782771970558683350e+01 7.812161254882812500e+02 +1.783804169222786840e+01 7.688672485351562500e+02 +1.784836324970624943e+01 7.554520263671875000e+02 +1.785868437777365259e+01 7.419759521484375000e+02 +1.786900507618176448e+01 7.303790893554687500e+02 +1.787932534468227175e+01 7.213001098632812500e+02 +1.788964518302688944e+01 7.153527832031250000e+02 +1.789996459096733616e+01 7.113712158203125000e+02 +1.791028356825532697e+01 7.092667846679687500e+02 +1.792060211464261243e+01 7.080697631835937500e+02 +1.793092022988093603e+01 7.075989990234375000e+02 +1.794123791372205190e+01 7.076199340820312500e+02 +1.795155516591773548e+01 7.076071166992187500e+02 +1.796187198621975512e+01 7.081680908203125000e+02 +1.797218837437990757e+01 7.087044677734375000e+02 +1.798250433014999317e+01 7.101602783203125000e+02 +1.799281985328181932e+01 7.120059204101562500e+02 +1.800313494352720411e+01 7.155232543945312500e+02 +1.801344960063798695e+01 7.212122192382812500e+02 +1.802376382436600366e+01 7.306028442382812500e+02 +1.803407761446310431e+01 7.437815551757812500e+02 +1.804439097068114961e+01 7.614805908203125000e+02 +1.805470389277201448e+01 7.818305664062500000e+02 +1.806501638048758451e+01 8.022124023437500000e+02 +1.807532843357974883e+01 8.202294921875000000e+02 +1.808564005180042145e+01 8.315443725585937500e+02 +1.809595123490150215e+01 8.359487304687500000e+02 +1.810626198263492270e+01 8.314569091796875000e+02 +1.811657229475262554e+01 8.205117187500000000e+02 +1.812688217100653887e+01 8.044221191406250000e+02 +1.813719161114863709e+01 7.862747802734375000e+02 +1.814750061493088040e+01 7.686583862304687500e+02 +1.815780918210523964e+01 7.537433471679687500e+02 +1.816811731242371053e+01 7.420435791015625000e+02 +1.817842500563828878e+01 7.347814941406250000e+02 +1.818873226150098077e+01 7.317902221679687500e+02 +1.819903907976381419e+01 7.338291625976562500e+02 +1.820934546017880962e+01 7.407465820312500000e+02 +1.821965140249801252e+01 7.523010864257812500e+02 +1.822995690647346834e+01 7.680565185546875000e+02 +1.824026197185724030e+01 7.845722656250000000e+02 +1.825056659840140938e+01 7.999602661132812500e+02 +1.826087078585804946e+01 8.097140502929687500e+02 +1.827117453397925573e+01 8.126909179687500000e+02 +1.828147784251713048e+01 8.079832153320312500e+02 +1.829178071122379023e+01 7.984093017578125000e+02 +1.830208313985136215e+01 7.877937011718750000e+02 +1.831238512815197694e+01 7.807188110351562500e+02 +1.832268667587777955e+01 7.808506469726562500e+02 +1.833298778278093266e+01 7.897702636718750000e+02 +1.834328844861359897e+01 8.058382568359375000e+02 +1.835358867312795539e+01 8.245750122070312500e+02 +1.836388845607619658e+01 8.410839233398437500e+02 +1.837418779721051720e+01 8.493847656250000000e+02 +1.838448669628312970e+01 8.474378051757812500e+02 +1.839478515304625006e+01 8.343650512695312500e+02 +1.840508316725211202e+01 8.138207397460937500e+02 +1.841538073865295999e+01 7.900282592773437500e+02 +1.842567786700103483e+01 7.676504516601562500e+02 +1.843597455204861646e+01 7.489623413085937500e+02 +1.844627079354796706e+01 7.356078491210937500e+02 +1.845656659125137011e+01 7.267136840820312500e+02 +1.846686194491113042e+01 7.216346435546875000e+02 +1.847715685427954924e+01 7.188361816406250000e+02 +1.848745131910893136e+01 7.173605957031250000e+02 +1.849774533915162777e+01 7.167833251953125000e+02 +1.850803891415995395e+01 7.166943359375000000e+02 +1.851833204388626797e+01 7.173260498046875000e+02 +1.852862472808292793e+01 7.180328979492187500e+02 +1.853891696650230259e+01 7.190219726562500000e+02 +1.854920875889678200e+01 7.197586669921875000e+02 +1.855950010501873848e+01 7.205858764648437500e+02 +1.856979100462059407e+01 7.211040039062500000e+02 +1.858008145745474593e+01 7.221997680664062500e+02 +1.859037146327362322e+01 7.242365722656250000e+02 +1.860066102182967285e+01 7.285671997070312500e+02 +1.861095013287531685e+01 7.357761840820312500e+02 +1.862123879616302702e+01 7.468242797851562500e+02 +1.863152701144526446e+01 7.611619262695312500e+02 +1.864181477847450807e+01 7.780957031250000000e+02 +1.865210209700324739e+01 7.952112426757812500e+02 +1.866238896678397907e+01 8.101705322265625000e+02 +1.867267538756921397e+01 8.203851318359375000e+02 +1.868296135911147360e+01 8.241625976562500000e+02 +1.869324688116328659e+01 8.212044067382812500e+02 +1.870353195347719577e+01 8.116358642578125000e+02 +1.871381657580575819e+01 7.977703857421875000e+02 +1.872410074790153445e+01 7.814544067382812500e+02 +1.873438446951709935e+01 7.655276489257812500e+02 +1.874466774040503836e+01 7.514714965820312500e+02 +1.875495056031794761e+01 7.411381225585937500e+02 +1.876523292900843742e+01 7.348399047851562500e+02 +1.877551484622911815e+01 7.332492675781250000e+02 +1.878579631173262854e+01 7.362891235351562500e+02 +1.879607732527160380e+01 7.439417724609375000e+02 +1.880635788659869334e+01 7.551285400390625000e+02 +1.881663799546656080e+01 7.681809082031250000e+02 +1.882691765162788045e+01 7.801627807617187500e+02 +1.883719685483532658e+01 7.885447387695312500e+02 +1.884747560484160900e+01 7.912915039062500000e+02 +1.885775390139941976e+01 7.877946166992187500e+02 +1.886803174426147933e+01 7.792626953125000000e+02 +1.887830913318051174e+01 7.677278442382812500e+02 +1.888858606790925876e+01 7.567703857421875000e+02 +1.889886254820047284e+01 7.482346801757812500e+02 +1.890913857380690644e+01 7.439717407226562500e+02 +1.891941414448133330e+01 7.433406982421875000e+02 +1.892968925997653074e+01 7.456917114257812500e+02 +1.893996392004529739e+01 7.490365600585937500e+02 +1.895023812444043898e+01 7.517507934570312500e+02 +1.896051187291475770e+01 7.524260253906250000e+02 +1.897078516522109126e+01 7.509691772460937500e+02 +1.898105800111227737e+01 7.470729370117187500e+02 +1.899133038034115373e+01 7.417830200195312500e+02 +1.900160230266058292e+01 7.356446533203125000e+02 +1.901187376782343108e+01 7.298914184570312500e+02 +1.902214477558258565e+01 7.250061035156250000e+02 +1.903241532569093764e+01 7.214038085937500000e+02 +1.904268541790138158e+01 7.193383178710937500e+02 +1.905295505196683692e+01 7.188428344726562500e+02 +1.906322422764022662e+01 7.202707519531250000e+02 +1.907349294467448431e+01 7.233595581054687500e+02 +1.908376120282256494e+01 7.282464599609375000e+02 +1.909402900183741636e+01 7.341683349609375000e+02 +1.910429634147200773e+01 7.404623413085937500e+02 +1.911456322147932951e+01 7.456713867187500000e+02 +1.912482964161236154e+01 7.487450561523437500e+02 +1.913509560162410494e+01 7.492740478515625000e+02 +1.914536110126757862e+01 7.479727172851562500e+02 +1.915562614029580146e+01 7.458661499023437500e+02 +1.916589071846182080e+01 7.449769287109375000e+02 +1.917615483551866618e+01 7.471109619140625000e+02 +1.918641849121939558e+01 7.536306152343750000e+02 +1.919668168531709540e+01 7.646304321289062500e+02 +1.920694441756482718e+01 7.786569213867187500e+02 +1.921720668771569152e+01 7.937110595703125000e+02 +1.922746849552278192e+01 8.068484497070312500e+02 +1.923772984073921322e+01 8.155707397460937500e+02 +1.924799072311811798e+01 8.180156250000000000e+02 +1.925825114241262526e+01 8.136597290039062500e+02 +1.926851109837587828e+01 8.037609252929687500e+02 +1.927877059076103450e+01 7.898468627929687500e+02 +1.928902961932126559e+01 7.742752685546875000e+02 +1.929928818380975741e+01 7.590690307617187500e+02 +1.930954628397967809e+01 7.456500854492187500e+02 +1.931980391958425614e+01 7.356265869140625000e+02 +1.933006109037669518e+01 7.286975097656250000e+02 +1.934031779611021307e+01 7.255880126953125000e+02 +1.935057403653805608e+01 7.252098388671875000e+02 +1.936082981141345627e+01 7.277471923828125000e+02 +1.937108512048968834e+01 7.318074951171875000e+02 +1.938133996352001276e+01 7.365597534179687500e+02 +1.939159434025770423e+01 7.406057128906250000e+02 +1.940184825045606942e+01 7.431658935546875000e+02 +1.941210169386839368e+01 7.435819091796875000e+02 +1.942235467024800499e+01 7.418815917968750000e+02 +1.943260717934822424e+01 7.388342895507812500e+02 +1.944285922092238295e+01 7.361294555664062500e+02 +1.945311079472384108e+01 7.347207031250000000e+02 +1.946336190050594084e+01 7.356623535156250000e+02 +1.947361253802206349e+01 7.386731567382812500e+02 +1.948386270702559031e+01 7.431148071289062500e+02 +1.949411240726990968e+01 7.474107055664062500e+02 +1.950436163850843130e+01 7.499967651367187500e+02 +1.951461040049456130e+01 7.498016967773437500e+02 +1.952485869298173427e+01 7.468599853515625000e+02 +1.953510651572338475e+01 7.415177612304687500e+02 +1.954535386847297218e+01 7.350593872070312500e+02 +1.955560075098393824e+01 7.284838867187500000e+02 +1.956584716300977078e+01 7.230425415039062500e+02 +1.957609310430394700e+01 7.190072021484375000e+02 +1.958633857461995120e+01 7.164344482421875000e+02 +1.959658357371131387e+01 7.152702636718750000e+02 +1.960682810133152998e+01 7.153690185546875000e+02 +1.961707215723413711e+01 7.169556274414062500e+02 +1.962731574117267641e+01 7.191832275390625000e+02 +1.963755885290068903e+01 7.222608032226562500e+02 +1.964780149217175165e+01 7.260382080078125000e+02 +1.965804365873942672e+01 7.302303466796875000e+02 +1.966828535235730513e+01 7.334452514648437500e+02 +1.967852657277899198e+01 7.355386352539062500e+02 +1.968876731975807459e+01 7.359983520507812500e+02 +1.969900759304818649e+01 7.357324218750000000e+02 +1.970924739240295764e+01 7.351773681640625000e+02 +1.971948671757602511e+01 7.359357299804687500e+02 +1.972972556832105084e+01 7.385322265625000000e+02 +1.973996394439169677e+01 7.440266723632812500e+02 +1.975020184554163549e+01 7.514941406250000000e+02 +1.976043927152455737e+01 7.603914794921875000e+02 +1.977067622209415987e+01 7.684024658203125000e+02 +1.978091269700416177e+01 7.745125122070312500e+02 +1.979114869600827475e+01 7.769787597656250000e+02 +1.980138421886023181e+01 7.756077880859375000e+02 +1.981161926531379436e+01 7.705910034179687500e+02 +1.982185383512269894e+01 7.632259521484375000e+02 +1.983208792804073184e+01 7.546664428710937500e+02 +1.984232154382165447e+01 7.463341064453125000e+02 +1.985255468221927444e+01 7.393134765625000000e+02 +1.986278734298737803e+01 7.345744018554687500e+02 +1.987301952587979414e+01 7.325396118164062500e+02 +1.988325123065033395e+01 7.338235473632812500e+02 +1.989348245705285478e+01 7.383938598632812500e+02 +1.990371320484118556e+01 7.465221557617187500e+02 +1.991394347376919427e+01 7.566868896484375000e+02 +1.992417326359075247e+01 7.680862426757812500e+02 +1.993440257405974236e+01 7.781165771484375000e+02 +1.994463140493006392e+01 7.853065185546875000e+02 +1.995485975595561356e+01 7.878267822265625000e+02 +1.996508762689031258e+01 7.861246337890625000e+02 +1.997531501748809291e+01 7.818289794921875000e+02 +1.998554192750289360e+01 7.778423461914062500e+02 +1.999576835668866792e+01 7.764342041015625000e+02 +2.000599430479937979e+01 7.798640747070312500e+02 +2.001621977158900023e+01 7.877948608398437500e+02 +2.002644475681152869e+01 7.992038574218750000e+02 +2.003666926022094330e+01 8.103577880859375000e+02 +2.004689328157127548e+01 8.186799926757812500e+02 +2.005711682061653534e+01 8.212789916992187500e+02 +2.006733987711075429e+01 8.175823364257812500e+02 +2.007756245080799218e+01 8.077850341796875000e+02 +2.008778454146228398e+01 7.939702148437500000e+02 +2.009800614882771441e+01 7.783119506835937500e+02 +2.010822727265836107e+01 7.631416625976562500e+02 +2.011844791270831223e+01 7.503002319335937500e+02 +2.012866806873168102e+01 7.401235961914062500e+02 +2.013888774048255925e+01 7.328850708007812500e+02 +2.014910692771509204e+01 7.283583984375000000e+02 +2.015932563018341384e+01 7.260407104492187500e+02 +2.016954384764166974e+01 7.256252441406250000e+02 +2.017976157984403329e+01 7.262089843750000000e+02 +2.018997882654466025e+01 7.275722045898437500e+02 +2.020019558749774902e+01 7.288049926757812500e+02 +2.021041186245749444e+01 7.298301391601562500e+02 +2.022062765117810201e+01 7.302282104492187500e+02 +2.023084295341379146e+01 7.303757324218750000e+02 +2.024105776891878961e+01 7.302671508789062500e+02 +2.025127209744734813e+01 7.307817382812500000e+02 +2.026148593875372228e+01 7.323646850585937500e+02 +2.027169929259217085e+01 7.359880371093750000e+02 +2.028191215871697750e+01 7.414580688476562500e+02 +2.029212453688243656e+01 7.486724243164062500e+02 +2.030233642684284590e+01 7.563472290039062500e+02 +2.031254782835251049e+01 7.637346191406250000e+02 +2.032275874116577086e+01 7.690167236328125000e+02 +2.033296916503695329e+01 7.715920410156250000e+02 +2.034317909972041605e+01 7.706908569335937500e+02 +2.035338854497051742e+01 7.664128417968750000e+02 +2.036359750054161566e+01 7.597145385742187500e+02 +2.037380596618811524e+01 7.515830078125000000e+02 +2.038401394166440284e+01 7.433748779296875000e+02 +2.039422142672489002e+01 7.361386108398437500e+02 +2.040442842112399191e+01 7.306543579101562500e+02 +2.041463492461613782e+01 7.273844604492187500e+02 +2.042484093695578196e+01 7.263282470703125000e+02 +2.043504645789736784e+01 7.274461059570312500e+02 +2.044525148719537100e+01 7.297661743164062500e+02 +2.045545602460426693e+01 7.328120117187500000e+02 +2.046566006987854536e+01 7.354628906250000000e+02 +2.047586362277271377e+01 7.375125122070312500e+02 +2.048606668304126899e+01 7.383669433593750000e+02 +2.049626925043875758e+01 7.384850463867187500e+02 +2.050647132471971545e+01 7.383836059570312500e+02 +2.051667290563868207e+01 7.394353027343750000e+02 +2.052687399295023241e+01 7.421872558593750000e+02 +2.053707458640892014e+01 7.473052978515625000e+02 +2.054727468576934513e+01 7.538310546875000000e+02 +2.055747429078611077e+01 7.608587646484375000e+02 +2.056767340121380983e+01 7.660640869140625000e+02 +2.057787201680707412e+01 7.682510375976562500e+02 +2.058807013732053548e+01 7.666866455078125000e+02 +2.059826776250883640e+01 7.614147949218750000e+02 +2.060846489212663712e+01 7.535036010742187500e+02 +2.061866152592860502e+01 7.445009155273437500e+02 +2.062885766366942164e+01 7.358319091796875000e+02 +2.063905330510377567e+01 7.286661987304687500e+02 +2.064924844998637354e+01 7.233254394531250000e+02 +2.065944309807193591e+01 7.198572387695312500e+02 +2.066963724911518341e+01 7.178672485351562500e+02 +2.067983090287086512e+01 7.173746337890625000e+02 +2.069002405909373365e+01 7.177894287109375000e+02 +2.070021671753854520e+01 7.189439086914062500e+02 +2.071040887796007723e+01 7.203477783203125000e+02 +2.072060054011312857e+01 7.220920410156250000e+02 +2.073079170375248736e+01 7.234033813476562500e+02 +2.074098236863297728e+01 7.244211425781250000e+02 +2.075117253450941490e+01 7.249796142578125000e+02 +2.076136220113663100e+01 7.258204345703125000e+02 +2.077155136826949189e+01 7.272450561523437500e+02 +2.078174003566283901e+01 7.301855468750000000e+02 +2.079192820307155998e+01 7.346543579101562500e+02 +2.080211587025053532e+01 7.409938964843750000e+02 +2.081230303695464912e+01 7.484417724609375000e+02 +2.082248970293882095e+01 7.564343872070312500e+02 +2.083267586795797399e+01 7.631658935546875000e+02 +2.084286153176703493e+01 7.680114135742187500e+02 +2.085304669412095890e+01 7.695377197265625000e+02 +2.086323135477468327e+01 7.681224975585937500e+02 +2.087341551348319513e+01 7.633532714843750000e+02 +2.088359917000146027e+01 7.565722656250000000e+02 +2.089378232408448355e+01 7.484278564453125000e+02 +2.090396497548726984e+01 7.404372558593750000e+02 +2.091414712396482756e+01 7.334875488281250000e+02 +2.092432876927219354e+01 7.284376220703125000e+02 +2.093450991116440818e+01 7.255488281250000000e+02 +2.094469054939651897e+01 7.245983886718750000e+02 +2.095487068372360184e+01 7.254913330078125000e+02 +2.096505031390072560e+01 7.273918457031250000e+02 +2.097522943968297682e+01 7.297587890625000000e+02 +2.098540806082547761e+01 7.318069458007812500e+02 +2.099558617708331809e+01 7.331336059570312500e+02 +2.100576378821164170e+01 7.333161010742187500e+02 +2.101594089396557052e+01 7.328394165039062500e+02 +2.102611749410027642e+01 7.319863281250000000e+02 +2.103629358837090280e+01 7.319127807617187500e+02 +2.104646917653263216e+01 7.328280029296875000e+02 +2.105664425834065412e+01 7.353099975585937500e+02 +2.106681883355016538e+01 7.389378051757812500e+02 +2.107699290191638042e+01 7.434800415039062500e+02 +2.108716646319451726e+01 7.474108886718750000e+02 +2.109733951713981170e+01 7.505890502929687500e+02 +2.110751206350751730e+01 7.515474243164062500e+02 +2.111768410205289470e+01 7.507997436523437500e+02 +2.112785563253121168e+01 7.476127319335937500e+02 +2.113802665469775022e+01 7.432864379882812500e+02 +2.114819716830782070e+01 7.379052124023437500e+02 +2.115836717311671578e+01 7.329539184570312500e+02 +2.116853666887977070e+01 7.285154418945312500e+02 +2.117870565535230654e+01 7.254807128906250000e+02 +2.118887413228967986e+01 7.241130981445312500e+02 +2.119904209944724727e+01 7.243327026367187500e+02 +2.120920955658036888e+01 7.256284790039062500e+02 +2.121937650344444393e+01 7.276065673828125000e+02 +2.122954293979485740e+01 7.301130981445312500e+02 +2.123970886538701208e+01 7.322709350585937500e+02 +2.124987427997634271e+01 7.338018188476562500e+02 +2.126003918331826270e+01 7.346123657226562500e+02 +2.127020357516822457e+01 7.354290771484375000e+02 +2.128036745528169504e+01 7.364980468750000000e+02 +2.129053082341413017e+01 7.390200195312500000e+02 +2.130069367932101798e+01 7.430181884765625000e+02 +2.131085602275783941e+01 7.488828735351562500e+02 +2.132101785348011092e+01 7.550213623046875000e+02 +2.133117917124335605e+01 7.612274780273437500e+02 +2.134133997580309483e+01 7.653481445312500000e+02 +2.135150026691487213e+01 7.671152954101562500e+02 +2.136166004433424703e+01 7.654270629882812500e+02 +2.137181930781677863e+01 7.613865356445312500e+02 +2.138197805711805799e+01 7.550834960937500000e+02 +2.139213629199367261e+01 7.481815185546875000e+02 +2.140229401219922067e+01 7.414109497070312500e+02 +2.141245121749032876e+01 7.358438720703125000e+02 +2.142260790762262346e+01 7.323726196289062500e+02 +2.143276408235174557e+01 7.311983642578125000e+02 +2.144291974143334301e+01 7.325395507812500000e+02 +2.145307488462309209e+01 7.359094848632812500e+02 +2.146322951167666915e+01 7.410588378906250000e+02 +2.147338362234976827e+01 7.466958618164062500e+02 +2.148353721639808001e+01 7.521638183593750000e+02 +2.149369029357733751e+01 7.558268432617187500e+02 +2.150384285364325976e+01 7.575520629882812500e+02 +2.151399489635159767e+01 7.567597045898437500e+02 +2.152414642145809154e+01 7.546224365234375000e+02 +2.153429742871851005e+01 7.518607177734375000e+02 +2.154444791788863967e+01 7.498885498046875000e+02 +2.155459788872426685e+01 7.489967041015625000e+02 +2.156474734098119583e+01 7.495333251953125000e+02 +2.157489627441524505e+01 7.505997924804687500e+02 +2.158504468878222937e+01 7.515296020507812500e+02 +2.159519258383800633e+01 7.512020263671875000e+02 +2.160533995933841922e+01 7.491229858398437500e+02 +2.161548681503933977e+01 7.455787353515625000e+02 +2.162563315069664327e+01 7.407432861328125000e+02 +2.163577896606622630e+01 7.356558227539062500e+02 +2.164592426090398902e+01 7.306895751953125000e+02 +2.165606903496583513e+01 7.267203369140625000e+02 +2.166621328800770740e+01 7.238385620117187500e+02 +2.167635701978554863e+01 7.222175903320312500e+02 +2.168650023005530159e+01 7.214168090820312500e+02 +2.169664291857293748e+01 7.214753417968750000e+02 +2.170678508509444171e+01 7.220202026367187500e+02 +2.171692672937579260e+01 7.230988769531250000e+02 +2.172706785117300754e+01 7.243219604492187500e+02 +2.173720845024208970e+01 7.254447021484375000e+02 +2.174734852633907423e+01 7.263069458007812500e+02 +2.175748807922000339e+01 7.270634765625000000e+02 +2.176762710864093009e+01 7.281474609375000000e+02 +2.177776561435791791e+01 7.297429809570312500e+02 +2.178790359612705529e+01 7.325150756835937500e+02 +2.179804105370442358e+01 7.365950927734375000e+02 +2.180817798684613251e+01 7.419956054687500000e+02 +2.181831439530829897e+01 7.482781982421875000e+02 +2.182845027884705402e+01 7.549304809570312500e+02 +2.183858563721853585e+01 7.609252929687500000e+02 +2.184872047017890395e+01 7.651530761718750000e+02 +2.185885477748432137e+01 7.667233886718750000e+02 +2.186898855889097248e+01 7.657097778320312500e+02 +2.187912181415504875e+01 7.620469360351562500e+02 +2.188925454303275941e+01 7.565317382812500000e+02 +2.189938674528031370e+01 7.500823974609375000e+02 +2.190951842065394928e+01 7.441542968750000000e+02 +2.191964956890991090e+01 7.397164306640625000e+02 +2.192978018980444332e+01 7.371303100585937500e+02 +2.193991028309383751e+01 7.370772094726562500e+02 +2.195003984853434886e+01 7.390100708007812500e+02 +2.196016888588229321e+01 7.422629394531250000e+02 +2.197029739489397571e+01 7.462427368164062500e+02 +2.198042537532569440e+01 7.497037353515625000e+02 +2.199055282693380420e+01 7.518077392578125000e+02 +2.200067974947464577e+01 7.521623535156250000e+02 +2.201080614270456692e+01 7.508613891601562500e+02 +2.202093200637996162e+01 7.489658203125000000e+02 +2.203105734025718476e+01 7.469713745117187500e+02 +2.204118214409264453e+01 7.457992553710937500e+02 +2.205130641764275623e+01 7.459979858398437500e+02 +2.206143016066393514e+01 7.471159667968750000e+02 +2.207155337291261787e+01 7.486980590820312500e+02 +2.208167605414524814e+01 7.498161010742187500e+02 +2.209179820411828743e+01 7.498402099609375000e+02 +2.210191982258821497e+01 7.483643798828125000e+02 +2.211204090931150290e+01 7.455035400390625000e+02 +2.212216146404466244e+01 7.414937744140625000e+02 +2.213228148654420124e+01 7.370115356445312500e+02 +2.214240097656663409e+01 7.324604492187500000e+02 +2.215251993386851481e+01 7.284616088867187500e+02 +2.216263835820637595e+01 7.252160034179687500e+02 +2.217275624933678912e+01 7.227407836914062500e+02 +2.218287360701633304e+01 7.210395507812500000e+02 +2.219299043100158642e+01 7.199434814453125000e+02 +2.220310672104915639e+01 7.193479614257812500e+02 +2.221322247691565366e+01 7.190631713867187500e+02 +2.222333769835770312e+01 7.192715454101562500e+02 +2.223345238513195099e+01 7.199358520507812500e+02 +2.224356653699504704e+01 7.214963989257812500e+02 +2.225368015370365526e+01 7.239962158203125000e+02 +2.226379323501444674e+01 7.278535156250000000e+02 +2.227390578068412452e+01 7.330333862304687500e+02 +2.228401779046938458e+01 7.393914184570312500e+02 +2.229412926412695484e+01 7.464702758789062500e+02 +2.230424020141354191e+01 7.532754516601562500e+02 +2.231435060208591636e+01 7.588200683593750000e+02 +2.232446046590080968e+01 7.618510742187500000e+02 +2.233456979261501019e+01 7.621683959960937500e+02 +2.234467858198528489e+01 7.596124877929687500e+02 +2.235478683376843279e+01 7.548233032226562500e+02 +2.236489454772125995e+01 7.486472167968750000e+02 +2.237500172360059025e+01 7.421517944335937500e+02 +2.238510836116325109e+01 7.365338134765625000e+02 +2.239521446016609119e+01 7.320855712890625000e+02 +2.240532002036597348e+01 7.294068603515625000e+02 +2.241542504151976800e+01 7.284702758789062500e+02 +2.242552952338435190e+01 7.293065795898437500e+02 +2.243563346571662720e+01 7.312992553710937500e+02 +2.244573686827351366e+01 7.340942382812500000e+02 +2.245583973081192397e+01 7.366464843750000000e+02 +2.246594205308880277e+01 7.383532104492187500e+02 +2.247604383486108759e+01 7.390750122070312500e+02 +2.248614507588575151e+01 7.385413208007812500e+02 +2.249624577591977470e+01 7.373471679687500000e+02 +2.250634593472013023e+01 7.356700439453125000e+02 +2.251644555204383735e+01 7.342603759765625000e+02 +2.252654462764790111e+01 7.332322998046875000e+02 +2.253664316128934786e+01 7.327053222656250000e+02 +2.254674115272522883e+01 7.308406982421875000e+02 +2.255683860171258459e+01 7.264440307617187500e+02 +2.256693550800849479e+01 7.263438720703125000e+02 +2.257703187137003198e+01 7.271393432617187500e+02 +2.258712769155428290e+01 7.259249877929687500e+02 +2.259722296831837340e+01 7.240044555664062500e+02 +2.260731770141940444e+01 7.220988769531250000e+02 +2.261741189061451607e+01 7.202157592773437500e+02 +2.262750553566085543e+01 7.188223876953125000e+02 +2.263759863631557323e+01 7.179505004882812500e+02 +2.264769119233584860e+01 7.176442871093750000e+02 +2.265778320347887131e+01 7.179425659179687500e+02 +2.266787466950182406e+01 7.185306396484375000e+02 +2.267796559016192859e+01 7.193562011718750000e+02 +2.268805596521641021e+01 7.199742431640625000e+02 +2.269814579442249425e+01 7.209830322265625000e+02 +2.270823507753744153e+01 7.218369140625000000e+02 +2.271832381431851644e+01 7.228035888671875000e+02 +2.272841200452298693e+01 7.239805297851562500e+02 +2.273849964790815648e+01 7.257156372070312500e+02 +2.274858674423131077e+01 7.282328491210937500e+02 +2.275867329324978172e+01 7.312168579101562500e+02 +2.276875929472089055e+01 7.347234497070312500e+02 +2.277884474840197626e+01 7.381640014648437500e+02 +2.278892965405040627e+01 7.412819824218750000e+02 +2.279901401142353379e+01 7.431138916015625000e+02 +2.280909782027875821e+01 7.434581298828125000e+02 +2.281918108037346116e+01 7.424673461914062500e+02 +2.282926379146505624e+01 7.402312011718750000e+02 +2.283934595331096773e+01 7.371472778320312500e+02 +2.284942756566862343e+01 7.339555053710937500e+02 +2.285950862829547603e+01 7.312662353515625000e+02 +2.286958914094899242e+01 7.293098144531250000e+02 +2.287966910338663595e+01 7.285246582031250000e+02 +2.288974851536589838e+01 7.290500488281250000e+02 +2.289982737664428214e+01 7.309379882812500000e+02 +2.290990568697930385e+01 7.337244873046875000e+02 +2.291998344612848371e+01 7.365943603515625000e+02 +2.293006065384937031e+01 7.393100585937500000e+02 +2.294013730989951583e+01 7.413662719726562500e+02 +2.295021341403648307e+01 7.427991943359375000e+02 +2.296028896601785618e+01 7.436527099609375000e+02 +2.297036396560122995e+01 7.442127685546875000e+02 +2.298043841254420983e+01 7.453989257812500000e+02 +2.299051230660441547e+01 7.478574218750000000e+02 +2.300058564753948431e+01 7.513333129882812500e+02 +2.301065843510706088e+01 7.549323730468750000e+02 +2.302073066906480037e+01 7.580065917968750000e+02 +2.303080234917038283e+01 7.598173217773437500e+02 +2.304087347518149542e+01 7.599035034179687500e+02 +2.305094404685583953e+01 7.578930053710937500e+02 +2.306101406395112718e+01 7.543052978515625000e+02 +2.307108352622508107e+01 7.493798828125000000e+02 +2.308115243343544520e+01 7.439818115234375000e+02 +2.309122078533997424e+01 7.387448730468750000e+02 +2.310128858169644062e+01 7.341444702148437500e+02 +2.311135582226260610e+01 7.307239990234375000e+02 +2.312142250679628219e+01 7.284132690429687500e+02 +2.313148863505528041e+01 7.273411254882812500e+02 +2.314155420679740160e+01 7.268229370117187500e+02 +2.315161922178048926e+01 7.268020629882812500e+02 +2.316168367976239395e+01 7.271885986328125000e+02 +2.317174758050097694e+01 7.274960327148437500e+02 +2.318181092375411012e+01 7.277974243164062500e+02 +2.319187370927967251e+01 7.280038452148437500e+02 +2.320193593683557509e+01 7.286057739257812500e+02 +2.321199760617973595e+01 7.295895996093750000e+02 +2.322205871707007319e+01 7.311789550781250000e+02 +2.323211926926454041e+01 7.336929931640625000e+02 +2.324217926252106992e+01 7.368277587890625000e+02 +2.325223869659765086e+01 7.404786376953125000e+02 +2.326229757125225817e+01 7.436553344726562500e+02 +2.327235588624288809e+01 7.462640380859375000e+02 +2.328241364132754754e+01 7.475523071289062500e+02 +2.329247083626426118e+01 7.473375854492187500e+02 +2.330252747081105724e+01 7.457058105468750000e+02 +2.331258354472599592e+01 7.428192749023437500e+02 +2.332263905776712676e+01 7.394597167968750000e+02 +2.333269400969253837e+01 7.359791259765625000e+02 +2.334274840026031228e+01 7.332334594726562500e+02 +2.335280222922855131e+01 7.312667236328125000e+02 +2.336285549635537961e+01 7.300323486328125000e+02 +2.337290820139891778e+01 7.297034912109375000e+02 +2.338296034411731483e+01 7.297107543945312500e+02 +2.339301192426873044e+01 7.302896728515625000e+02 +2.340306294161133138e+01 7.307846069335937500e+02 +2.341311339590329155e+01 7.313889770507812500e+02 +2.342316328690282745e+01 7.321438598632812500e+02 +2.343321261436813785e+01 7.331349487304687500e+02 +2.344326137805745702e+01 7.348269042968750000e+02 +2.345330957772901570e+01 7.364448852539062500e+02 +2.346335721314106237e+01 7.385614013671875000e+02 +2.347340428405187396e+01 7.404287719726562500e+02 +2.348345079021972026e+01 7.415915527343750000e+02 +2.349349673140290307e+01 7.419190673828125000e+02 +2.350354210735972060e+01 7.409401855468750000e+02 +2.351358691784848531e+01 7.391195068359375000e+02 +2.352363116262755227e+01 7.363842163085937500e+02 +2.353367484145524813e+01 7.337169799804687500e+02 +2.354371795408994572e+01 7.313314208984375000e+02 +2.355376050029002144e+01 7.293485107421875000e+02 +2.356380247981384812e+01 7.283361816406250000e+02 +2.357384389241984834e+01 7.280439453125000000e+02 +2.358388473786641626e+01 7.286974487304687500e+02 +2.359392501591198865e+01 7.298231811523437500e+02 +2.360396472631501652e+01 7.311641845703125000e+02 +2.361400386883394376e+01 7.327922973632812500e+02 +2.362404244322725688e+01 7.342432250976562500e+02 +2.363408044925341400e+01 7.358976440429687500e+02 +2.364411788667093361e+01 7.372161254882812500e+02 +2.365415475523832001e+01 7.387265625000000000e+02 +2.366419105471409523e+01 7.411078491210937500e+02 +2.367422678485679910e+01 7.441171264648437500e+02 +2.368426194542498564e+01 7.478220214843750000e+02 +2.369429653617721598e+01 7.515314331054687500e+02 +2.370433055687207613e+01 7.549827270507812500e+02 +2.371436400726815208e+01 7.574323730468750000e+02 +2.372439688712405825e+01 7.581129150390625000e+02 +2.373442919619840552e+01 7.569374389648437500e+02 +2.374446093424983317e+01 7.539581909179687500e+02 +2.375449210103699116e+01 7.498588867187500000e+02 +2.376452269631854008e+01 7.452626342773437500e+02 +2.377455271985315477e+01 7.407767944335937500e+02 +2.378458217139952779e+01 7.368407592773437500e+02 +2.379461105071635174e+01 7.337199707031250000e+02 +2.380463935756235117e+01 7.319590454101562500e+02 +2.381466709169626128e+01 7.312636108398437500e+02 +2.382469425287681730e+01 7.314065551757812500e+02 +2.383472084086278286e+01 7.318652954101562500e+02 +2.384474685541292871e+01 7.322704467773437500e+02 +2.385477229628603624e+01 7.326260986328125000e+02 +2.386479716324091171e+01 7.325028076171875000e+02 +2.387482145603636141e+01 7.321464233398437500e+02 +2.388484517443121646e+01 7.317039184570312500e+02 +2.389486831818432577e+01 7.312595825195312500e+02 +2.390489088705452403e+01 7.312902221679687500e+02 +2.391491288080069921e+01 7.315507202148437500e+02 +2.392493429918172509e+01 7.320613403320312500e+02 +2.393495514195649321e+01 7.327878417968750000e+02 +2.394497540888393061e+01 7.332899169921875000e+02 +2.395499509972294305e+01 7.334602050781250000e+02 +2.396501421423247891e+01 7.331026000976562500e+02 +2.397503275217148300e+01 7.321578979492187500e+02 +2.398505071329892147e+01 7.307741699218750000e+02 +2.399506809737378532e+01 7.293054199218750000e+02 +2.400508490415505491e+01 7.277282104492187500e+02 +2.401510113340173547e+01 7.265909423828125000e+02 +2.402511678487286417e+01 7.258944702148437500e+02 +2.403513185832746046e+01 7.254949951171875000e+02 +2.404514635352458640e+01 7.253560180664062500e+02 +2.405516027022328629e+01 7.254898681640625000e+02 +2.406517360818265416e+01 7.258839111328125000e+02 +2.407518636716177340e+01 7.262957763671875000e+02 +2.408519854691975226e+01 7.266030273437500000e+02 +2.409521014721569898e+01 7.272586669921875000e+02 +2.410522116780876445e+01 7.283383178710937500e+02 +2.411523160845808178e+01 7.295882568359375000e+02 +2.412524146892280896e+01 7.311176147460937500e+02 +2.413525074896212885e+01 7.329538574218750000e+02 +2.414525944833522431e+01 7.346743774414062500e+02 +2.415526756680129949e+01 7.361858520507812500e+02 +2.416527510411957635e+01 7.371785888671875000e+02 +2.417528206004927682e+01 7.375834350585937500e+02 +2.418528843434964060e+01 7.372282104492187500e+02 +2.419529422677993935e+01 7.361264648437500000e+02 +2.420529943709943765e+01 7.345433349609375000e+02 +2.421530406506742850e+01 7.329206542968750000e+02 +2.422530811044319421e+01 7.315998535156250000e+02 +2.423531157298607042e+01 7.309475708007812500e+02 +2.424531445245537498e+01 7.311697998046875000e+02 +2.425531674861045417e+01 7.322641601562500000e+02 +2.426531846121065783e+01 7.339467773437500000e+02 +2.427531959001535711e+01 7.358123779296875000e+02 +2.428532013478395157e+01 7.375750122070312500e+02 +2.429532009527581593e+01 7.386431884765625000e+02 +2.430531947125038172e+01 7.388706665039062500e+02 +2.431531826246706274e+01 7.384968872070312500e+02 +2.432531646868530473e+01 7.378012084960937500e+02 +2.433531408966457121e+01 7.367724609375000000e+02 +2.434531112516431506e+01 7.355616455078125000e+02 +2.435530757494402465e+01 7.346156616210937500e+02 +2.436530343876320615e+01 7.335180053710937500e+02 +2.437529871638135859e+01 7.323394775390625000e+02 +2.438529340755801655e+01 7.310717163085937500e+02 +2.439528751205271107e+01 7.292959594726562500e+02 +2.440528102962499801e+01 7.274204101562500000e+02 +2.441527396003445105e+01 7.253792724609375000e+02 +2.442526630304064028e+01 7.231632080078125000e+02 +2.443525805840317844e+01 7.212538452148437500e+02 +2.444524922588165339e+01 7.197088623046875000e+02 +2.445523980523570629e+01 7.184276123046875000e+02 +2.446522979622497473e+01 7.174297485351562500e+02 +2.447521919860909989e+01 7.168062133789062500e+02 +2.448520801214776199e+01 7.164501342773437500e+02 +2.449519623660062706e+01 7.160970458984375000e+02 +2.450518387172739665e+01 7.149804077148437500e+02 +2.451517091728778652e+01 7.146787109375000000e+02 +2.452515737304150889e+01 7.149536743164062500e+02 +2.453514323874830794e+01 7.157740478515625000e+02 +2.454512851416793495e+01 7.169461059570312500e+02 +2.455511319906015544e+01 7.186826171875000000e+02 +2.456509729318474200e+01 7.211811523437500000e+02 +2.457508079630150277e+01 7.237723999023437500e+02 +2.458506370817023523e+01 7.261978759765625000e+02 +2.459504602855076882e+01 7.279750976562500000e+02 +2.460502775720294011e+01 7.287985229492187500e+02 +2.461500889388658209e+01 7.285061035156250000e+02 +2.462498943836158816e+01 7.271100463867187500e+02 +2.463496939038782330e+01 7.251331787109375000e+02 +2.464494874972518446e+01 7.227344970703125000e+02 +2.465492751613357925e+01 7.202164306640625000e+02 +2.466490568937292238e+01 7.181744384765625000e+02 +2.467488326920316410e+01 7.166704101562500000e+02 +2.468486025538424755e+01 7.158450317382812500e+02 +2.469483664767613718e+01 7.157779541015625000e+02 +2.470481244583881875e+01 7.162350463867187500e+02 +2.471478764963227803e+01 7.168270263671875000e+02 +2.472476225881653278e+01 7.173079833984375000e+02 +2.473473627315159362e+01 7.178070068359375000e+02 +2.474470969239750318e+01 7.183842163085937500e+02 +2.475468251631431826e+01 7.189402465820312500e+02 +2.476465474466209571e+01 7.197562255859375000e+02 +2.477462637720091720e+01 7.207651977539062500e+02 +2.478459741369087865e+01 7.217820434570312500e+02 +2.479456785389209017e+01 7.229616088867187500e+02 +2.480453769756467253e+01 7.238117065429687500e+02 +2.481450694446876071e+01 7.241400756835937500e+02 +2.482447559436450746e+01 7.235448608398437500e+02 +2.483444364701207618e+01 7.217908935546875000e+02 +2.484441110217165516e+01 7.193190917968750000e+02 +2.485437795960343266e+01 7.164542236328125000e+02 +2.486434421906761827e+01 7.134727172851562500e+02 +2.487430988032443580e+01 7.107712402343750000e+02 +2.488427494313411970e+01 7.084592895507812500e+02 +2.489423940725692574e+01 7.067798461914062500e+02 +2.490420327245312038e+01 7.057015380859375000e+02 +2.491416653848298779e+01 7.051328125000000000e+02 +2.492412920510681928e+01 7.050150756835937500e+02 +2.493409127208492038e+01 7.050321044921875000e+02 +2.494405273917761789e+01 7.051548461914062500e+02 +2.495401360614525643e+01 7.055763549804687500e+02 +2.496397387274818769e+01 7.058337402343750000e+02 +2.497393353874677047e+01 7.064252319335937500e+02 +2.498389260390139910e+01 7.072668457031250000e+02 +2.499385106797246436e+01 7.065288696289062500e+02 +2.500380893072036770e+01 7.054935913085937500e+02 +2.501376619190554962e+01 7.126982421875000000e+02 +2.502372285128843998e+01 7.167177734375000000e+02 +2.503367890862949352e+01 7.189946899414062500e+02 +2.504363436368919693e+01 7.198128051757812500e+02 +2.505358921622800494e+01 7.194262695312500000e+02 +2.506354346600643268e+01 7.179938354492187500e+02 +2.507349711278498461e+01 7.161674194335937500e+02 +2.508345015632419717e+01 7.141243896484375000e+02 +2.509340259638460680e+01 7.119711303710937500e+02 +2.510335443272676059e+01 7.102283325195312500e+02 +2.511330566511124118e+01 7.088727416992187500e+02 +2.512325629329863119e+01 7.080216674804687500e+02 +2.513320631704952746e+01 7.075119628906250000e+02 +2.514315573612454457e+01 7.072391967773437500e+02 +2.515310455028431136e+01 7.072614135742187500e+02 +2.516305275928946372e+01 7.073866577148437500e+02 +2.517300036290067311e+01 7.075297241210937500e+02 +2.518294736087860031e+01 7.075089721679687500e+02 +2.519289375298394162e+01 7.077084350585937500e+02 +2.520283953897738627e+01 7.079507446289062500e+02 +2.521278471861965542e+01 7.082542114257812500e+02 +2.522272929167148803e+01 7.085427246093750000e+02 +2.523267325789361948e+01 7.030917968750000000e+02 +2.524261661704681003e+01 7.016478881835937500e+02 +2.525255936889183772e+01 7.072658081054687500e+02 +2.526250151318949122e+01 7.091431274414062500e+02 +2.527244304970056987e+01 7.099122924804687500e+02 +2.528238397818589078e+01 7.102598266601562500e+02 +2.529232429840629237e+01 7.105624389648437500e+02 +2.530226401012262727e+01 7.108206787109375000e+02 +2.531220311309574811e+01 7.112144775390625000e+02 +2.532214160708652528e+01 7.118955078125000000e+02 +2.533207949185586472e+01 7.129283447265625000e+02 +2.534201676716466167e+01 7.139867553710937500e+02 +2.535195343277384694e+01 7.150387573242187500e+02 +2.536188948844434776e+01 7.163543090820312500e+02 +2.537182493393711624e+01 7.175852050781250000e+02 +2.538175976901311515e+01 7.183576049804687500e+02 +2.539169399343332856e+01 7.191803588867187500e+02 +2.540162760695874766e+01 7.204554443359375000e+02 +2.541156060935038141e+01 7.219538574218750000e+02 +2.542149300036925297e+01 7.236217041015625000e+02 +2.543142477977641036e+01 7.253292236328125000e+02 +2.544135594733288386e+01 7.269837036132812500e+02 +2.545128650279975346e+01 7.281899414062500000e+02 +2.546121644593811340e+01 7.290120239257812500e+02 +2.547114577650904010e+01 7.294706420898437500e+02 +2.548107449427366689e+01 7.294021606445312500e+02 +2.549100259899309151e+01 7.290330810546875000e+02 +2.550093009042848280e+01 7.285797729492187500e+02 +2.551085696834098115e+01 7.282573242187500000e+02 +2.552078323249176606e+01 7.281978759765625000e+02 +2.553070888264202054e+01 7.285422363281250000e+02 +2.554063391855293474e+01 7.293423461914062500e+02 +2.555055833998573789e+01 7.303726196289062500e+02 +2.556048214670165919e+01 7.315553588867187500e+02 +2.557040533846193142e+01 7.327090454101562500e+02 +2.558032791502782644e+01 7.338639526367187500e+02 +2.559024987616060898e+01 7.347703247070312500e+02 +2.560017122162157577e+01 7.355370483398437500e+02 +2.561009195117202708e+01 7.362696533203125000e+02 +2.562001206457328095e+01 7.370014648437500000e+02 +2.562993156158667674e+01 7.376657104492187500e+02 +2.563985044197355379e+01 7.382001953125000000e+02 +2.564976870549527987e+01 7.385095825195312500e+02 +2.565968635191323344e+01 7.383316040039062500e+02 +2.566960338098880712e+01 7.376373901367187500e+02 +2.567951979248340777e+01 7.363634033203125000e+02 +2.568943558615845646e+01 7.345343017578125000e+02 +2.569935076177539912e+01 7.323602294921875000e+02 +2.570926531909567103e+01 7.301770629882812500e+02 +2.571917925788076076e+01 7.278530883789062500e+02 +2.572909257789213555e+01 7.258315429687500000e+02 +2.573900527889129819e+01 7.240258178710937500e+02 +2.574891736063976211e+01 7.229814453125000000e+02 +2.575882882289904074e+01 7.222614135742187500e+02 +2.576873966543069372e+01 7.218745727539062500e+02 +2.577864988799626644e+01 7.216661987304687500e+02 +2.578855949035733275e+01 7.215791015625000000e+02 +2.579846847227549134e+01 7.218419189453125000e+02 +2.580837683351231604e+01 7.223738403320312500e+02 +2.581828457382945174e+01 7.232708740234375000e+02 +2.582819169298851136e+01 7.245108032226562500e+02 +2.583809819075115044e+01 7.259854736328125000e+02 +2.584800406687902452e+01 7.274253540039062500e+02 +2.585790932113381047e+01 7.288754882812500000e+02 +2.586781395327720645e+01 7.302161254882812500e+02 +2.587771796307090710e+01 7.312641601562500000e+02 +2.588762135027663902e+01 7.316588134765625000e+02 +2.589752411465614301e+01 7.314030151367187500e+02 +2.590742625597115989e+01 7.307059936523437500e+02 +2.591732777398346244e+01 7.299153442382812500e+02 +2.592722866845483765e+01 7.291088867187500000e+02 +2.593712893914706541e+01 7.284295654296875000e+02 +2.594702858582196825e+01 7.283231201171875000e+02 +2.595692760824136869e+01 7.286881713867187500e+02 +2.596682600616710701e+01 7.292031250000000000e+02 +2.597672377936104127e+01 7.300218505859375000e+02 +2.598662092758504016e+01 7.308476562500000000e+02 +2.599651745060099373e+01 7.312631225585937500e+02 +2.600641334817079908e+01 7.312466430664062500e+02 +2.601630862005637468e+01 7.310377807617187500e+02 +2.602620326601964607e+01 7.306655883789062500e+02 +2.603609728582256722e+01 7.300691528320312500e+02 +2.604599067922709565e+01 7.296461181640625000e+02 +2.605588344599521022e+01 7.293392333984375000e+02 +2.606577558588890398e+01 7.289664916992187500e+02 +2.607566709867017352e+01 7.285045166015625000e+02 +2.608555798410104742e+01 7.277998046875000000e+02 +2.609544824194356494e+01 7.268626708984375000e+02 +2.610533787195977595e+01 7.257697753906250000e+02 +2.611522687391174102e+01 7.248314819335937500e+02 +2.612511524756155268e+01 7.241414184570312500e+02 +2.613500299267131055e+01 7.235937500000000000e+02 +2.614489010900310362e+01 7.232962646484375000e+02 +2.615477659631908836e+01 7.231647338867187500e+02 +2.616466245438138571e+01 7.232199096679687500e+02 +2.617454768295215928e+01 7.235150756835937500e+02 +2.618443228179359750e+01 7.237353515625000000e+02 +2.619431625066786040e+01 7.241082763671875000e+02 +2.620419958933717908e+01 7.247976074218750000e+02 +2.621408229756374908e+01 7.256868286132812500e+02 +2.622396437510981926e+01 7.267857055664062500e+02 +2.623384582173763135e+01 7.282987670898437500e+02 +2.624372663720944487e+01 7.301199951171875000e+02 +2.625360682128755130e+01 7.319218750000000000e+02 +2.626348637373423855e+01 7.337121582031250000e+02 +2.627336529431180878e+01 7.352509155273437500e+02 +2.628324358278259965e+01 7.361162719726562500e+02 +2.629312123890893815e+01 7.362697753906250000e+02 +2.630299826245319039e+01 7.356388549804687500e+02 +2.631287465317772245e+01 7.344775390625000000e+02 +2.632275041084491818e+01 7.330850219726562500e+02 +2.633262553521717919e+01 7.316190795898437500e+02 +2.634250002605691421e+01 7.301757202148437500e+02 +2.635237388312657103e+01 7.290122070312500000e+02 +2.636224710618858325e+01 7.284886474609375000e+02 +2.637211969500541286e+01 7.283223876953125000e+02 +2.638199164933954322e+01 7.281744995117187500e+02 +2.639186296895346473e+01 7.282926025390625000e+02 +2.640173365360968560e+01 7.285200195312500000e+02 +2.641160370307072114e+01 7.289302978515625000e+02 +2.642147311709911506e+01 7.296677856445312500e+02 +2.643134189545741819e+01 7.303970336914062500e+02 +2.644121003790820978e+01 7.313483276367187500e+02 +2.645107754421405843e+01 7.326119384765625000e+02 +2.646094441413756471e+01 7.337763671875000000e+02 +2.647081064744136114e+01 7.347467041015625000e+02 +2.648067624388805541e+01 7.353169555664062500e+02 +2.649054120324030848e+01 7.356198730468750000e+02 +2.650040552526077064e+01 7.354995727539062500e+02 +2.651026920971211709e+01 7.347810058593750000e+02 +2.652013225635705140e+01 7.338282470703125000e+02 +2.652999466495826653e+01 7.326593627929687500e+02 +2.653985643527849092e+01 7.314519653320312500e+02 +2.654971756708046371e+01 7.305530395507812500e+02 +2.655957806012693112e+01 7.299657592773437500e+02 +2.656943791418067136e+01 7.296964721679687500e+02 +2.657929712900444841e+01 7.296464843750000000e+02 +2.658915570436107956e+01 7.299348144531250000e+02 +2.659901364001337498e+01 7.305820312500000000e+02 +2.660887093572416262e+01 7.311842041015625000e+02 +2.661872759125628818e+01 7.318494262695312500e+02 +2.662858360637260446e+01 7.327897338867187500e+02 +2.663843898083599271e+01 7.339793090820312500e+02 +2.664829371440934835e+01 7.353211669921875000e+02 +2.665814780685557039e+01 7.366682739257812500e+02 +2.666800125793759690e+01 7.377418212890625000e+02 +2.667785406741833398e+01 7.383219604492187500e+02 +2.668770623506076234e+01 7.383116455078125000e+02 +2.669755776062784136e+01 7.376874389648437500e+02 +2.670740864388254820e+01 7.366812133789062500e+02 +2.671725888458789910e+01 7.353932495117187500e+02 +2.672710848250688898e+01 7.341818847656250000e+02 +2.673695743740255537e+01 7.334310302734375000e+02 +2.674680574903795360e+01 7.330673828125000000e+02 +2.675665341717612833e+01 7.332625732421875000e+02 +2.676650044158017039e+01 7.339269409179687500e+02 +2.677634682201315997e+01 7.345960083007812500e+02 +2.678619255823820566e+01 7.353226318359375000e+02 +2.679603765001844096e+01 7.360580444335937500e+02 +2.680588209711699932e+01 7.368311157226562500e+02 +2.681572589929703199e+01 7.372202148437500000e+02 +2.682556905632171151e+01 7.372656250000000000e+02 +2.683541156795422111e+01 7.375628051757812500e+02 +2.684525343395775110e+01 7.379965820312500000e+02 +2.685509465409553798e+01 7.382749633789062500e+02 +2.686493522813079693e+01 7.381573486328125000e+02 +2.687477515582679288e+01 7.379122924804687500e+02 +2.688461443694676944e+01 7.374521484375000000e+02 +2.689445307125400930e+01 7.363959960937500000e+02 +2.690429105851181291e+01 7.351165161132812500e+02 +2.691412839848348781e+01 7.339475097656250000e+02 +2.692396509093235224e+01 7.326726684570312500e+02 +2.693380113562176348e+01 7.314790649414062500e+02 +2.694363653231505396e+01 7.304363403320312500e+02 +2.695347128077561294e+01 7.298671264648437500e+02 +2.696330538076682259e+01 7.297805175781250000e+02 +2.697313883205208285e+01 7.299288330078125000e+02 +2.698297163439482205e+01 7.302425537109375000e+02 +2.699280378755846144e+01 7.304841308593750000e+02 +2.700263529130646489e+01 7.305453491210937500e+02 +2.701246614540227853e+01 7.307169799804687500e+02 +2.702229634960939819e+01 7.309422607421875000e+02 +2.703212590369131618e+01 7.314143066406250000e+02 +2.704195480741154967e+01 7.318858642578125000e+02 +2.705178306053361581e+01 7.322851562500000000e+02 +2.706161066282107441e+01 7.329129028320312500e+02 +2.707143761403746751e+01 7.335102539062500000e+02 +2.708126391394637977e+01 7.335581054687500000e+02 +2.709108956231139942e+01 7.334583129882812500e+02 +2.710091455889613599e+01 7.332976074218750000e+02 +2.711073890346420256e+01 7.329574584960937500e+02 +2.712056259577924422e+01 7.323825073242187500e+02 +2.713038563560491667e+01 7.320144653320312500e+02 +2.714020802270487920e+01 7.316758422851562500e+02 +2.715002975684283015e+01 7.315045776367187500e+02 +2.715985083778245368e+01 7.314523315429687500e+02 +2.716967126528747656e+01 7.318482055664062500e+02 +2.717949103912162911e+01 7.320064086914062500e+02 +2.718931015904865944e+01 7.320278930664062500e+02 +2.719912862483233340e+01 7.322275390625000000e+02 +2.720894643623642040e+01 7.328175048828125000e+02 +2.721876359302472181e+01 7.329913330078125000e+02 +2.722858009496104614e+01 7.330700683593750000e+02 +2.723839594180922674e+01 7.332795410156250000e+02 +2.724821113333309697e+01 7.337283325195312500e+02 +2.725802566929651150e+01 7.339998779296875000e+02 +2.726783954946336053e+01 7.341961059570312500e+02 +2.727765277359751650e+01 7.341503295898437500e+02 +2.728746534146289449e+01 7.338649291992187500e+02 +2.729727725282340955e+01 7.336383666992187500e+02 +2.730708850744300165e+01 7.332101440429687500e+02 +2.731689910508562846e+01 7.323241577148437500e+02 +2.732670904551524416e+01 7.315139160156250000e+02 +2.733651832849584906e+01 7.310755004882812500e+02 +2.734632695379142930e+01 7.308010253906250000e+02 +2.735613492116600654e+01 7.308137817382812500e+02 +2.736594223038362017e+01 7.308848266601562500e+02 +2.737574888120830252e+01 7.308961791992187500e+02 +2.738555487340412853e+01 7.311405639648437500e+02 +2.739536020673517314e+01 7.318039550781250000e+02 +2.740516488096553260e+01 7.327438354492187500e+02 +2.741496889585932095e+01 7.336926879882812500e+02 +2.742477225118065220e+01 7.346126098632812500e+02 +2.743457494669367591e+01 7.357812500000000000e+02 +2.744437698216255939e+01 7.371552734375000000e+02 +2.745417835735146639e+01 7.379931640625000000e+02 +2.746397907202459621e+01 7.384804077148437500e+02 +2.747377912594614457e+01 7.380745239257812500e+02 +2.748357851888033920e+01 7.377653198242187500e+02 +2.749337725059142201e+01 7.369566040039062500e+02 +2.750317532084363492e+01 7.360452880859375000e+02 +2.751297272940125893e+01 7.345214233398437500e+02 +2.752276947602857504e+01 7.331337280273437500e+02 +2.753256556048988912e+01 7.321799316406250000e+02 +2.754236098254950704e+01 7.314841918945312500e+02 +2.755215574197177375e+01 7.312870483398437500e+02 +2.756194983852103775e+01 7.312186279296875000e+02 +2.757174327196165819e+01 7.311788940429687500e+02 +2.758153604205802623e+01 7.312631225585937500e+02 +2.759132814857452232e+01 7.312958984375000000e+02 +2.760111959127558023e+01 7.314622802734375000e+02 +2.761091036992561243e+01 7.315698242187500000e+02 +2.762070048428908109e+01 7.317319946289062500e+02 +2.763048993413043064e+01 7.321416625976562500e+02 +2.764027871921413748e+01 7.325386352539062500e+02 +2.765006683930471354e+01 7.327823486328125000e+02 +2.765985429416664942e+01 7.327935791015625000e+02 +2.766964108356448548e+01 7.325598754882812500e+02 +2.767942720726275141e+01 7.318623046875000000e+02 +2.768921266502600176e+01 7.312012329101562500e+02 +2.769899745661882307e+01 7.308709106445312500e+02 +2.770878158180579121e+01 7.306028442382812500e+02 +2.771856504035152113e+01 7.303837280273437500e+02 +2.772834783202063136e+01 7.302053833007812500e+02 +2.773812995657775815e+01 7.300330200195312500e+02 +2.774791141378755910e+01 7.301366577148437500e+02 +2.775769220341469179e+01 7.303701171875000000e+02 +2.776747232522384934e+01 7.308424072265625000e+02 +2.777725177897973907e+01 7.313559570312500000e+02 +2.778703056444707187e+01 7.321154174804687500e+02 +2.779680868139058703e+01 7.328516845703125000e+02 +2.780658612957502385e+01 7.337604980468750000e+02 +2.781636290876515361e+01 7.347424316406250000e+02 +2.782613901872576179e+01 7.358659667968750000e+02 +2.783591445922164098e+01 7.373141479492187500e+02 +2.784568923001761931e+01 7.376210327148437500e+02 +2.785546333087850712e+01 7.375791015625000000e+02 +2.786523676156915741e+01 7.373754882812500000e+02 +2.787500952185444092e+01 7.369012451171875000e+02 +2.788478161149922840e+01 7.362587280273437500e+02 +2.789455303026841904e+01 7.356065673828125000e+02 +2.790432377792691554e+01 7.348991088867187500e+02 +2.791409385423965261e+01 7.345482177734375000e+02 +2.792386325897157562e+01 7.347990722656250000e+02 +2.793363199188764057e+01 7.347173461914062500e+02 +2.794340005275282479e+01 7.353244628906250000e+02 +2.795316744133211273e+01 7.359252319335937500e+02 +2.796293415739052435e+01 7.363357543945312500e+02 +2.797270020069306540e+01 7.365950927734375000e+02 +2.798246557100479848e+01 7.365737304687500000e+02 +2.799223026809076131e+01 7.367456054687500000e+02 +2.800199429171604493e+01 7.369792480468750000e+02 +2.801175764164572257e+01 7.370290527343750000e+02 +2.802152031764489948e+01 7.368079833984375000e+02 +2.803128231947871285e+01 7.363236694335937500e+02 +2.804104364691227858e+01 7.362607421875000000e+02 +2.805080429971076583e+01 7.361060180664062500e+02 +2.806056427763934735e+01 7.353693847656250000e+02 +2.807032358046319231e+01 7.345913696289062500e+02 +2.808008220794751963e+01 7.338859863281250000e+02 +2.808984015985753757e+01 7.326786499023437500e+02 +2.809959743595848281e+01 7.318336181640625000e+02 +2.810935403601562044e+01 7.316445312500000000e+02 +2.811910995979419425e+01 7.315125122070312500e+02 +2.812886520705950133e+01 7.311190185546875000e+02 +2.813861977757684585e+01 7.306495971679687500e+02 +2.814837367111153199e+01 7.307339477539062500e+02 +2.815812688742890657e+01 7.311224365234375000e+02 +2.816787942629430219e+01 7.314318847656250000e+02 +2.817763128747309764e+01 7.318215332031250000e+02 +2.818738247073067171e+01 7.322834472656250000e+02 +2.819713297583241385e+01 7.332420654296875000e+02 +2.820688280254375258e+01 7.341570434570312500e+02 +2.821663195063010576e+01 7.345292358398437500e+02 +2.822638041985692325e+01 7.351846313476562500e+02 +2.823612820998967265e+01 7.355986938476562500e+02 +2.824587532079382513e+01 7.354273681640625000e+02 +2.825562175203488735e+01 7.350325317382812500e+02 +2.826536750347836602e+01 7.345165405273437500e+02 +2.827511257488978558e+01 7.340256958007812500e+02 +2.828485696603469535e+01 7.333426513671875000e+02 +2.829460067667865530e+01 7.328088378906250000e+02 +2.830434370658723964e+01 7.324367675781250000e+02 +2.831408605552605451e+01 7.323919067382812500e+02 +2.832382772326069897e+01 7.325646362304687500e+02 +2.833356870955679696e+01 7.326453247070312500e+02 +2.834330901418000437e+01 7.331408691406250000e+02 +2.835304863689596999e+01 7.331282348632812500e+02 +2.836278757747037460e+01 7.323607788085937500e+02 +2.837252583566891317e+01 7.328208007812500000e+02 +2.838226341125728069e+01 7.330665283203125000e+02 +2.839200030400122188e+01 7.329102783203125000e+02 +2.840173651366646723e+01 7.330521850585937500e+02 +2.841147204001877569e+01 7.326403808593750000e+02 +2.842120688282392038e+01 7.320308837890625000e+02 +2.843094104184769577e+01 7.315514526367187500e+02 +2.844067451685591053e+01 7.313831176757812500e+02 +2.845040730761438041e+01 7.312095336914062500e+02 +2.846013941388895319e+01 7.307658691406250000e+02 +2.846987083544548369e+01 7.305082397460937500e+02 +2.847960157204984455e+01 7.303758544921875000e+02 +2.848933162346793324e+01 7.305944824218750000e+02 +2.849906098946563660e+01 7.309609375000000000e+02 +2.850878966980889828e+01 7.309919433593750000e+02 +2.851851766426364776e+01 7.312819213867187500e+02 +2.852824497259583580e+01 7.315757446289062500e+02 +2.853797159457145227e+01 7.320529785156250000e+02 +2.854769752995646215e+01 7.331019287109375000e+02 +2.855742277851688726e+01 7.337909545898437500e+02 +2.856714734001874234e+01 7.344871826171875000e+02 +2.857687121422807053e+01 7.353767089843750000e+02 +2.858659440091092563e+01 7.355745849609375000e+02 +2.859631689983337921e+01 7.361062011718750000e+02 +2.860603871076150995e+01 7.363441162109375000e+02 +2.861575983346143559e+01 7.358413696289062500e+02 +2.862548026769927034e+01 7.355227661132812500e+02 +2.863520001324115682e+01 7.346726074218750000e+02 +2.864491906985323766e+01 7.333265380859375000e+02 +2.865463743730169455e+01 7.324672241210937500e+02 +2.866435511535271274e+01 7.314595947265625000e+02 +2.867407210377249527e+01 7.307822875976562500e+02 +2.868378840232725580e+01 7.308693847656250000e+02 +2.869350401078325064e+01 7.305715942382812500e+02 +2.870321892890671478e+01 7.305612792968750000e+02 +2.871293315646391875e+01 7.307781982421875000e+02 +2.872264669322116148e+01 7.307393798828125000e+02 +2.873235953894473838e+01 7.308393554687500000e+02 +2.874207169340097323e+01 7.310302734375000000e+02 +2.875178315635620052e+01 7.310723266601562500e+02 +2.876149392757676893e+01 7.309559326171875000e+02 +2.877120400682906265e+01 7.312838134765625000e+02 +2.878091339387945169e+01 7.313255004882812500e+02 +2.879062208849435223e+01 7.308954467773437500e+02 +2.880033009044018044e+01 7.305708007812500000e+02 +2.881003739948336673e+01 7.301951904296875000e+02 +2.881974401539037700e+01 7.297225952148437500e+02 +2.882944993792766653e+01 7.290471191406250000e+02 +2.883915516686172964e+01 7.284782714843750000e+02 +2.884885970195907845e+01 7.278118896484375000e+02 +2.885856354298621440e+01 7.274188232421875000e+02 +2.886826668970969578e+01 7.269110717773437500e+02 +2.887796914189605246e+01 7.265557250976562500e+02 +2.888767089931187115e+01 7.264169921875000000e+02 +2.889737196172373501e+01 7.263269042968750000e+02 +2.890707232889824496e+01 7.263858032226562500e+02 +2.891677200060202324e+01 7.267285156250000000e+02 +2.892647097660171340e+01 7.270115356445312500e+02 +2.893616925666395545e+01 7.270916748046875000e+02 +2.894586684055543557e+01 7.272241821289062500e+02 +2.895556372804282574e+01 7.279049072265625000e+02 +2.896525991889284057e+01 7.277188720703125000e+02 +2.897495541287219822e+01 7.274641723632812500e+02 +2.898465020974763107e+01 7.273262329101562500e+02 +2.899434430928590700e+01 7.274862670898437500e+02 +2.900403771125378327e+01 7.272969970703125000e+02 +2.901373041541805620e+01 7.266459960937500000e+02 +2.902342242154552565e+01 7.265253295898437500e+02 +2.903311372940301283e+01 7.260816650390625000e+02 +2.904280433875735667e+01 7.253526611328125000e+02 +2.905249424937541747e+01 7.256453247070312500e+02 +2.906218346102406613e+01 7.258809204101562500e+02 +2.907187197347018426e+01 7.256018066406250000e+02 +2.908155978648068185e+01 7.258160400390625000e+02 +2.909124689982248313e+01 7.262200317382812500e+02 +2.910093331326252653e+01 7.265287475585937500e+02 +2.911061902656776468e+01 7.265086059570312500e+02 +2.912030403950517510e+01 7.266036987304687500e+02 +2.912998835184175306e+01 7.268823242187500000e+02 +2.913967196334448317e+01 7.267356567382812500e+02 +2.914935487378041401e+01 7.260652465820312500e+02 +2.915903708291656926e+01 7.258501586914062500e+02 +2.916871859052001881e+01 7.253088378906250000e+02 +2.917839939635783253e+01 7.240382690429687500e+02 +2.918807950019708741e+01 7.238420410156250000e+02 +2.919775890180491018e+01 7.231333618164062500e+02 +2.920743760094842045e+01 7.223496704101562500e+02 +2.921711559739475561e+01 7.219378662109375000e+02 +2.922679289091108146e+01 7.213191528320312500e+02 +2.923646948126455669e+01 7.210366210937500000e+02 +2.924614536822239685e+01 7.207994384765625000e+02 +2.925582055155179972e+01 7.202911987304687500e+02 +2.926549503101998795e+01 7.202466430664062500e+02 +2.927516880639421260e+01 7.206307373046875000e+02 +2.928484187744172829e+01 7.205041503906250000e+02 +2.929451424392981451e+01 7.201030883789062500e+02 +2.930418590562576497e+01 7.195729980468750000e+02 +2.931385686229688758e+01 7.196536865234375000e+02 +2.932352711371051868e+01 7.202729492187500000e+02 +2.933319665963399459e+01 7.207434082031250000e+02 +2.934286549983468007e+01 7.208905639648437500e+02 +2.935253363407995764e+01 7.209297485351562500e+02 +2.936220106213721692e+01 7.207445068359375000e+02 +2.937186778377387242e+01 7.196271362304687500e+02 +2.938153379875735638e+01 7.186663818359375000e+02 +2.939119910685511883e+01 7.182761230468750000e+02 +2.940086370783461689e+01 7.176363525390625000e+02 +2.941052760146333256e+01 7.171066894531250000e+02 +2.942019078750876204e+01 7.164257202148437500e+02 +2.942985326573842997e+01 7.163071899414062500e+02 +2.943951503591986096e+01 7.166292114257812500e+02 +2.944917609782059387e+01 7.157980346679687500e+02 +2.945883645120821726e+01 7.149534301757812500e+02 +2.946849609585030194e+01 7.150408325195312500e+02 +2.947815503151444716e+01 7.151278076171875000e+02 +2.948781325796826991e+01 7.147857666015625000e+02 +2.949747077497940140e+01 7.147715454101562500e+02 +2.950712758231550836e+01 7.147714233398437500e+02 +2.951678367974423978e+01 7.138904418945312500e+02 +2.952643906703329435e+01 7.135582275390625000e+02 +2.953609374395036724e+01 7.130081787109375000e+02 +2.954574771026317848e+01 7.119794311523437500e+02 +2.955540096573947650e+01 7.109762573242187500e+02 +2.956505351014699556e+01 7.096732177734375000e+02 +2.957470534325351963e+01 7.089163208007812500e+02 +2.958435646482683623e+01 7.090888671875000000e+02 +2.959400687463474711e+01 7.087586669921875000e+02 +2.960365657244507887e+01 7.079938964843750000e+02 +2.961330555802566522e+01 7.070686035156250000e+02 +2.962295383114435765e+01 7.068906250000000000e+02 +2.963260139156905026e+01 7.062741088867187500e+02 +2.964224823906761586e+01 7.064904785156250000e+02 +2.965189437340796985e+01 7.061801757812500000e+02 +2.966153979435802768e+01 7.063544311523437500e+02 +2.967118450168574739e+01 7.061673583984375000e+02 +2.968082849515907995e+01 7.049190063476562500e+02 +2.969047177454599762e+01 7.043295898437500000e+02 +2.970011433961450464e+01 7.041221923828125000e+02 +2.970975619013260172e+01 7.034354858398437500e+02 +2.971939732586832150e+01 7.018108520507812500e+02 +2.972903774658971798e+01 7.002899169921875000e+02 +2.973867745206483448e+01 6.987274169921875000e+02 +2.974831644206177117e+01 6.980977783203125000e+02 +2.975795471634861045e+01 6.972084960937500000e+02 +2.976759227469347024e+01 6.955151977539062500e+02 +2.977722911686448981e+01 6.943611450195312500e+02 +2.978686524262980839e+01 6.932053222656250000e+02 +2.979650065175759721e+01 6.922052612304687500e+02 +2.980613534401603459e+01 6.917118530273437500e+02 +2.981576931917333084e+01 6.906671142578125000e+02 +2.982540257699768560e+01 6.893287353515625000e+02 +2.983503511725735535e+01 6.884641723632812500e+02 +2.984466693972057882e+01 6.869281616210937500e+02 +2.985429804415562671e+01 6.858670043945312500e+02 +2.986392843033079458e+01 6.849159545898437500e+02 +2.987355809801436735e+01 6.831806640625000000e+02 +2.988318704697469030e+01 6.815989990234375000e+02 +2.989281527698008389e+01 6.800095825195312500e+02 +2.990244278779891118e+01 6.780161743164062500e+02 +2.991206957919955300e+01 6.763162841796875000e+02 +2.992169565095037953e+01 6.746636352539062500e+02 +2.993132100281981778e+01 6.721287841796875000e+02 +2.994094563457628411e+01 6.697698974609375000e+02 +2.995056954598822330e+01 6.686368408203125000e+02 +2.996019273682409434e+01 6.667157592773437500e+02 +2.996981520685237399e+01 6.633543701171875000e+02 +2.997943695584156742e+01 6.601245117187500000e+02 +2.998905798356016206e+01 6.585920410156250000e+02 +2.999867828977670570e+01 6.563908081054687500e+02 +3.000829787425974260e+01 6.547699584960937500e+02 +3.001791673677783479e+01 6.543618164062500000e+02 +3.002753487709956204e+01 6.549055175781250000e+02 +3.003715229499352901e+01 6.544462280273437500e+02 +3.004676899022834391e+01 6.526001586914062500e+02 +3.005638496257264691e+01 6.500903930664062500e+02 +3.006600021179508531e+01 6.471275634765625000e+02 +3.007561473766432414e+01 6.441400146484375000e+02 +3.008522853994904978e+01 6.413679809570312500e+02 +3.009484161841797345e+01 6.406323852539062500e+02 +3.010445397283980640e+01 6.404413452148437500e+02 +3.011406560298329538e+01 6.397152099609375000e+02 +3.012367650861719071e+01 6.402265014648437500e+02 +3.013328668951026046e+01 6.419284057617187500e+02 +3.014289614543129758e+01 6.441613159179687500e+02 +3.015250487614911279e+01 6.459108886718750000e+02 +3.016211288143253455e+01 6.456569213867187500e+02 +3.017172016105039845e+01 6.455060424804687500e+02 +3.018132671477156492e+01 6.452680664062500000e+02 +3.019093254236491219e+01 6.441242065429687500e+02 +3.020053764359933623e+01 6.432831420898437500e+02 +3.021014201824375078e+01 6.439409790039062500e+02 +3.021974566606708024e+01 6.445250244140625000e+02 +3.022934858683827741e+01 6.472052612304687500e+02 +3.023895078032631289e+01 6.479945678710937500e+02 +3.024855224630015371e+01 6.476074218750000000e+02 +3.025815298452880953e+01 6.471079101562500000e+02 +3.026775299478129710e+01 6.462006225585937500e+02 +3.027735227682664743e+01 6.459758300781250000e+02 +3.028695083043391634e+01 6.456336059570312500e+02 +3.029654865537217034e+01 6.435211181640625000e+02 +3.030614575141050437e+01 6.420408935546875000e+02 +3.031574211831801335e+01 6.410575561523437500e+02 +3.032533775586382774e+01 6.398981323242187500e+02 +3.033493266381708864e+01 6.399980468750000000e+02 +3.034452684194694072e+01 6.402353515625000000e+02 +3.035412029002257128e+01 6.388941650390625000e+02 +3.036371300781317473e+01 6.385469360351562500e+02 +3.037330499508794546e+01 6.373518066406250000e+02 +3.038289625161613472e+01 6.366307983398437500e+02 +3.039248677716696889e+01 6.354168090820312500e+02 +3.040207657150971698e+01 6.349065551757812500e+02 +3.041166563441366577e+01 6.337166748046875000e+02 +3.042125396564810202e+01 6.332886962890625000e+02 +3.043084156498235515e+01 6.320107421875000000e+02 +3.044042843218574390e+01 6.292490844726562500e+02 +3.045001456702762610e+01 6.275798950195312500e+02 +3.045959996927737734e+01 6.258865356445312500e+02 +3.046918463870437321e+01 6.250547485351562500e+02 +3.047876857507802129e+01 6.243579711914062500e+02 +3.048835177816774689e+01 6.229226684570312500e+02 +3.049793424774298600e+01 6.204249267578125000e+02 +3.050751598357319594e+01 6.179489135742187500e+02 +3.051709698542784466e+01 6.160234375000000000e+02 +3.052667725307643565e+01 6.148678588867187500e+02 +3.053625678628847595e+01 6.122053833007812500e+02 +3.054583558483348682e+01 6.104899902343750000e+02 +3.055541364848101438e+01 6.080277099609375000e+02 +3.056499097700063317e+01 6.059958496093750000e+02 +3.057456757016190707e+01 6.034841918945312500e+02 +3.058414342773444972e+01 5.998724975585937500e+02 +3.059371854948786762e+01 5.968936157226562500e+02 +3.060329293519178862e+01 5.925253295898437500e+02 +3.061286658461587962e+01 5.899281616210937500e+02 +3.062243949752980043e+01 5.844417724609375000e+02 +3.063201167370323930e+01 5.809273071289062500e+02 +3.064158311290589864e+01 5.767075805664062500e+02 +3.065115381490750224e+01 5.691906738281250000e+02 +3.066072377947780225e+01 5.609036865234375000e+02 +3.067029300638652956e+01 5.543360595703125000e+02 +3.067986149540348251e+01 5.429436035156250000e+02 +3.068942924629844526e+01 5.312633056640625000e+02 +3.069899625884123040e+01 5.154556884765625000e+02 +3.070856253280167181e+01 5.002900695800781250e+02 +3.071812806794959982e+01 4.891472473144531250e+02 +3.072769286405489808e+01 4.822662048339843750e+02 +3.073725692088743955e+01 4.737917480468750000e+02 +3.074682023821711851e+01 4.685761718750000000e+02 +3.075638281581386480e+01 4.580401611328125000e+02 +3.076594465344760465e+01 4.409396057128906250e+02 +3.077550575088829632e+01 4.147247009277343750e+02 +3.078506610790590869e+01 3.878192749023437500e+02 +3.079462572427042844e+01 3.687083129882812500e+02 +3.080418459975187062e+01 3.584149169921875000e+02 +3.081374273412025389e+01 3.561899414062500000e+02 +3.082330012714561462e+01 3.596179809570312500e+02 +3.083285677859802476e+01 3.629963073730468750e+02 +3.084241268824755267e+01 3.744177551269531250e+02 +3.085196785586430224e+01 3.716326904296875000e+02 +3.086152228121838093e+01 3.373220825195312500e+02 +3.087107596407991750e+01 2.990657958984375000e+02 +3.088062890421906914e+01 2.483579406738281250e+02 +3.089018110140600726e+01 1.636023254394531250e+02 +3.089973255541090325e+01 9.403746795654296875e+01 +3.090928326600396403e+01 3.087844657897949219e+01 From cdc091ab8a2240e576f5bdde2b4bcbf707b2b0ed Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 12 Dec 2025 10:47:23 -0500 Subject: [PATCH 434/445] function for deprecation print message --- src/diffpy/utils/_deprecator.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 91f3c0fb..80b183e4 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -81,3 +81,28 @@ def wrapper(*args, **kwargs): ) return decorator + + +def deprecation_message(base, old_name, new_name, removal_version): + """Generate a deprecation message. + + Parameters + ---------- + base : str + The base module or class where the deprecated item resides. + old_name : str + The name of the deprecated item. + new_name : str + The name of the new item to use. + removal_version : str + The version when the deprecated item will be removed. + + Returns + ------- + str + A formatted deprecation message. + """ + return ( + f"'{base}.{old_name}' is deprecated and will be removed in " + f"version {removal_version}. Please use '{base}.{new_name}' instead." + ) From 8e65b06ea9e36b16e8e47e83139079b1dcac8d39 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 12 Dec 2025 10:49:34 -0500 Subject: [PATCH 435/445] news --- news/dep-msg-helper.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/dep-msg-helper.rst diff --git a/news/dep-msg-helper.rst b/news/dep-msg-helper.rst new file mode 100644 index 00000000..733801e6 --- /dev/null +++ b/news/dep-msg-helper.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add ``deprecation_message`` helper for printing consistent deprecation messages. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From e741a273fcc1ccf8dec9d6ff88721f55028204fe Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 18 Dec 2025 12:46:16 -0500 Subject: [PATCH 436/445] scikit:recut diffpy.utils using latest scikit-package. --- .github/ISSUE_TEMPLATE/release_checklist.md | 2 +- .../workflows/build-wheel-release-upload.yml | 2 +- README.rst | 4 +- cookiecutter.json | 18 +++++++++ docs/source/index.rst | 4 +- pyproject.toml | 8 ++++ src/diffpy/utils/_deprecator.py | 4 +- src/diffpy/utils/diffraction_objects.py | 37 +++++++++++-------- src/diffpy/utils/parsers/serialization.py | 5 ++- src/diffpy/utils/resampler.py | 4 +- src/diffpy/utils/tools.py | 33 +++++++++-------- src/diffpy/utils/transforms.py | 16 +++++--- src/diffpy/utils/utils_app.py | 33 +++++++++++++++++ src/diffpy/utils/version.py | 9 +++-- src/diffpy/utils/wx/gridutils.py | 6 +-- 15 files changed, 129 insertions(+), 56 deletions(-) create mode 100644 cookiecutter.json create mode 100644 src/diffpy/utils/utils_app.py diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 6107962c..56bcd015 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -34,7 +34,7 @@ Please let the maintainer know that all checks are done and the package is ready - [ ] Ensure that the full release has appeared on PyPI successfully. -- [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. +- [ ] New package dependencies listed in `conda.txt` and `tests.txt` are added to `meta.yaml` in the feedstock. - [ ] Close any open issues on the feedstock. Reach out to the maintainer if you have questions. - [ ] Tag the maintainer for conda-forge release. diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 485aa356..e40144a7 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -7,7 +7,7 @@ on: - "*" # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml jobs: - release: + build-release: uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 with: project: diffpy.utils diff --git a/README.rst b/README.rst index cf989cdf..388598b2 100644 --- a/README.rst +++ b/README.rst @@ -25,6 +25,7 @@ :target: https://anaconda.org/conda-forge/diffpy.utils .. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff + :target: https://github.com/diffpy/diffpy.utils/pulls .. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.utils :target: https://pypi.org/project/diffpy.utils/ @@ -35,8 +36,7 @@ .. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue :target: https://github.com/diffpy/diffpy.utils/issues -diffpy.utils Package -======================================================================== +Shared utilities for diffpy packages. General utilities for analyzing diffraction data diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 00000000..30db1791 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,18 @@ +{ + "maintainer_name": "Simon Billinge", + "maintainer_email": "sb2896@columbia.edu", + "maintainer_github_username": "sbillinge", + "contributors": "Timur Davis, Chris Farrow, Pavol Juhas", + "license_holders": "The Trustees of Columbia University in the City of New York", + "project_name": "diffpy.utils", + "github_username_or_orgname": "diffpy", + "github_repo_name": "diffpy.utils", + "conda_pypi_package_dist_name": "diffpy.utils", + "package_dir_name": "diffpy.utils", + "project_short_description": "Shared utilities for diffpy packages.", + "project_keywords": "text data parsers, wx grid, diffraction objects", + "minimum_supported_python_version": "3.11", + "maximum_supported_python_version": "3.13", + "project_needs_c_code_compiled": "No", + "project_has_gui_tests": "No" +} diff --git a/docs/source/index.rst b/docs/source/index.rst index a406daa3..27359a3f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,7 +6,7 @@ diffpy.utils - General utilities for analyzing diffraction data -| Software version |release|. +| Software version |release| | Last updated |today|. The diffpy.utils package provides a number of functions and classes designed to help @@ -33,7 +33,7 @@ Illustrations of when and how one would use various diffpy.utils functions. Authors ======= -diffpy.utils is developed by members of the Billinge Group at +``diffpy.utils`` is developed by members of the Billinge Group at Columbia University and at Brookhaven National Laboratory including Pavol Juhás, Christopher L. Farrow, Simon J. L. Billinge, Andrew Yang, with contributions from many Billinge Group members and diff --git a/pyproject.toml b/pyproject.toml index ef9192f4..02377e62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,9 @@ include = ["*"] # package names should match these glob patterns (["*"] by defa exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) +[project.scripts] +diffpy-utils = "diffpy.utils.app:main" + [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} @@ -56,6 +59,11 @@ exclude-file = ".codespell/ignore_lines.txt" ignore-words = ".codespell/ignore_words.txt" skip = "*.cif,*.dat" +[tool.docformatter] +recursive = true +wrap-summaries = 72 +wrap-descriptions = 72 + [tool.black] line-length = 79 include = '\.pyi?$' diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 80b183e4..72172cae 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -11,8 +11,8 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): - """Deprecation decorator for functions and classes that is compatible with - Python versions prior to 3.13. + """Deprecation decorator for functions and classes that is + compatible with Python versions prior to 3.13. Examples -------- diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 733d3705..aa58033f 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -211,8 +211,8 @@ def __eq__(self, other): return True def __add__(self, other): - """Add a scalar value or another DiffractionObject to the yarray of the - DiffractionObject. + """Add a scalar value or another DiffractionObject to the yarray + of the DiffractionObject. Parameters ---------- @@ -262,8 +262,8 @@ def __add__(self, other): __radd__ = __add__ def __sub__(self, other): - """Subtract scalar value or another DiffractionObject to the yarray of - the DiffractionObject. + """Subtract scalar value or another DiffractionObject to the + yarray of the DiffractionObject. This method behaves similarly to the `__add__` method, but performs subtraction instead of addition. For details on parameters, returns @@ -290,8 +290,8 @@ def __sub__(self, other): __rsub__ = __sub__ def __mul__(self, other): - """Multiply a scalar value or another DiffractionObject with the yarray - of this DiffractionObject. + """Multiply a scalar value or another DiffractionObject with the + yarray of this DiffractionObject. This method behaves similarly to the `__add__` method, but performs multiplication instead of addition. For details on parameters, @@ -318,8 +318,8 @@ def __mul__(self, other): __rmul__ = __mul__ def __truediv__(self, other): - """Divide the yarray of this DiffractionObject by a scalar value or - another DiffractionObject. + """Divide the yarray of this DiffractionObject by a scalar value + or another DiffractionObject. This method behaves similarly to the `__add__` method, but performs division instead of addition. For details on parameters, returns, @@ -474,7 +474,8 @@ def _get_original_array(self): return self.on_d(), "d" def on_q(self): - """Return the tuple of two 1D numpy arrays containing q and y data. + """Return the tuple of two 1D numpy arrays containing q and y + data. Returns ------- @@ -484,7 +485,8 @@ def on_q(self): return [self.all_arrays[:, 1], self.all_arrays[:, 0]] def on_tth(self): - """Return the tuple of two 1D numpy arrays containing tth and y data. + """Return the tuple of two 1D numpy arrays containing tth and y + data. Returns ------- @@ -494,7 +496,8 @@ def on_tth(self): return [self.all_arrays[:, 2], self.all_arrays[:, 0]] def on_d(self): - """Return the tuple of two 1D numpy arrays containing d and y data. + """Return the tuple of two 1D numpy arrays containing d and y + data. Returns ------- @@ -506,8 +509,8 @@ def on_d(self): def scale_to( self, target_diff_object, q=None, tth=None, d=None, offset=None ): - """Return a new diffraction object which is the current object but - rescaled in y to the target. + """Return a new diffraction object which is the current object + but rescaled in y to the target. By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max intensity from each object. Otherwise, y-value in @@ -568,7 +571,8 @@ def scale_to( return scaled_do def on_xtype(self, xtype): - """Return a tuple of two 1D numpy arrays containing x and y data. + """Return a tuple of two 1D numpy arrays containing x and y + data. Parameters ---------- @@ -597,8 +601,9 @@ def on_xtype(self, xtype): raise ValueError(_xtype_wmsg(xtype)) def dump(self, filepath, xtype=None): - """Dump the xarray and yarray of the diffraction object to a two-column - file, with the associated information included in the header. + """Dump the xarray and yarray of the diffraction object to a + two-column file, with the associated information included in the + header. Parameters ---------- diff --git a/src/diffpy/utils/parsers/serialization.py b/src/diffpy/utils/parsers/serialization.py index 7531c77f..b8ed0c60 100644 --- a/src/diffpy/utils/parsers/serialization.py +++ b/src/diffpy/utils/parsers/serialization.py @@ -33,8 +33,9 @@ def serialize_data( show_path=True, serial_file=None, ): - """Serialize file data into a dictionary. Can also save dictionary into a - serial language file. Dictionary is formatted as {filename: data}. + """Serialize file data into a dictionary. Can also save dictionary + into a serial language file. Dictionary is formatted as {filename: + data}. Requires hdata and data_table (can be generated by loadData). diff --git a/src/diffpy/utils/resampler.py b/src/diffpy/utils/resampler.py index 4bf2d524..354eb37d 100644 --- a/src/diffpy/utils/resampler.py +++ b/src/diffpy/utils/resampler.py @@ -80,8 +80,8 @@ def wsinterp(x, xp, fp, left=None, right=None): def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None): - """One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon - grid. + """One-dimensional Whittaker-Shannon interpolation onto the Nyquist- + Shannon grid. Takes a band-limited function fp and original grid xp and resamples fp on the NS grid. Uses the minimum number of points N required by the Nyquist diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 3a42990d..d6f96174 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -51,8 +51,8 @@ def _load_config(file_path): def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): - """Get name, email, and ORCID of the owner/user from various sources and - return it as a metadata dictionary. + """Get name, email, and ORCID of the owner/user from various sources + and return it as a metadata dictionary. The function looks for information in JSON configuration files named ``diffpyconfig.json``. These can be in the user's home directory and in @@ -109,8 +109,8 @@ def get_user_info(owner_name=None, owner_email=None, owner_orcid=None): def check_and_build_global_config(skip_config_creation=False): - """Check for a global diffpu config file in user's home directory and - creates one if it is missing. + """Check for a global diffpu config file in user's home directory + and creates one if it is missing. The file it looks for is called diffpyconfig.json. This can contain anything in json format, but minimally contains information about the @@ -234,7 +234,8 @@ def get_density_from_cloud(sample_composition, mp_token=""): def compute_mu_using_xraydb( sample_composition, energy, sample_mass_density=None, packing_fraction=None ): - """Compute the attenuation coefficient (mu) using the XrayDB database. + """Compute the attenuation coefficient (mu) using the XrayDB + database. Computes mu based on the sample composition and energy. User should provide a sample mass density or a packing fraction. @@ -287,8 +288,8 @@ def compute_mu_using_xraydb( def _top_hat(z, half_slit_width): - """Create a top-hat function, return 1.0 for values within the specified - slit width and 0 otherwise.""" + """Create a top-hat function, return 1.0 for values within the + specified slit width and 0 otherwise.""" return np.where((z >= -half_slit_width) & (z <= half_slit_width), 1.0, 0.0) @@ -319,9 +320,10 @@ def _model_function(z, diameter, z0, I0, mud, slope): def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): - """Extend z values and I values for padding (so that we don't have tails in - convolution), then perform convolution (note that the convolved I values - are the same as modeled I values if slit width is close to 0)""" + """Extend z values and I values for padding (so that we don't have + tails in convolution), then perform convolution (note that the + convolved I values are the same as modeled I values if slit width is + close to 0)""" n_points = len(z) z_left_pad = np.linspace( z.min() - n_points * (z[1] - z[0]), z.min(), n_points @@ -342,8 +344,8 @@ def _extend_z_and_convolve(z, diameter, half_slit_width, z0, I0, mud, slope): def _objective_function(params, z, observed_data): """Compute the objective function for fitting a model to the - observed/experimental data by minimizing the sum of squared residuals - between the observed data and the convolved model data.""" + observed/experimental data by minimizing the sum of squared + residuals between the observed data and the convolved model data.""" diameter, half_slit_width, z0, I0, mud, slope = params convolved_model_data = _extend_z_and_convolve( z, diameter, half_slit_width, z0, I0, mud, slope @@ -353,7 +355,8 @@ def _objective_function(params, z, observed_data): def _compute_single_mud(z_data, I_data): - """Perform dual annealing optimization and extract the parameters.""" + """Perform dual annealing optimization and extract the + parameters.""" bounds = [ ( 1e-5, @@ -382,8 +385,8 @@ def _compute_single_mud(z_data, I_data): def compute_mud(filepath): - """Compute the best-fit mu*D value from a z-scan file, removing the sample - holder effect. + """Compute the best-fit mu*D value from a z-scan file, removing the + sample holder effect. This function loads z-scan data and fits it to a model that convolves a top-hat function with I = I0 * e^{-mu * l}. diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index ab1d009e..8cb07605 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -76,7 +76,8 @@ def q_to_tth(q, wavelength): def tth_to_q(tth, wavelength): - r"""Helper function to convert two-theta to q on independent variable axis. + r"""Helper function to convert two-theta to q on independent variable + axis. If wavelength is missing, returns independent variable axis as integer indexes. @@ -124,8 +125,8 @@ def tth_to_q(tth, wavelength): def q_to_d(q): - r"""Helper function to convert q to d on independent variable axis, using - :math:`d = \frac{2 \pi}{q}`. + r"""Helper function to convert q to d on independent variable axis, + using :math:`d = \frac{2 \pi}{q}`. Parameters ---------- @@ -144,7 +145,8 @@ def q_to_d(q): def tth_to_d(tth, wavelength): - r"""Helper function to convert two-theta to d on independent variable axis. + r"""Helper function to convert two-theta to d on independent variable + axis. The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}. @@ -178,7 +180,8 @@ def tth_to_d(tth, wavelength): def d_to_q(d): - r"""Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`. + r"""Helper function to convert q to d using :math:`d = \frac{2 + \pi}{q}`. Parameters ---------- @@ -197,7 +200,8 @@ def d_to_q(d): def d_to_tth(d, wavelength): - r"""Helper function to convert d to two-theta on independent variable axis. + r"""Helper function to convert d to two-theta on independent variable + axis. The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right). diff --git a/src/diffpy/utils/utils_app.py b/src/diffpy/utils/utils_app.py new file mode 100644 index 00000000..588e28c9 --- /dev/null +++ b/src/diffpy/utils/utils_app.py @@ -0,0 +1,33 @@ +import argparse + +from diffpy.utils.version import __version__ # noqa + + +def main(): + parser = argparse.ArgumentParser( + prog="diffpy.utils", + description=( + "Shared utilities for diffpy packages.\n\n" + "For more information, visit: " + "https://github.com/diffpy/diffpy.utils/" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument( + "--version", + action="store_true", + help="Show the program's version number and exit", + ) + + args = parser.parse_args() + + if args.version: + print(f"diffpy.utils {__version__}") + else: + # Default behavior when no arguments are given + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/src/diffpy/utils/version.py b/src/diffpy/utils/version.py index a17ccb5c..d94610ae 100644 --- a/src/diffpy/utils/version.py +++ b/src/diffpy/utils/version.py @@ -18,8 +18,9 @@ # __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] # obtain version information -from importlib.metadata import version +from importlib.metadata import PackageNotFoundError, version -__version__ = version("diffpy.utils") - -# End of file +try: + __version__ = version("diffpy.utils") +except PackageNotFoundError: + __version__ = "unknown" diff --git a/src/diffpy/utils/wx/gridutils.py b/src/diffpy/utils/wx/gridutils.py index d6f2874a..d94860ca 100644 --- a/src/diffpy/utils/wx/gridutils.py +++ b/src/diffpy/utils/wx/gridutils.py @@ -146,9 +146,9 @@ def quickResizeColumns(grid, indices): def _indicesToBlocks(indices): - """Convert a list of integer indices to a list of (start, stop) tuples. The - (start, stop) tuple defines a continuous block, where the stop index is - included in the block. + """Convert a list of integer indices to a list of (start, stop) + tuples. The (start, stop) tuple defines a continuous block, where + the stop index is included in the block. Parameters ---------- From c8fbcfefb55cc29894c5a1bc90909abd0e0f6fc6 Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 18 Dec 2025 13:07:44 -0500 Subject: [PATCH 437/445] chore: add news item --- news/scikit-package.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/scikit-package.rst diff --git a/news/scikit-package.rst b/news/scikit-package.rst new file mode 100644 index 00000000..9f7ee0e6 --- /dev/null +++ b/news/scikit-package.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: recut diffpy.utils using latest scikit-package + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From f0d2a4dcb4e8e810d68e3715533eade8768a249b Mon Sep 17 00:00:00 2001 From: stevenhua0320 Date: Thu, 18 Dec 2025 13:09:37 -0500 Subject: [PATCH 438/445] fix: change script character to underscore --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 02377e62..9799bbf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) [project.scripts] -diffpy-utils = "diffpy.utils.app:main" +diffpy-utils = "diffpy.utils_app:main" [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} From e6dbb3c13541b1b1d8a917793cf1693c0b8e8dbe Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Sun, 21 Dec 2025 13:25:31 -0600 Subject: [PATCH 439/445] remove packing fraction from the database mu calculation function --- src/diffpy/utils/tools.py | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index d6f96174..19f8e03b 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -231,49 +231,30 @@ def get_density_from_cloud(sample_composition, mp_token=""): ) -def compute_mu_using_xraydb( - sample_composition, energy, sample_mass_density=None, packing_fraction=None -): +def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density): """Compute the attenuation coefficient (mu) using the XrayDB database. - Computes mu based on the sample composition and energy. - User should provide a sample mass density or a packing fraction. - If neither density nor packing fraction is specified, - or if both are specified, a ValueError will be raised. - Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu. + Computes mu based on the sample composition, X-ray energy, and + sample mass density. + + Reference: + https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu Parameters ---------- sample_composition : str The chemical formula of the material. energy : float - The energy of the incident x-rays in keV. - sample_mass_density : float, ``optional`` - The mass density of the packed powder/sample in g/cm*3. - Default is None. - packing_fraction : float, ``optional`` - The fraction of sample in the capillary (between 0 and 1). - Specify either sample_mass_density or packing_fraction but not both. - Default is None. + The energy of the incident X-rays in keV. + sample_mass_density : float + The mass density of the sample in g/cm^3. Returns ------- mu : float - The attenuation coefficient mu in mm^{-1}. + The attenuation coefficient μ in mm⁻¹. """ - if (sample_mass_density is None and packing_fraction is None) or ( - sample_mass_density is not None and packing_fraction is not None - ): - raise ValueError( - "You must specify either sample_mass_density or packing_fraction, " - "but not both. " - "Please rerun specifying only one." - ) - if packing_fraction is not None: - sample_mass_density = ( - get_density_from_cloud(sample_composition) * packing_fraction - ) energy_eV = energy * 1000 mu = ( material_mu( From 6300e2274c9c7a49782ab5ff3bfa03a2527a039e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Sun, 21 Dec 2025 13:26:34 -0600 Subject: [PATCH 440/445] remove the bad test --- tests/test_tools.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index 6be3870f..18c30fe3 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -9,7 +9,6 @@ from diffpy.utils.tools import ( _extend_z_and_convolve, check_and_build_global_config, - compute_mu_using_xraydb, compute_mud, get_package_info, get_user_info, @@ -270,40 +269,6 @@ def test_get_package_info(monkeypatch, inputs, expected): assert actual_metadata == expected -@pytest.mark.parametrize( - "inputs", - [ - # Test when the function has invalid inputs - ( # C1: Both mass density and packing fraction are provided, - # expect ValueError exception - { - "sample_composition": "SiO2", - "energy": 10, - "sample_mass_density": 2.65, - "packing_fraction": 1, - } - ), - ( # C2: None of mass density or packing fraction are provided, - # expect ValueError exception - { - "sample_composition": "SiO2", - "energy": 10, - } - ), - ], -) -def test_compute_mu_using_xraydb_bad(inputs): - with pytest.raises( - ValueError, - match=( - "You must specify either sample_mass_density or " - "packing_fraction, but not both. " - "Please rerun specifying only one." - ), - ): - compute_mu_using_xraydb(**inputs) - - def test_compute_mud(tmp_path): diameter, slit_width, z0, I0, mud, slope = 1, 0.1, 0, 1e5, 3, 0 z_data = np.linspace(-1, 1, 50) From d41a81755afe1398a577c367e21cf59b040beb00 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Sun, 21 Dec 2025 13:28:35 -0600 Subject: [PATCH 441/445] news --- news/rm-packing-fraction.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/rm-packing-fraction.rst diff --git a/news/rm-packing-fraction.rst b/news/rm-packing-fraction.rst new file mode 100644 index 00000000..70bccad7 --- /dev/null +++ b/news/rm-packing-fraction.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Removed API that calculates mu from packing fraction. + +**Fixed:** + +* + +**Security:** + +* From 8e1d474a969192a4969efd0902dc37f1d51762cf Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 18 Jan 2026 09:40:57 -0500 Subject: [PATCH 442/445] feat: docstring generator function --- src/diffpy/utils/_deprecator.py | 70 +++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 72172cae..570be437 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -20,8 +20,7 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): .. code-block:: python - from diffpy._deprecations import deprecated - import warnings + from diffpy.utils._deprecator import deprecated, d @deprecated("old_function is deprecated; use new_function instead") def old_function(x, y): @@ -38,12 +37,17 @@ def new_function(x, y): .. code-block:: python - from diffpy._deprecations import deprecated + from diffpy._deprecations import deprecated, deprecation_message import warnings warnings.simplefilter("always", DeprecationWarning) - @deprecated("OldAtom is deprecated; use NewAtom instead") + deprecation_warning = build_deprecation_message("diffpy.utils", + "OldAtom", + "NewAtom", + "4.0.0") + + @deprecated(deprecation_warning) class OldAtom: def __init__(self, symbol): self.symbol = symbol @@ -52,8 +56,8 @@ class NewAtom: def __init__(self, symbol): self.symbol = symbol - a = OldAtom("C") # Emits DeprecationWarning - b = NewAtom("C") # No warning + a = OldAtom("C") # Works but emits DeprecationWarning + b = NewAtom("C") # Works with no warning """ if _builtin_deprecated is not None: return _builtin_deprecated( @@ -75,7 +79,6 @@ def wrapper(*args, **kwargs): return obj(*args, **kwargs) return wrapper - raise TypeError( "deprecated decorator can only be applied to functions or classes" ) @@ -83,26 +86,71 @@ def wrapper(*args, **kwargs): return decorator -def deprecation_message(base, old_name, new_name, removal_version): +def build_deprecation_message( + old_base, old_name, new_name, removal_version, new_base=None +): """Generate a deprecation message. Parameters ---------- - base : str + old_base : str The base module or class where the deprecated item resides. + This will look like the import statement used in the code + currently old_name : str The name of the deprecated item. new_name : str The name of the new item to use. removal_version : str The version when the deprecated item will be removed. + new_base : str Optional. Defaults to old_base. + The base module or class where the new item resides. + This will look like the import statement that + will be used in the code moving forward. If not specified, + the new base defaults to the old one. Returns ------- str A formatted deprecation message. """ + if new_base is None: + new_base = old_base return ( - f"'{base}.{old_name}' is deprecated and will be removed in " - f"version {removal_version}. Please use '{base}.{new_name}' instead." + f"'{old_base}.{old_name}' is deprecated and will be removed in " + f"version {removal_version}. Please use '{new_base}.{new_name}' " + f"instead." + ) + + +def generate_deprecation_docstring(new_name, removal_version, new_base=None): + """Generate a docstring for copy-pasting into a deprecated function. + + this function will print the text to the terminal for copy-pasting + + usage: + python + >>> import diffpy.utils._deprecator.generate_deprecation_docstring as gdd + >>> gdd("new_name", "4.0.0") + + Parameters + ---------- + new_name: str + The name of the new function or class to replace the existing one + removal_version: str + The version when the deprecated item is targeted for removal, + e.g., 4.0.0 + new_base: str Optional. Defaults to old_base. + The new base for importing. The new import statement would look like + "from new_base import new_name" + + Returns + ------- + None + """ + print( + f"This function has been deprecated and will be " + f"removed in version {removal_version}. Please use" + f"{new_base}.{new_name} instead." ) + return From c3bd3b84423b7d657bfaff6b18ba92d84d6a67ed Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 18 Jan 2026 09:45:15 -0500 Subject: [PATCH 443/445] news --- news/deprecator.rst | 1 + src/diffpy/utils/_deprecator.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/news/deprecator.rst b/news/deprecator.rst index dabbfdf1..01b88147 100644 --- a/news/deprecator.rst +++ b/news/deprecator.rst @@ -1,5 +1,6 @@ **Added:** +* added a function in _deprecator to generate a deprecation message for copy pasting * Add ``@deprecated`` decorator. **Changed:** diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 570be437..956f2471 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -133,6 +133,10 @@ def generate_deprecation_docstring(new_name, removal_version, new_base=None): >>> import diffpy.utils._deprecator.generate_deprecation_docstring as gdd >>> gdd("new_name", "4.0.0") + The message looks like: + This function has been deprecated and will be removed in version + {removal_version}. Please use {new_base}.{new_name} instead. + Parameters ---------- new_name: str From 820cd8b1e78b8977470652bbe125e5c4d12e70fe Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 19 Jan 2026 04:47:07 -0800 Subject: [PATCH 444/445] docs: fix docstrings showing usage in deprecated() function --- src/diffpy/utils/_deprecator.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 956f2471..485ef8bd 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -20,17 +20,24 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): .. code-block:: python - from diffpy.utils._deprecator import deprecated, d + from diffpy._deprecations import deprecated, deprecation_message - @deprecated("old_function is deprecated; use new_function instead") + deprecation_warning = build_deprecation_message("diffpy.utils", + "old_function", + "new_function", + "4.0.0") + + @deprecated(deprecation_warning) def old_function(x, y): - return x + y + '''This function is deprecated and will be removed in version + 4.0.0. Please use new_function instead''' + return new_function(x, y) def new_function(x, y): return x + y - old_function(1, 2) # Emits DeprecationWarning - new_function(1, 2) # No warning + old_function(1, 2) # Works but emits DeprecationWarning + new_function(1, 2) # Works, no warning Deprecating a class: @@ -38,9 +45,6 @@ def new_function(x, y): .. code-block:: python from diffpy._deprecations import deprecated, deprecation_message - import warnings - - warnings.simplefilter("always", DeprecationWarning) deprecation_warning = build_deprecation_message("diffpy.utils", "OldAtom", @@ -49,8 +53,14 @@ def new_function(x, y): @deprecated(deprecation_warning) class OldAtom: - def __init__(self, symbol): - self.symbol = symbol + def __new__(cls, *args, **kwargs): + warnings.warn( + "OldAtom is deprecated and will be removed in + version 4.0.0. Use NewClass instead.", + DeprecationWarning, + stacklevel=2, + ) + return NewAtom(*args, **kwargs) class NewAtom: def __init__(self, symbol): From a3ca6c307931fbf3dcd45de19e1a001dfb6ec7bc Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 22 Jan 2026 16:41:50 -0800 Subject: [PATCH 445/445] fix: typos in docstring --- src/diffpy/utils/_deprecator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/utils/_deprecator.py b/src/diffpy/utils/_deprecator.py index 485ef8bd..560c9530 100644 --- a/src/diffpy/utils/_deprecator.py +++ b/src/diffpy/utils/_deprecator.py @@ -20,7 +20,7 @@ def deprecated(message, *, category=DeprecationWarning, stacklevel=1): .. code-block:: python - from diffpy._deprecations import deprecated, deprecation_message + from diffpy.utils._deprecator import deprecated, deprecation_message deprecation_warning = build_deprecation_message("diffpy.utils", "old_function", @@ -44,7 +44,7 @@ def new_function(x, y): .. code-block:: python - from diffpy._deprecations import deprecated, deprecation_message + from diffpy.utils._deprecator import deprecated, deprecation_message deprecation_warning = build_deprecation_message("diffpy.utils", "OldAtom",